Investigate AO music and implement PRK.Client authored music playback
Outcome Understand AO's authored music selection and transition behavior, then implement native PRK.Client music using the existing audio foundation rather than a separate player or a folder-based playlist. Dependencies and scope - Follow-up to audio PR #2551 (feature/client-audio-first-iteration -> develop), implementation tsk_vjf59g4jc2, and broader research tsk_n87n48dj3s. The foundation PR is still open and has outstanding platform/device/release gates; this task does not close or duplicate them. - Confirmed first implementation slice: in-game district/environment music, combat/death transitions and return to environment music, with authored layers/pauses/loops/fades and original installed assets. Confirm actual trigger and priority semantics before implementing; do not infer them from filenames or animation stance. - Limit this milestone to the gameplay music slice above. Defer login/character selection/creation and land-control music per user direction. Scripted encounter override integration is outside the initial slice. Inspect special-trigger paths only as needed to establish core selection/priority semantics; record deferred coverage explicitly rather than silently substituting generic music. - Implementation is in progress on feature/client-gameplay-music, based on audio PR commit b79de7dfa. Committed as 74b587e28 (feature: Add authored client music playback). Login/front-end, land-control and special encounter integration remain deferred. Repository evidence and reuse - PRK.Client/PRK.Client/Audio/ClientAudio.cs owns bounded queues, decode and OpenAL owner threads, pooled voices, preferences publication and generation-safe reset. AudioCategory.Music and AudioVolumes already route MasterVolume/MusicVolume/SoundOnOff, but requests still resolve SBF entries and fully decode clips. - AudioDecoder.cs supports PCM/IMA ADPCM WAV, MPEG-in-WAV and Ogg/Vorbis using NLayer/NVorbis, with whole-file/sample limits. OpenAlBackend.cs/IAudioBackend expose static-buffer playback only: no streaming, grouped start or sample-clock scheduling yet. Simply raising clip budgets or replaying SBF requests is not a music implementation. - Tools/RDBExtractor/Extractors/SwsClasses.cs contains historical project/layer/sample/pause/transition/fade/chord shapes. SWSExtractor.cs is commented-out exploratory code, not a functioning parser. RDBLib2 FuncomSerializer provides format/registration precedents, but bounds validation and NativeAOT compatibility must be assessed before reuse; do not blindly copy its reflection-based path into the client. - RdbDistrict.cs retains 18 MusicIds bytes per district plus positions; meaning, district selection and mapping to SWS layers remain unverified. Existing WorldManager playfield events and authoritative character/session state, Bootstrap audio publication/reset and Sky/TweakScriptSource/parser/model are integration reuse points. - Docs/TWK-EncounterSystem-ClientAnalysis.md records CombatMusicOverride and scripted district override set/pop behavior; use as leads requiring binary/call-site corroboration. - Installed /mnt/games/Anarchy Online/cd_image/sound/music/env/anarchy.sws is available with environment/battle/expansion/land-control assets. Prior research found SandyInterface/SIMPlayer music APIs and a sim.config path reference, but no sim.config file was found in the inspected installation. Establish whether this is optional/defaulted/version-dependent, not an assumed required asset. Implementation approach and constraints 1. Trace original Gamecode/N3/SandyInterface/SIMPlayer selection, SWS loading, priority, playback and teardown paths; correlate representative district data and installed assets. Preserve binary hashes/function addresses and distinguish proven rules from unknowns. Import neighboring DLLs together through ghidra_container before Ghidra MCP; no host analyzeHeadless. 2. Establish a validated typed SWS/music catalog shared with diagnostic/tooling needs where practical, retaining authored metadata and checking reference graphs, counts, timings and safe paths. Reuse format readers and existing tweak parsing instead of parallel ad hoc readers. 3. Extend existing decoding/backend/runtime boundaries with bounded worker-fed stereo streaming and an audio/sample-clock scheduling model. Prototype synchronized layers and transitions first; independent source starts or frame-tick crossfades are not presumed sufficient. Share device/context ownership, asset resolution, codecs, source budgeting and lifecycle; reserve music capacity so effect saturation cannot accidentally prevent required music. 4. Add a MusicDirector responsible for authored selection/transition state, fed by immutable/coalesced world/session inputs. Keep it independent of graphics effects. Wire the agreed gameplay slice through existing playfield/character state and preference flows. Research determines priority, retrigger, silence, mute/resume and zone-continuity semantics. 5. Validate installed assets, deterministic state/timing behavior, real playback, lifecycle, performance and packaged NativeAOT clients; update coverage and platform gaps explicitly. Performance/platform constraints Native Windows/Linux/macOS through the already approved separately shipped OpenAL Soft; no proprietary Miles/SIMPlayer runtime dependency or asset conversion requirement. Preserve stereo and authored timing. Keep frame publication nonblocking, decoding/I/O off frames and backend-owner ticks, bounded PCM queues/memory, and warmed scheduling/refill paths allocation-free where feasible and measured. Check decoder internal allocations and native streaming queues: the foundation's static-buffer optimization and measured allocation baseline do not prove streaming behavior. Use temporary measurement and lightweight regression checks, not a permanent profiling subsystem. Any decoder/native dependency or native source patch must preserve AOT and distribution/license obligations. No game assets committed without redistribution clearance. Acceptance criteria - Evidence-backed music format/selection/transition map establishes the gameplay slice and explicitly identifies unsupported or unresolved special/screen triggers, including sim.config behavior. - Parser and installed music catalog validate authored layers/samples/transitions/pause/fade metadata and resolve the required assets; corrupt/truncated data, invalid references, excessive counts and unsafe paths fail safely. - Gameplay environment/district, combat/death and return transitions follow the verified authored rules. Repeated unchanged state does not restart music; layer alignment and transition timing meet a measured, evidence-derived tolerance rather than a guessed one. - Long music streams with bounded memory and stereo preserved, without frame-thread decoding or starving effects; effect saturation, delayed I/O and underruns have explicit safe policies. No parallel audio device/context/runtime. - Existing master/music/mute preferences work with verified mute/resume semantics. Rapid zoning/logout/reconnect/shutdown cannot leak sources/readers or start stale prepared music; unavailable audio and missing/corrupt assets do not crash the client. Zone continuity is reconciled with generation cancellation rather than inheriting unconditional one-shot reset behavior blindly. - Synthetic parser/reference fixtures, deterministic fake-clock/backend director tests, installed-corpus resolution/streaming checks, long-run layer drift/loop/transition and underrun checks, effect coexistence and repeated lifecycle tests pass. Audible comparison with original AO verifies representative environment/combat/death paths. - Client build/regressions and available native/AOT packaged checks pass; Windows/macOS and audible checks unavailable on this host remain explicit outstanding gates. Quantify added frame/worker/memory and warmed allocation costs; do not claim universal no-impact or allocation-free behavior without evidence. Confirmed scope decision Implement gameplay district/environment music and combat/death transitions first. Hold off on login/front-end and land-control music; do not expand this milestone into special encounter integrations. Current implementation and verified evidence - Added explicit, bounded, reflection-free SWS v1 parsing/models in RDBLib2/Audio/MusicProject.cs and MusicProjectReader.cs. Validate archive/record framing and byte budgets, typed members, all references/counts, finite values and relative paths. Preserve project sample IDs separately from archive references, normal/preparation transitions, sample offsets/tempos/chords, layer volumes and pause tracks. Musical cycles are legal. Samples store fades in/out; transitions store out/in, represented by different types to prevent accidental reversal. - AudioAssetIndex is shared by effects and music for case-insensitive asset resolution and reparse-point exclusion. MusicCatalog resolves exact authored layer/name.wav paths and retains explicit missing-asset records. The player is now implemented in AudioStreamReader, MusicDirector and MusicPlayer, integrated with ClientAudio/OpenAlBackend and gameplay. Diagnostics include --music-catalog, --music-census and --music-play <installation> <layer> [seconds]; run-client-dev.sh accepts AO_CLIENT_ROOT defaults without prepending normal game arguments. - The installed archive parses completely: 91 layers, 1,257 samples, 4,620 transitions, 20,994 fade objects, 26 pause tracks and 246 chords. 1,237 sample paths resolve. Twenty Adonis paths remain unresolved because SWS names EP_01/Adonis_Mightycastle while the installation contains EP_01/adonis_mighty_castle. No fuzzy lookup, fallback playlist or hard-coded rename is introduced. Determine original directory remapping/default behavior before resolving this compatibility gap; missing sim.config is not yet fully explained. - Synthetic tests cover metadata/reference spaces, cycles, distinct fade ordering, every truncated prefix, trailing data, counts, typed references, invalid boolean/float/timing/path data, unsupported/orphan records, empty/silent layers, missing assets, ambiguous case aliases and symlink exclusion. Installed parser/catalog tests pass. Focused music/audio/frame/preference regression: 217 passed, 1 native conditional test skipped, excluding five install-profile checks whose fixture is unavailable. The broader 223-test attempt was not clean: missing prefs/Prefs.xml, Wine symlink traversal in an existing install-tree test, and native library ABI mismatch in the SDK container. Do not treat the wider suite as passing. - Linux NativeAOT publish succeeded; host native --music-catalog confirms the counts/20 unresolved paths and intentionally returns 1 for incomplete resolution. Host NativeAOT --audio-check with ALSOFT_DRIVERS=null passed the four original codec families, 128 independent sources, mono/stereo rebinding, installed GUI playback and reset. Container native unit validation could not load the host-built OpenAL library (requires glibc 2.43); this is separate from the passing host native smoke test and existing platform/release gates. Original-client trace anchors for continuation - SandyInterface.dll SHA256 8827f94909b31d1173edc0cc331e2b65b60b0b3138ab8a8976f17695311648ce; SIMPlayer.dll d8592826737b77ac9cc09b3cc52ab6be1723a9eed66b29e094e11e3f0deff6bb; Gamecode.dll 654969a6b65946cb161f0e60aed8589260fc5eca1795488f66bb56f8fff73726; N3.dll 8c019efd72d547879a06585b69147ab1546b9617a2fce090e5863791aec8b0bb. Installed SWS SHA256 3cf90c2ff722eaf6fdc622eb2e27128c29dad9e2fb28b7d7cbb77dab36a7e9f1. DLLs imported together under /data/inbox before Ghidra MCP. - Sandy LoadSimProject 0x10005ffa loads anarchy.sws, calls LoadDirectoryCfg on sim.config and resolves explicit battle/death/rebirth/victory layer names. PlayMusic 0x100062e4 suppresses unchanged layer requests, converts 0xffff to no-layer, sends SignalEvent with force flag and 2000 ms and starts/stops SIMPlayer. SetupENVSounds 0x1000657d records district ID and handles the deferred land-control sentinel. - SIM CProject Archive 0x10003534 emits layer IDs/names/pause/volume plus ordered sample references. CTransition Archive 0x1000afa2 emits source/destination IDs as positions in that sample list, not archive IDs. GetEntrySample 0x100033b9 randomly chooses flagged entry samples, with all-layer samples as fallback if entry-only selection is not required; layers are selection groups, not assumed simultaneous stems. - CSample GetFileName 0x100080b3 appends .wav. GetAbsoluteSampleName 0x100041b7 combines layer/name and the directory mapping; LoadDirectoryCfg 0x10004b71 remains a continuation lead for default/remap semantics. - CTransition constructors 0x1000a90e/0x1000aa1e establish outgoing fade from source GetFadeInfo(1), incoming from destination GetFadeInfo(0). RecalcFadeTimes 0x1000adaa sets source start to source end marker time minus destination total offset, and fade duration to that offset. Preparation 0x1000a143 seeks destination to fit before pausing; 0x1000963a builds fade tables for ftime and records source fot; 0x100096b6 starts prepared playback at the source marker. Timing fields are modeled as SourceStartMilliseconds, DestinationOffsetMilliseconds and FadeDurationMilliseconds, not three durations. - SIM SignalEvent 0x1000a599 uses unchanged-layer suppression in 0x10008b89. Transition selection 0x10008d2e searches future authored edges with a 10 ms lead, earliest target-layer starts and same-layer continuation; forced generated-transition fallback needs complete corroboration before implementation. PauseTrack GetNextNode 0x10002a3e uses the authored loop index after the last node. - Gamecode environment update 0x100b72bf selects district data from the controlled dynel's zone, samples weather thresholds >0.4 or GameTime phase+1, applies layer overrides and calls ActivateEnvZone about once per second. Verify the 18-byte music table's on-disk mapping/day-phase/weather slots before wiring it. CombatUpdate wrapper 0x1001126c is called from 0x10059c7d; combat/death wrappers 0x1001124a/0x1001125b have callers in 0x100690c8/0x1006a1b1. Sandy ProcessCombatMusic 0x10001eef is considerably richer than animation stance: health/side/opponent inputs, preferences, rebirth/death/victory and timed holds. Exact parameter semantics and client event mapping remain to verify. Player implementation and current validation - User requires close behavioral compatibility implemented idiomatically in C#, not a copy/port of decompiled client internals. MusicDirector is a worker-owned typed state machine; one 44.1 kHz stereo sample timeline mixes source/destination clips at authored markers/offsets with continuous circular/sine fade curves, avoiding independent source-clock drift. Prefer entry samples; choose future authored bridges/continuations with earliest cross-layer starts and bounded anti-repetition weights. Generated forced bridges follow the verified 2000 ms/three-quarter-duration limits. Terminal clips end rather than looping automatically. Pause windows finish the current clip and resume using the authored pause-node loop index. - AudioStreamReader incrementally decodes PCM WAV, MPEG-in-WAV and Ogg/Vorbis; WaveContainer and PCM conversion are shared with short effects. All resolved installed music was verified at 44100 Hz mono/stereo, so the director preserves that layout rather than adding a managed resampler. Other music rates and streaming IMA are explicitly refused; IMA effects remain supported. No whole-song PCM allocation. Eight reusable 512-frame stereo handoff blocks, three reusable track buffers and four OpenAL streaming buffers bound queued PCM; MP3 indexes/decoder state are additional cold-load memory. - Music uses a reserved 129th OpenAL source alongside 128 effect sources, sharing the existing owner thread/device/context and packaging. The music decoder runs on its own worker; I/O is not performed by frame publication or native playback ticks. Coalesced controls and epoch checks cancel stale prepared music, including reset/mute racing with buffer consumption. Output starvation resumes queued sample order; effect saturation does not consume music capacity. - Gameplay uses copied district tables and zone-to-district mappings, not nearest-center guesses. Outdoor zones reuse the existing 40-unit grid; indoor room indices map back to resource zone indices. GameDayClock supplies dawn/day/dusk/night slots. Health/opponent snapshots supply combat intensity and faction death/rebirth plus victory holds, independent of animation/graphics effects. Static playfield combat overrides use the existing tweak assembly/parser; sky and music share a bounded parsed-definition cache rather than duplicate parsers. Main-thread source/metadata work happens at zone/context changes; no music file decoding is performed there. - Master/music/global mute and existing MusicOn are wired. Volume changes preserve playback; mute cancels streaming, and re-enabling starts the current gameplay request. Session-owned world-independent music intentionally survives zoning/reconnection and unchanged layer requests, matching AO's global music lifetime; world effect requests still cancel on generation changes. Logout/front-end/error transitions stop music and clear cue history. This is the explicit distinction between permitted zoning continuity and stale world-owned audio, not an unconditional reset of music on every world unload. - Verified final focused music/audio/targeting/frame/tweak/preferences/session/clock/message regression: 304 passed, 1 conditional native test skipped. Five unavailable stock-profile/install-tree checks were excluded as previously documented, so the full suite is not claimed clean. Tests cover partition-independent rendering over about 57 seconds of cyclic music, authored bridges, circular/sine envelopes, terminal clips, pause-node looping, long PCM streaming beyond effect budgets, exact PCM seeks, installed PCM/MPEG read/seek, malformed/missing assets, mute/reset races, output backpressure/recovery, day/zone mappings, combat holds and installed override resolution. bash -n and git diff --check pass. - Linux NativeAOT publish passes with no new C#/AOT warnings. Host/null-driver --audio-check passes all four effect codec families, 128 effect voices plus reserved music, all-buffer streaming consumption, initial/playing queue reset, rebinding, installed GUI playback and world reset. NativeAOT --music-play forest/Day follows authored clip transitions; battle/NeutralVictory ends into silence without repeating. These are API/scheduling/null-sink checks, not an audible comparison or a verified in-game play session. - Full incremental music census: 1,237 distinct resolved files read to completion, zero decode/rate/frame-count failures. Twenty Adonis paths remain unresolved; census intentionally exits 1 for that coverage gap. No heuristic rename or generic playlist fallback was added. Additional original-client evidence - GameData.dll district deserialization 0x100049be reads nine unsigned 16-bit music-table values in order. GetDistrictData(zone) 0x10009be2 indexes the explicit zone mapping; ReadBlob 0x10009def validates that mapping. Gamecode UpdateDayPeriod 0x1000b22c corroborates dawn/day/dusk/night indices; music requests use day period + 1 or weather slots 5/6/7. - Sandy constructor 0x10002b64 initializes forced-transition flag at +0x10d to true. SetCombatMusicMode 0x100021be and SetDeathMusicMode 0x10002216 are no-ops in this version: actual cues come from CombatUpdate/ProcessCombatMusic. The managed policy expresses health/threat thresholds and 5/25/20 second holds with meaningful names, not native field offsets or control-flow copying. - SIM fade archive 0x10001785 establishes curve/condition/factor/levels. CalcFadeTable 0x10001c17 and formula routines 0x10001b2e/0x10001a89, corroborated by disassembly/constants, establish disabled/unity, circular-factor and sine-factor curves. The managed version evaluates continuously rather than allocating stepped lookup tables. - Selection helper 0x10008c7e raises unchosen runtime edge weights and resets at 1000; these are separate from archived priority. Managed selection preserves the variety policy without the native modulo/boundary quirks. End callback 0x10009529 moves an unbridged completed stream to terminal state 6 or paused state 0; no implicit file looping was inferred. Pause processor 0x10002761 uses integer 60000/BPM beat milliseconds and finishes current clips before quiet periods; GetNextNode uses loopto. Performance evidence and remaining gates A temporary probe outside the repository measured unchanged music publication at zero caller allocations (100,000 calls in about 6.4 ms). Warmed director/MPEG refill measured 18,816 managed bytes and about 23.6 ms for 1.161 seconds of music in this isolated managed-container probe. Exact NLayer 3.0.0 source commit 046c7ce422970f8f0f0bc205d6c6341bcb7debf1 confirms FrameBase.SaveBuffer allocates a byte array per MPEG frame and ClearBuffer releases it. This is roughly 16 KiB/s of continuous decoder allocation, not a proven allocation-free path or a cold-load-only claim. Decide/implement bounded buffer reuse in the decoder dependency before meeting the stricter allocation gate; no reflection hacks or dependency fork was introduced silently. Native streaming allocator behavior, rendered-engine comparison, real-device timing and broader scenes still need measurement. No runtime counters or permanent profiling subsystem were added. Remaining work - Verify/integrate weather-driven music slots with actual weather inputs; the current gameplay path uses day-period selection. Verify the original combat preference source/default; current managed baseline uses battle mode 2 without new user-facing controls. - Resolve the Adonis directory/default-remapping and missing sim.config behavior from evidence. Broader preparation/chord/editor metadata is preserved, not claimed to have independent runtime behavior without corroboration. - Audible original-AO comparison and real in-game district/combat/death/volume/zoning/reconnect checks; longer device/IO-stall stress, allocation removal/measurement and engine-wide performance comparison; Windows/macOS native/AOT/signing/library replacement and existing release compliance gates. Task remains in_progress. Login/front-end, land-control and scripted encounter integrations remain excluded. Implementation committed as 74b587e28 on feature/client-gameplay-music. Not pushed. Gameplay flow diagnostic follow-up User reports poor in-game music flow/transitions; behavior is not considered audibly validated. Added uncommitted, always-on timestamped [music] console diagnostics for changed gameplay inputs/selected cues, layer requests, sample loads, authored/generated/continuation bridge preparation, marker wait/fade timing and envelopes, transition start/completion, terminal silence, pause nodes, mute/gain/epoch resets and session teardown. Director messages include render-frame positions (decode timeline, not measured device playback time). Unchanged gameplay decisions/requests and repeated no-entry/wait states are suppressed. Playback rules are unchanged. Verification: client build through container test runner; focused Music/Audio tests 45 passed, 6 installed-asset/native conditional tests skipped; regression asserts transition diagnostics and unchanged-request suppression. git diff --check passes. Original-player investigation of supplied mountain/day logs confirmed a concrete outgoing-tail lifecycle mismatch. The tail-lifecycle fix is implemented and verified in the uncommitted working tree; audible in-game confirmation remains outstanding. - SIMPlayer.dll hash matches d8592826737b77ac9cc09b3cc52ab6be1723a9eed66b29e094e11e3f0deff6bb. Imported SIMPlayer/SandyInterface/mss32 together into /data/inbox before Ghidra MCP. Decompilation plus raw x86 disassembly corroborate the findings. - CFadeInfo::CalcFadeTable 0x10001c17 (unity branch 0x10001c90) returns constant 1 for ctype=0; fcond=2 is not a hidden crossfade in this runtime calculation. Do not substitute arbitrary fades. - Fade processor 0x1000972d updates gains during nonzero durations, then at completion only swaps current/prepared slot indices (+0x24/+0x2c) and returns to selection state. It does not close/stop the outgoing stream or force its final gain to zero. Zero-duration transitions skip gain updates entirely, retaining old playback gain. Our MusicDirector.CompleteTransition unconditionally clears/disposes _current, truncating that audible tail. - Original uses three stream slots (initialization 0x1000984d, volume application 0x100092dd). Preparation 0x1000a2c8 occurs about 200 ms before the upcoming marker. Preparation 0x1000a143 preserves a still-playing outgoing slot by rotating it into the spare slot (+0x28), closes/reuses the prior spare, and opens/seeks/pauses the next clip. Subsequent nonzero fade processing resets all three gain slots and applies the two active envelopes. Thus retain bounded tails/slot reuse, not unlimited full-clip overlap and not a naive two-slot swap followed by immediate next-load disposal. - Start 0x100096b6, fade setup 0x1000963a, transition archive 0x1000afa2 and constructors corroborate fot as source start, fit as destination seek and ftime as overlap/fade duration. Supplied early markers alone do not prove bad timing parsing: they start the successor before the source file ends. - Independent ffmpeg decode confirms material after the observed 5.216-second cut: MD04 and MD28 each have ~5.024 seconds remaining (RMS ~0.0372/~0.0423); MD18 has ~5.155 seconds remaining (RMS ~0.0382). These are real non-silent tails being discarded, not merely silent file padding. Implemented fix and validation - MusicDirector now owns exactly three reusable streams. Handoff retains the outgoing reader and both final envelope gains; zero-duration handoffs do not fabricate fades or stop the source. Selection and opening are separate: opening/seek occurs 200 ms before the marker, rotating a playing outgoing stream into the spare role and releasing the previous spare only then. Nonzero fades mute the older spare, matching the original's gain-slot reset. Prepared streams remain suspended until their authored marker. Generated bridge selection uses authored destination end-marker metadata without eagerly loading/evicting a tail. - Terminal current clips no longer clear playing tails, and MusicPlayer keeps producing while retained streams are playing. Incoming EOF during an active fade preserves the handoff identity until completion rather than inadvertently restarting the layer. Epoch reset, mute, disposal and prepared-request cancellation continue to release bounded readers safely. Diagnostics now distinguish selected from prepared transitions and report retained gains, oldest-tail reuse and EOF. - Red-green verification: six new regression cases failed against the old director with missing tail amplitude/readers, then passed after the fix. Coverage now includes zero-duration and unity-overlap retention after the next selection/preparation, three-slot reuse bounds and exactly-once disposal, older-tail muting, zero and partial fade endpoints, current/successor EOF and worker tail drainage, requests before/after preparation and during fades, missing prepared assets, and block-partition independence across ~57 seconds of repeated unity transitions. - Installed mountain/day regression compares a synthetic handoff at the supplied MD04 marker against sequentially decoded actual MD04 + MD11 samples, confirming retained non-silent tail audio sample-by-sample. The reference decoder uses sequential reads rather than an independent MPEG seek, whose warm-up samples differed in the initial comparison. - Final broader music/audio/targeting/frame/tweak/preferences/session/clock/message regression: 375 passed, 1 conditional OpenAL unit test skipped. Tests categorized RequiresInstall were excluded because unrelated stock-profile/install requirements are unavailable; the supplied installation was mounted for audio/music corpus tests, which ran. An earlier broader attempt had two unrelated missing-install-environment failures; it was not treated as passing. - Latest Linux NativeAOT publish succeeded (existing NU1903 SQLite dependency warning only). Host/null-driver --audio-check passed all four codecs, 128 effect voices/reserved music, queue reset and world teardown; 25-second NativeAOT --music-play mountain/day passed with retained outgoing gains and tails reaching their actual EOFs after handoff. These are automated/scheduling checks, not an audible original-AO comparison. git diff --check passes. Zoning silence follow-up User supplied EP_01/ambient logs advancing 15.408 seconds of rendered music in ~1.36 seconds after teleportation, with no audible output; restarting directly into the same zone restored playback. Investigation confirmed an independent OpenAL source-reset bug, not bad assets. - Minimal native repro against the shipped library: queue/play -> stop/detach leaves AL_STOPPED (0x1014); adding one new buffer immediately reports queued=1, processed=1. ReclaimMusic removes it before a second buffer can accumulate, while _musicStarted=false requires two buffers to start. This drains decoder output rapidly without playing. The fresh-source/initial-state path works, explaining restart recovery. - OpenAlBackend.ResetMusic now rewinds the empty source after stop/detach to restore AL_INITIAL. Existing underrun recovery still starts a previously played source with one buffer; reset starts require two-buffer preroll. Added output start/resume logs with prior native state, queued count and gain. - Corrected a validation blind spot: the old --audio-check only checked buffers were reclaimed after a wait; this could pass even if no playback started. The shared native lifecycle check now asserts actual AL_PLAYING after preroll across repeated initial/playing/exhausted resets and actual restart after underruns; a conditional unit regression invokes the same check. - Final broader focused regression: 375 passed, 2 conditional native tests skipped in the managed container; RequiresInstall exclusions remain as described above. Latest Linux NativeAOT publish and host/null-driver --audio-check pass, executing the shared native restart check against the shipped library. Logs confirm AL_INITIAL/two-buffer starts after reset and AL_STOPPED/one-buffer recovery after exhaustion. git diff --check passes. No changes made to the user's active client process or audio routing. ICC silence verification Read-only inspection of local rdb_1000014 record 655 (district format version 7) identifies ICC as district index 37. The supplied cells 2811, 2941, 2942, 3071 and 3072 all map to that district when reading mappings after the district records; the blob has 12 trailing zero bytes, so taking the last ChunkCount bytes would be an incorrect mapping offset. ICC's nine raw values are [6, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535]. Gamecode environment update 0x100b72bf passes slot zero separately as the district-sound ID and selects slots 1–7 for day/weather music. Sandy SetupENVSounds 0x1000657d stores the district-sound ID and forwards the selected music ID to PlayMusic without a slot-zero music fallback. Thus the supplied ICC environment=-1 decisions match the baseline authored music data; this does not establish absence of ambient effects or scripted overrides. No repository change needed. Next: restart/rebuild the client and verify repeated in-game teleports plus audible mountain/day and combat/death transitions. No claim that every music-flow issue or the wider platform/performance/release gates are resolved. Diagnostics, tail-lifecycle and zoning-reset fixes committed as 99bac794c (fix: Preserve music tails and restore playback after zoning) on feature/client-gameplay-music. Working tree clean; not pushed. User also confirms the real AO client has only ambient sounds in ICC, corroborating the authored no-music finding. CI native audio packaging Run #2557 failed RequireNativeAudioPackage because the client CI workflow never built the OpenAL package. The workflow now installs CMake/G++/Make and ALSA/PulseAudio development libraries and builds the existing pinned linux-x64 recipe before client build/publish. Bookworm reproduction also exposed upstream's empty HRTF add_dependencies call, rejected by CMake 3.25 with embedding disabled; ApplyPatch.cmake now guards that call and the distributed recipe README identifies the compatibility patch. Clean Bookworm build passed with ALSA/PulseAudio detected, required library/license/source package files present, and patch reapplication idempotent. Validation used the existing SHA-256-verified source archive after the initial upstream download returned a partial transfer. Workflow YAML/order and git diff --check pass. Full managed/AOT publish and remote CI rerun were not repeated for this follow-up. CI packaging fixes committed as 95af9ff1f (fix: Build native audio package in client CI), not pushed; broader task gates remain outstanding. Repeated combat victory freeze User reports abrupt combat entry/exit in Subway 127. Supplied logs accept battle/NeutralVictory at 22:02:58.821 but never deliver it to MusicDirector; rendering freezes until the cave request at 22:03:19.037, then OpenAL resumes from AL_STOPPED. Confirmed root cause in MusicPlayer: failedLayer stores the last completed one-shot layer indefinitely, even while a different layer is playing. Returning to that victory layer enters the suspended branch without delivering the request or producing any more battle PCM. A temporary native probe with installed assets and the shipped OpenAL library reproduced the exact pattern on the second victory: accepted control, absent director request, stopped output, then recovery on cave. Fix replaces layer-global suspension with a control revision scoped to the completed/failed request. An unchanged terminal request remains silent; a new request rearms a suspended director, including coalesced layer changes. Existing epoch-based mute/reset cancellation and continuous active-layer transitions remain unchanged. Regression fails before the fix and passes afterward; it exercises victory completion, unchanged-request suppression, an active/backpressured battle layer, and a later victory request draining beyond the ready-pool capacity. Validation: focused music/audio suite 59 passed, 8 conditional checks skipped without fixture/native environment. Broader music/audio/targeting/frame/tweak/preferences/session/clock/message run with installed audio fixture: 339 passed, 3 conditional native/other-asset tests skipped, RequiresInstall-category tests excluded. Linux NativeAOT publish with TreatWarningsAsErrors and system SQLite passed; host/null-driver --audio-check passed four codec families plus shared source/streaming reset checks, zero failures. Repeated native victory -> battle -> victory -> cave probe now delivers the second victory immediately to the director, renders its authored bridge and plays the victory clip; no hold-length battle freeze. git diff --check passes. MusicPlayer.cs and its regression test committed as 64ae1447a (fix: Resume music on repeated combat victory requests), not pushed. No GUI/client process or real audio routing was touched; audible in-game confirmation remains outstanding. Original-player cross-check: SIMPlayer/SandyInterface/mss32 imported together via ghidra_container. SIM transition selection 0x10008d2e and generated-transition adjustment 0x10008bd8, plus Sandy PlayMusic 0x100062e4, corroborate that the default force mode 1 preserves sample fade metadata, including unity fades. Authored zero-duration handoffs retain tails. No arbitrary smoothing/fade substitution was introduced, and this fix is not a claim that all audible differences are resolved. Tribal flute EOF / discarded successor User supplied Subway 127 logs where flute_05 ends shortly before its prepared choir_01 handoff and suspected combat involvement. Gameplay logs keep combatCue=none and requested layer 14:tribal/night; there is no combat-layer publication cancelling playback. The installed flute_05.wav SHA256 is 94d6d3ed981aa7ee664e9b3c68877f72394f988ccbc39c3e62be20784dfc7e38. Independent FFmpeg decoding and AudioStreamReader both produce 396,288 stereo frames at 44,100 Hz (8.986122 seconds), while actual authored edge 627 to choir_01 starts at 9,000 ms / frame 396,900. This is a 612-frame (~13.878 ms) metadata/decoded-length gap, not evidence of a corrupt clip or codec failure. Confirmed MusicDirector bug: source EOF before the selected marker clears the source, prepared successor and plan, marking the layer terminal. It discards the handoff instead of continuing its sample timeline through the short gap. Fixed Track mixing to advance with silence up to a pending authored marker, preserving selection/preparation and the exact destination start; ordinary terminal clips still end, and quiet-period requests override the gap. A broader test initially caught delayed quiet-period termination; that regression was corrected before final validation. Explicit silence/reset cancellation still releases the prepared/current readers. Red-green validation: both zero/nonzero-fade synthetic shortfall cases and the installed flute_05 -> choir_01 reproduction failed before the fix, passed afterward. Added boundaries for EOF before preparation and exactly at the marker, silence-request cancellation during the gap, bounded readers/exactly-once disposal, and sample-by-sample comparison of the actual choir output at the unchanged marker. Final broader focused suite with installed audio fixture: 345 passed, 3 conditional native/other-asset checks skipped, RequiresInstall-category tests excluded. Linux NativeAOT publish with warnings-as-errors/system SQLite passes; 45-second host/null-driver tribal/night smoke passes, plus --audio-check with four decoded codec families and zero failures. The random native smoke did not select flute_05; its exact handoff is verified by the installed-asset regression, not claimed as observed in that random smoke. git diff --check passes. Flute/EOF fix and its regression tests committed separately as 25c05b336 (fix: Preserve music handoffs after early clip EOF), not pushed. The preceding MusicPlayer combat-suspension fix and its separate test are committed as 64ae1447a; working tree clean. No game assets or playback routing changed. Audible in-game confirmation remains outstanding. Original-player evidence: previously imported SIMPlayer/SandyInterface/mss32 refreshed through ghidra_container. SIM EOF callback 0x10009529 makes unbridged/quiet states terminal but does not discard a prepared bridge; prepare/start paths 0x1000a2c8, 0x1000963a, 0x100096b6 retain the authored start. Original tick routines 0x1000a661/0x1000a6e1 use Miles stream-position timing. Exact Miles behavior across this MPEG shortfall was not independently measured; the managed fix preserves authored timing without fabricated samples, resampling or arbitrary fades. Pull request - #2557 — Gameplay music - https://git.project-rk.com/prk/engines/pulls/2557 - Open; feature/client-gameplay-music -> develop. Existing empty description populated with the complete audio/music scope, verified testing and outstanding compatibility/device/platform/release gates. Branch is pushed through 64ae1447a; working tree clean. Task status remains in_progress.
Checklist
2 of 5 complete- Verify AO music loading, district mapping, trigger priorities and authored timing against binaries/assets; settle first-slice coverage and label remaining unknowns.
- Implement and test a validated typed SWS/music catalog with installed-asset resolution and safe malformed/reference/path handling.
- Extend the shared audio runtime with bounded stereo streaming and verified sample-clock layer/transition synchronization; test loops, drift, underruns and effect-budget coexistence.
- Integrate MusicDirector with agreed world/session triggers, existing volumes/mute and lifecycle; verify authored transitions, repeated-state stability and stale-work cancellation.
- Pass focused/regression, installed-asset, audible AO comparison, long-run lifecycle/performance and packaged native/AOT validation; record unsupported coverage and outstanding platform/release gates.
- Area
- Project Rubi-Ka
- Project
- engines
- Kind
- Feature
- Task ID
tsk_jsv48x84cf- Updated