Music (OPL2/OPL3 MUS+MIDI) and multiplayer (netgame lockstep) - #3
Merged
Conversation
… 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.music/musmus2midmus2mid.c(compiled oracle over synthetic MUS vectors)music/midimidifile.c)D_INTROto end-of-trackmusic/genmidimusic/oplmusic/oplplayeri_oplmusic.c)i_oplmusic.cforD_INTRO(looping)Engine wiring:
initMusicModule()installs the OPL synth asmusic_module;i_RegisterSongreturns 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 vanillasaveg_write_ticcmd_t),Doomdatapackets (vanilla checksum +NCMD_*flags),Doomcomhandshake, and a deterministic lockstep loop (maketic/gametic,BACKUPTICS=128window, resend/backoff, consistancy check →DesyncError, lost-nodeTimeoutError). InjectableTransportseam: in-memory mesh (deterministic drop/reorder) + realnet.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
go vet -unsafeptr=falseclean;go.modfloor → 1.26.4.music/TESTDATA.md(Freedoom, BSD-3-equivalent).Residuals (honest)
i_oplmusic.cdoes.netgameis a validated standalone protocol library; binding it to drive the transpiled engine'sG_Tickeracross hosts is follow-up work that must preserve the frame oracle.Readpath advances a sample-quantized clock (faithfulopl_sdlport) — identical register values/order, sample-accurate firing.🤖 Generated with Claude Code