Skip to content

Music (OPL2/OPL3 MUS+MIDI) and multiplayer (netgame lockstep) - #3

Merged
tannevaled merged 3 commits into
mainfrom
feat/music-multiplayer
Jul 27, 2026
Merged

Music (OPL2/OPL3 MUS+MIDI) and multiplayer (netgame lockstep)#3
tannevaled merged 3 commits into
mainfrom
feat/music-multiplayer

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Implements the two remaining engine features as pure-Go (CGO=0) packages, wired into the transpiled engine without disturbing its determinism oracles. Reference behaviour: chocolate-doom / Nuked-OPL3.

Music — chocolate-doom OPL path (music/)

MUS/MIDI parsing + Yamaha YMF262 (OPL2/OPL3) synthesis driving the DMX GENMIDI bank, PCM pulled via gore.ReadMusicPCM.

Package Role Differential oracle Coverage
music/mus DMX MUS parse + mus2mid byte-identical to chocolate-doom mus2mid.c (compiled oracle over synthetic MUS vectors) 100%
music/midi Standard MIDI File parser (midifile.c) parses Freedoom D_INTRO to end-of-track 100%
music/genmidi GENMIDI instrument-bank parser parses the 175-instrument bank 100%
music/opl Nuked OPL3 emulator → PCM bit-exact vs Nuked-OPL3 C register→PCM trace 100%
music/oplplayer MIDI+GENMIDI → OPL regs → PCM (i_oplmusic.c) byte-identical OPL register-write trace vs i_oplmusic.c for D_INTRO (looping) 100%

Engine wiring: initMusicModule() installs the OPL synth as music_module; i_RegisterSong returns the module handle. music_opl.go / music_bridge.go.

Multiplayer — classic doomcom/ticcmd lockstep (netgame/)

Faithful model of id's d_net.c/i_net.c: Ticcmd (byte-faithful to the engine's vanilla saveg_write_ticcmd_t), Doomdata packets (vanilla checksum + NCMD_* flags), Doomcom handshake, and a deterministic lockstep loop (maketic/gametic, BACKUPTICS=128 window, resend/backoff, consistancy check → DesyncError, lost-node TimeoutError). Injectable Transport seam: in-memory mesh (deterministic drop/reorder) + real net.UDPConn.

Oracle: 2/3/4 in-process engines fed identical inputs stay in lockstep with byte-identical per-tic state-hash histories; 30% loss + reorder still converge identically; injected desync detected. Coverage 98.3% (residual = defensive Encode/Unmarshal error branches unreachable with valid ≤128-cmd windows).

Provable-test protocol (4-gate)

The engine's committed frame PPMs and audio-event log remain byte-equal after wiring (re-harvested and diffed) — music/multiplayer do not perturb determinism.

Conformance

  • CGO=0 throughout; builds on all six 64-bit arches (amd64/arm64/riscv64/loong64/ppc64le/s390x).
  • gofmt + go vet -unsafeptr=false clean; go.mod floor → 1.26.4.
  • SPDX headers honour upstream licenses: GPL-2.0-or-later (chocolate-doom-derived), LGPL-2.1-or-later (Nuked OPL3), BSD-3-Clause (netgame, original). Test-data provenance in music/TESTDATA.md (Freedoom, BSD-3-equivalent).

Residuals (honest)

  • OPL player handles all instruments/percussion/driver-version quirks; unhandled MIDI event types (aftertouch/sysex) are ignored exactly as i_oplmusic.c does.
  • netgame is a validated standalone protocol library; binding it to drive the transpiled engine's G_Ticker across hosts is follow-up work that must preserve the frame oracle.
  • The oplplayer register-trace oracle uses the reference's event-driven timer model (µs-exact); the production Read path advances a sample-quantized clock (faithful opl_sdl port) — identical register values/order, sample-accurate firing.

🤖 Generated with Claude Code

tannevaled and others added 3 commits July 27, 2026 16:56
… PCM)

Implement the chocolate-doom OPL music path in pure Go (CGO=0), wiring
synthesised music into the transpiled engine's existing music_module hook.

New packages under music/:
- mus:       DMX MUS (D_* lumps) parser + mus2mid MUS->MIDI conversion,
             asserted BYTE-IDENTICAL to chocolate-doom's mus2mid.c compiled
             as an oracle over hand-authored synthetic MUS vectors.
- midi:      Standard MIDI File parser (port of midifile.c).
- genmidi:   GENMIDI lump parser (the DMX OPL instrument bank).
- opl:       Nuked OPL3 (Yamaha YMF262) emulator, validated BIT-EXACT
             against a Nuked-OPL3 C register->PCM trace.
- oplplayer: MIDI + GENMIDI -> OPL register writes -> PCM (port of
             i_oplmusic.c), validated against a captured i_oplmusic.c
             register-write trace for D_INTRO (byte-identical, looping).

Engine wiring (music_bridge.go / music_opl.go): initMusicModule() installs
the OPL synth as the active music_module; the host frontend pulls stereo PCM
through gore.ReadMusicPCM. i_RegisterSong now returns the module handle.

The frame-determinism and audio-event oracles remain byte-equal after wiring
(the 4-gate provable-test protocol is respected). go.mod floor -> 1.26.4.

Coverage: mus/midi/genmidi/opl/oplplayer all 100%. Builds CGO=0 on all six
64-bit arches. Ports carry upstream SPDX headers (GPL-2.0-or-later for the
chocolate-doom-derived code; LGPL-2.1-or-later for the Nuked OPL3 port).
Testdata provenance/licensing documented in music/TESTDATA.md (Freedoom).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implement the classic DOOM netgame protocol as a self-contained, testable
pure-Go package (CGO=0), modelling id Software's d_net.c / i_net.c doomcom /
doomdata / ticcmd lockstep exchange (chocolate-doom-faithful behaviour).

- ticcmd.go:    Ticcmd + byte-faithful serialization, identical to the
                engine's own vanilla 8-byte saveg_write_ticcmd_t layout.
- packet.go:    Doomdata packet (vanilla checksum/flags word, retransmitfrom,
                starttic, player, numtics, cmds[]) encode/decode with the
                NCMD_* command flags and the vanilla running checksum.
- doomcom.go:   Doomcom descriptor + shared Config.
- transport.go: injectable Transport seam -- in-memory mesh (deterministic
                drop/reorder for tests) and a real net.UDPConn transport.
- net.go:       deterministic lockstep loop -- handshake/node discovery,
                per-tic ticcmd exchange, maketic/gametic advancement with a
                BACKUPTICS=128 send window, unsolicited + explicit
                NCMD_RETRANSMIT resend/backoff, the classic consistancy check
                (DesyncError), and lost-node TimeoutError. No crashes.

Differential oracle: two, three and four in-process engines fed identical
deterministic inputs over the in-memory transport stay in lockstep with
byte-identical per-tic state-hash histories and identical final state; 30%
packet loss and reorder still converge identically; an injected desync is
detected. Coverage 98.3% (residual = defensive error branches unreachable
with valid inputs -- Encode/Unmarshal that never fail on <=128-cmd windows).
Builds CGO=0 on all six 64-bit arches. BSD-3-Clause (original work).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two CI-only fixes surfaced by PR #3's first run:

1. 6-arch QEMU jobs run the `go test -c` binaries from the module root, not
   the package dir, so relative "testdata/..." reads failed (and the module
   root has its own unrelated testdata/). Each fixture-reading music package
   now has a test-only init() that locates the go.mod ancestor and chdir's
   into its own directory, so reads resolve under both native `go test`
   (cwd = package dir) and the QEMU harness (cwd = repo root). The
   differential oracles now run on all six arches, not just amd64/arm64.

2. The go.yml `deadcode` gate flagged the new music/netgame code: it is a
   library consumed via runtime func-pointer indirection (the engine's
   music_module) and by the cgo frontends (excluded from deadcode), so the
   curated headless roots never reach it statically. Add the pure-Go
   music/... and netgame packages as their own -test roots (their tests
   anchor reachability) plus the root engine package (.), whose new
   music_opl_test.go exercises the OPL bridge / ReadMusicPCM. deadcode is
   clean again.

Verified locally: music tests pass when run from the repo root (QEMU
equivalent); `deadcode -test . ./example/webserver ./cmd/harvest-reference
./embedwad ./music/... ./netgame` reports nothing; engine builds + vets on
386 and all six 64-bit arches; the engine runs to completion with music
active on the real shareware WAD (MUS + DMX GENMIDI), and oplplayer renders
non-silent PCM from a real shareware MUS lump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit 6a751c5 into main Jul 27, 2026
7 checks passed
@tannevaled
tannevaled deleted the feat/music-multiplayer branch July 27, 2026 15:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant