Skip to content

Remove N64 RSP DMA alignment constraint from ADPCM/S8 sample loading - #6364

Merged
serprex merged 1 commit into
HarbourMasters:developfrom
koplas:fix-audio-read-overflow
Mar 21, 2026
Merged

Remove N64 RSP DMA alignment constraint from ADPCM/S8 sample loading#6364
serprex merged 1 commit into
HarbourMasters:developfrom
koplas:fix-audio-read-overflow

Conversation

@koplas

@koplas koplas commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

The sampleDataStartPad and aligned variables existed solely to satisfy
the N64 RSP DMA requirement that source addresses be 16-byte aligned.
On PC, aLoadBuffer is a plain memcpy with no such constraint.

The alignment dance caused aLoadBuffer to read up to 15 bytes before
sampleData and up to 8+ bytes past the end of the sample buffer. On
platforms with strict allocator guard pages (e.g. OpenBSD), this
triggers a SIGSEGV.

A second issue remains after removing the alignment dance: nFramesToDecode
is derived from sample counts (loopEnd), but size is not always a multiple
of frameSize. loopEnd and size are derived independently during encoding
and can disagree on the final partial frame, leaving nFramesToDecode *
frameSize exceeding the remaining bytes in the buffer.

Remove sampleDataStartPad and aligned entirely. Clamp the load to
min(nFramesToDecode * frameSize, audioFontSample->size - sampleDataOffset).
The ADPCM decoder operates on DMEM, so a partial last frame in DMEM
produces at most a negligible artifact at sound termination.

Build Artifacts

@koplas

koplas commented Mar 17, 2026

Copy link
Copy Markdown
Contributor Author

@fabienr Can you test this on OpenBSD?

@koplas
koplas force-pushed the fix-audio-read-overflow branch from ecd8edc to aed0321 Compare March 17, 2026 16:28
Comment thread soh/src/code/audio_synthesis.c Outdated
@fabienr

fabienr commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

@fabienr Can you test this on OpenBSD?

It's better as it didn't crash early but later when playing.

I didn't try to understand why exactly, there are a few contexts on the crash :

Core was generated by `shipwrightsoh'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x00000d7773fd4870 in memcpy (dst0=0xd751b8c63e2 <rspa+1650>, src0=, length=38) at /usr/src/lib/libc/string/memcpy.c:97
97 TLOOP1(dst++ = src++);
[Current thread is 1 (process 544176)]
(gdb) bt
#0 0x00000d7773fd4870 in memcpy (dst0=0xd751b8c63e2 <rspa+1650>, src0=, length=38) at /usr/src/lib/libc/string/memcpy.c:97
#1 0x00000d751a776d16 in aLoadBufferImpl (source_addr=0xd774f733fe0, dest_addr=2304, nbytes=38)
at /mnt/ext/_ports/pobj/shipwright-9.1.2pl20260314/Shipwright-7899b6a0484f098ccb4fe2f45088c7c2fd01c68a/soh/soh/mixer.c:103
#2 0x00000d751a8c3c27 in AudioSynth_ProcessNote (noteIndex=21, noteSubEu=0xd782bd05ee8, synthState=0xd782bd02710, aiBuf=0xd77ce474b30, aiBufLen=192, cmd=0x420, updateIndex=2)
at /mnt/ext/_ports/pobj/shipwright-9.1.2pl20260314/Shipwright-7899b6a0484f098ccb4fe2f45088c7c2fd01c68a/soh/src/code/audio_synthesis.c:903
#3 0x00000d751a8c16ef in AudioSynth_DoOneAudioUpdate (aiBuf=0xd77ce474b30, aiBufLen=192, cmd=0x420, updateIndex=2)
at /mnt/ext/_ports/pobj/shipwright-9.1.2pl20260314/Shipwright-7899b6a0484f098ccb4fe2f45088c7c2fd01c68a/soh/src/code/audio_synthesis.c:625
#4 0x00000d751a8c10e7 in AudioSynth_Update (cmdStart=0x0, cmdCnt=0xd77ce473370, aiStart=0xd77ce474570, aiBufLen=192)
at /mnt/ext/_ports/pobj/shipwright-9.1.2pl20260314/Shipwright-7899b6a0484f098ccb4fe2f45088c7c2fd01c68a/soh/src/code/audio_synthesis.c:167
#5 0x00000d751a8c6e01 in AudioMgr_CreateNextAudioBuffer (samples=0xd77ce474570, num_samples=560)
at /mnt/ext/_ports/pobj/shipwright-9.1.2pl20260314/Shipwright-7899b6a0484f098ccb4fe2f45088c7c2fd01c68a/soh/src/code/code_800E4FE0.c:70
#6 0x00000d751a36e965 in OTRAudio_Thread () at /mnt/ext/_ports/pobj/shipwright-9.1.2pl20260314/Shipwright-7899b6a0484f098ccb4fe2f45088c7c2fd01c68a/soh/soh/OTRGlobals.cpp:1039
#7 0x00000d751a3d7850 in std::__1::__invoke[abi:ne190107]<void (
)()>(void (
&&)()) (__f=@0xd77646409b8: 0xd751a36e7e0 <OTRAudio_Thread()>)
at /usr/include/c++/v1/__type_traits/invoke.h:149
#8 0x00000d751a3d780e in __thread_execute<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_deletestd::__1::__thread_struct >, void ()()> (__t=...)
at /usr/include/c++/v1/__thread/thread.h:192
#9 0x00000d751a3d74ed in std::__1::__thread_proxy[abi:ne190107]<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_deletestd::__1::__thread_struct >, void (
)()> >(void*) (__vp=0xd77646409b0) at /usr/include/c++/v1/__thread/thread.h:201
#10 0x00000d778fdfbfd2 in _rthread_start (v=0x26) at /usr/src/lib/librthread/rthread.c:99
#11 0x00000d777400f66a in __tfork_thread () at /usr/src/lib/libc/arch/amd64/sys/tfork_thread.S:87

(gdb) print *synthState
$2 = {restart = 0 '\000', sampleDmaIndex = 0 '\000', prevHeadsetPanRight = 0 '\000', prevHeadsetPanLeft = 0 '\000', reverbVol = 21 '\025', numParts = 1 '\001', samplePosFrac = 41856,
samplePosInt = 5139, synthesisBuffers = 0xd782bd050b0, curVolLeft = 19389, curVolRight = 18147, unk_14 = 0, unk_16 = 0, unk_18 = 0, unk_1A = 0 '\000', unk_1C = 0, unk_1E = 0,
opusFile = 0x0}

(gdb) print *noteSubEu
$3 = {bitField0 = {enabled = 1 '\001', needsInit = 0 '\000', finished = 0 '\000', unused = 0 '\000', stereoStrongRight = 0 '\000', stereoStrongLeft = 0 '\000',
stereoHeadsetEffects = 0 '\000', usesHeadsetPanEffects = 0 '\000'}, bitField1 = {reverbIndex = 1 '\001', bookOffset = 0 '\000', isSyntheticWave = 0 '\000', hasTwoParts = 0 '\000',
usesHeadsetPanEffects2 = 0 '\000'}, unk_2 = 0 '\000', headsetPanRight = 0 '\000', headsetPanLeft = 0 '\000', reverbVol = 21 '\025', unk_06 = 0 '\000', unk_07 = 16 '\020',
targetVolLeft = 1212, targetVolRight = 1133, resamplingRateFixedPoint = 20514, unk_0E = 255, sound = {soundFontSound = 0xd77be38b970, samples = 0xd77be38b970}, filter = 0xd782bc28d00,
pad_18 = "\000\000\000\000\000\000\000"}

(gdb) print *note
$4 = {listItem = {prev = 0xd782bd17048, next = 0xd782bd17048, u = {value = 0xd782bd026f0, count = 735061744}, pool = 0xd782bd16fe8}, synthesisState = {restart = 0 '\000',
sampleDmaIndex = 0 '\000', prevHeadsetPanRight = 0 '\000', prevHeadsetPanLeft = 0 '\000', reverbVol = 21 '\025', numParts = 1 '\001', samplePosFrac = 41856, samplePosInt = 5139,
synthesisBuffers = 0xd782bd050b0, curVolLeft = 19389, curVolRight = 18147, unk_14 = 0, unk_16 = 0, unk_18 = 0, unk_1A = 0 '\000', unk_1C = 0, unk_1E = 0, opusFile = 0x0},
playbackState = {priority = 14 '\016', waveId = 0 '\000', sampleCountIndex = 0 '\000', fontId = 0 '\000', unk_04 = 0 '\000', stereoHeadsetEffects = 1 '\001', adsrVolScaleUnused = 0,
portamentoFreqScale = 1, vibratoFreqScale = 1, prevParentLayer = 0xffffffffffffffff, parentLayer = 0xd751b9fa818 <gAudioContext+33912>, wantedParentLayer = 0xffffffffffffffff,
attributes = {reverb = 21 '\025', unk_1 = 0 '\000', pan = 63 '?', stereo = {s = {unused = 0 '\000', bit2 = 0 '\000', strongRight = 0 '\000', strongLeft = 0 '\000',
stereoHeadsetEffects = 0 '\000', usesHeadsetPanEffects = 0 '\000'}, asByte = 0 '\000'}, unk_4 = 16 '\020', unk_6 = 255, freqScale = 0.626293659, velocity = 0.750201523,
filter = 0xd782bd02780, filterBuf = {0, 0, 0, 32767, 0, 0, 0, 0}}, adsr = {action = {s = {unk_0b80 = 0 '\000', hang = 0 '\000', decay = 0 '\000', release = 0 '\000',
state = 5 '\005'}, asByte = 80 'P'}, envIndex = 1 '\001', delay = -1, sustain = 0, velocity = 0.995914698, fadeOutVel = 0.444444448, current = 0.995914698, target = 0.995914698,
unk_18 = "\000\000\000", envelope = 0xd782bc28d20}}, portamento = {mode = 0 '\000', cur = 0, speed = 0, extent = 0}, vibratoState = {channel = 0xd782bd16f20, time = 0,
curve = 0xd751b54d730 , extent = 0, rate = 2048, active = 1 '\001', rateChangeTimer = 0, extentChangeTimer = 0, delay = 0}, unk_B8 = "\000\000\000", unk_BC = 0,
noteSubEu = {bitField0 = {enabled = 1 '\001', needsInit = 0 '\000', finished = 0 '\000', unused = 0 '\000', stereoStrongRight = 0 '\000', stereoStrongLeft = 0 '\000',
stereoHeadsetEffects = 0 '\000', usesHeadsetPanEffects = 0 '\000'}, bitField1 = {reverbIndex = 1 '\001', bookOffset = 0 '\000', isSyntheticWave = 0 '\000', hasTwoParts = 0 '\000',
usesHeadsetPanEffects2 = 0 '\000'}, unk_2 = 0 '\000', headsetPanRight = 0 '\000', headsetPanLeft = 0 '\000', reverbVol = 0 '\000', unk_06 = 0 '\000', unk_07 = 0 '\000',
targetVolLeft = 0, targetVolRight = 0, resamplingRateFixedPoint = 0, unk_0E = 0, sound = {soundFontSound = 0xd77be38b970, samples = 0xd77be38b970}, filter = 0x0,
pad_18 = "\000\000\000\000\000\000\000"}}

(gdb) print *audioFontSample
$5 = {{{codec = 0, medium = 0, unk_bit26 = 0, isRelocated = 0}, asU32 = 3755990784}, size = 2926, fileSize = 3755991007,
sampleAddr = 0xd774f733490 "\201\036\275"\373\306\021K0\201\241\0032/\037\003<\022q\306+ \375\342\3752/\201\016\r\355\362\377\276A\374\221\016\366A\t\026\321\036\002\2211;\020\263\v@\023\001\201\317\373\017\361\317\016\335\377\221", <incomplete sequence \342>, loop = 0xd780e064e20, book = 0xd780e064e50}

(gdb) print nSamplesProcessed
$6 = 0
(gdb) print samplesLenAdjusted
$7 = 120
(gdb) print nFirstFrameSamplesToIgnore
$8 = 3
(gdb) print nSamplesUntilLoopEnd
$9 = 62
(gdb) print nSamplesToProcess
$10 = 120
(gdb) print nFramesToDecode
$11 = 4
(gdb) print nSamplesToDecode
$12 = 49
(gdb) print nSamplesInFirstFrame
$13 = 13
(gdb) print nTrailingSamplesToIgnore
$14 = 0
(gdb) print frameSize
$15 = 9
(gdb) print skipInitialSamples
$16 = 16
(gdb) print frameIndex
$17 = 322

There is a lot of round stuff in the mixer so I'm curious why there are all those constraints on memory alignment in the RSPA at start, do you know ?

If that's something we can remove then it will be easier to think about the calculation but (after spending some time there) I think the math is correct and the ADPCM decode loop isn't the reason why the sound is bad on openbsd. Just it needs aligned memory per design otherwise it crash either with read underflow (landingpad, not sure in which mem layout this trigger but a larger buffer isn't enough isn't enough to fix it) or read overflow (round to 16). I fear removing alignment implies rewriting a lot of stuff.

The landing pad is also about alignment to copy data in chunks of 16 bytes, aligned to 16 bytes in memory.
If you remove the alignment constraint, I think the landing pad should go also.

If the buffer is strictly aligned (from allocation), then it doesn't crash anymore.
https://github.com/fabienr/openbsd-harbourmasters/blob/main/games/shipwright/patches/patch-soh_soh_resource_importer_AudioSampleFactory_cpp
https://github.com/fabienr/openbsd-harbourmasters/blob/main/games/shipwright/patches/patch-soh_soh_resource_type_AudioSample_cpp

On top of this memory issue, I also have a very poor quality sound.
shipwright-openbsd.wav

With this audio record, where do you think I should dig ?
This isn't related to SDL as I replaced the audio backend from SDL to sndio (openbsd native), same issue.
Considering it works fine on Linux/Windows ... what can diverge, toolchain, cpp, threading ...

I will have limited spare time until mid-april, still any advice is welcome.

@koplas
koplas force-pushed the fix-audio-read-overflow branch 2 times, most recently from 753d51e to fd274d3 Compare March 17, 2026 21:49
@Malkierian

Copy link
Copy Markdown
Contributor

Your initial comment mentions using the actual padding as part of the offset calculation, but from what I can see, sampleDataStartPad is simply removed from the code.

@koplas koplas changed the title Fix ADPCM sample buffer overread in audio synthesis Remove N64 RSP DMA alignment constraint from ADPCM/S8 sample loading Mar 18, 2026
@koplas
koplas marked this pull request as draft March 18, 2026 08:16
@koplas

koplas commented Mar 18, 2026

Copy link
Copy Markdown
Contributor Author

Your initial comment mentions using the actual padding as part of the offset calculation, but from what I can see, sampleDataStartPad is simply removed from the code.

I force pushed a new commit and forgot to update the pull request description. I am now testing a different approach.

@fabienr Can you check if it now works on OpenBSD. If not I need to setup a OpenBSD machine for proper testing. I converted this to a draft until it is confirmed, that it works on OpenBSD.

@fabienr

fabienr commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Your initial comment mentions using the actual padding as part of the offset calculation, but from what I can see, sampleDataStartPad is simply removed from the code.

I force pushed a new commit and forgot to update the pull request description. I am now testing a different approach.

@fabienr Can you check if it now works on OpenBSD. If not I need to setup a OpenBSD machine for proper testing. I converted this to a draft until it is confirmed, that it works on OpenBSD.

Sadly it still crash. If you plan to test on openbsd I'm using this malloc options (more strict) :
doas sysctl vm.malloc_conf="CFGJJSU<<<<"

I have done two tests, on the second one I regen oot.o2r (rm & restart) and remove the HD pack mod.
Same crash, both looks random but they actually have the same overflow of 8.

Core was generated by `shipwrightsoh'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x00000383b02af930 in memcpy (dst0=0x3817c9a0352 <rspa+1650>, src0=, length=48) at /usr/src/lib/libc/string/memcpy.c:103
103 TLOOP((word )dst = (word )src; src += wsize; dst += wsize);
[Current thread is 1 (process 323831)]
(gdb) bt
#0 0x00000383b02af930 in memcpy (dst0=0x3817c9a0352 <rspa+1650>, src0=, length=48) at /usr/src/lib/libc/string/memcpy.c:103
#1 0x000003817b850d16 in aLoadBufferImpl (source_addr=0x383ecd96fd2, dest_addr=2304, nbytes=54)
at /mnt/ext/_ports/pobj/shipwright-9.1.2pl20260314/Shipwright-7899b6a0484f098ccb4fe2f45088c7c2fd01c68a/soh/soh/mixer.c:103
#2 0x000003817b99dbef in AudioSynth_ProcessNote (noteIndex=21, noteSubEu=0x3846ce9ab50, synthState=0x3846ce97710, aiBuf=0x383c4607580, aiBufLen=184, cmd=0x1a8, updateIndex=1)
at /mnt/ext/_ports/pobj/shipwright-9.1.2pl20260314/Shipwright-7899b6a0484f098ccb4fe2f45088c7c2fd01c68a/soh/src/code/audio_synthesis.c:898
#3 0x000003817b99b6ef in AudioSynth_DoOneAudioUpdate (aiBuf=0x383c4607580, aiBufLen=184, cmd=0x1a8, updateIndex=1)
at /mnt/ext/_ports/pobj/shipwright-9.1.2pl20260314/Shipwright-7899b6a0484f098ccb4fe2f45088c7c2fd01c68a/soh/src/code/audio_synthesis.c:625
#4 0x000003817b99b0e7 in AudioSynth_Update (cmdStart=0x0, cmdCnt=0x383c4607220, aiStart=0x383c46072a0, aiBufLen=376)
at /mnt/ext/_ports/pobj/shipwright-9.1.2pl20260314/Shipwright-7899b6a0484f098ccb4fe2f45088c7c2fd01c68a/soh/src/code/audio_synthesis.c:167
#5 0x000003817b9a0d71 in AudioMgr_CreateNextAudioBuffer (samples=0x383c46072a0, num_samples=560)
at /mnt/ext/_ports/pobj/shipwright-9.1.2pl20260314/Shipwright-7899b6a0484f098ccb4fe2f45088c7c2fd01c68a/soh/src/code/code_800E4FE0.c:70
#6 0x000003817b448965 in OTRAudio_Thread () at /mnt/ext/_ports/pobj/shipwright-9.1.2pl20260314/Shipwright-7899b6a0484f098ccb4fe2f45088c7c2fd01c68a/soh/soh/OTRGlobals.cpp:1039
#7 0x000003817b4b1850 in std::__1::__invoke[abi:ne190107]<void (
)()>(void (
&&)()) (__f=@0x383fb3e8f38: 0x3817b4487e0 <OTRAudio_Thread()>)
at /usr/include/c++/v1/__type_traits/invoke.h:149
#8 0x000003817b4b180e in __thread_execute<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_deletestd::__1::__thread_struct >, void (
)()> (__t=...)
at /usr/include/c++/v1/__thread/thread.h:192
#9 0x000003817b4b14ed in std::__1::__thread_proxy[abi:ne190107]<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_deletestd::__1::__thread_struct >, void (
)()> >(void*) (__vp=0x383fb3e8f30) at /usr/include/c++/v1/__thread/thread.h:201
#10 0x0000038391a43fd2 in _rthread_start (v=0x3817c9a0380 <rspa+1696>) at /usr/src/lib/librthread/rthread.c:99
#11 0x00000383b02ea66a in __tfork_thread () at /usr/src/lib/libc/arch/amd64/sys/tfork_thread.S:87

For example the first crash :

(gdb) print *audioFontSample
$1 = {{{codec = 0, medium = 0, unk_bit26 = 0, isRelocated = 0}, asU32 = 3755990784}, size = 3664, fileSize = 3755991007,
sampleAddr = 0x383ecd961b0 "s\300@B\303", <incomplete sequence \314>, loop = 0x3847ab4cc10, book = 0x3847ab4cc40}
(gdb) print *audioFontSample->loop
$2 = {start = 0, loopEnd = 6513, count = 0, sampleEnd = 3755991007, predictorState = {0 <repeats 16 times>}}
(gdb) print sampleData-audioFontSample->sampleAddr
$3 = 3618

It tries to copy nbytes=54 but the buffer left is 46 (3664 (audioFontSample->size) - 3618 (sampleData - audioFontSample->sampleAddr)). It overflow by 8.

In the second crash I got size=3358, offset=3339, nbytes=27, left 19, overflow 8.

In the first example, 3664 isn't aligned to codec framesize (9) which mean it have 407.11.. frames which looks strange. I regen and the second crash is on 3358 (/9=373.11..).

It looks like the buffser still sized correctly, loopEnd = 6513 / 16 (frames) * 9 (bytes to decode) = 3663.5625.

Do you think both size and loopend pos are truncated, are they based from the same initial number ?
Or maybe thats how it works, I didn't check the decode, so does it looks possible to decode 1 byte only and get the intended sound sample ?

If math are correct in audio_synthesis the decoding loop isn't designed to stop on a single byte and will always read past the buffer either the one from mem or from dmem buffer. From dmem it will not crash, just the copy from mem to dmem have to copy bytes left and not frames(*9) left.

On linux, maybe you could add an overflow check, dump a core, and check if you have the same truncation I have on size/endpos ?

@koplas
koplas force-pushed the fix-audio-read-overflow branch 2 times, most recently from f98993b to e66d11c Compare March 19, 2026 15:25
The sampleDataStartPad and aligned variables existed solely to satisfy
the N64 RSP DMA requirement that source addresses be 16-byte aligned.
On PC, aLoadBuffer is a plain memcpy with no such constraint.

The alignment dance caused aLoadBuffer to read up to 15 bytes before
sampleData and up to 8+ bytes past the end of the sample buffer. On
platforms with strict allocator guard pages (e.g. OpenBSD), this
triggers a SIGSEGV.

A second issue remains after removing the alignment dance: nFramesToDecode
is derived from sample counts (loopEnd), but size is not always a multiple
of frameSize. loopEnd and size are derived independently during encoding
and can disagree on the final partial frame, leaving nFramesToDecode *
frameSize exceeding the remaining bytes in the buffer.

Remove sampleDataStartPad and aligned entirely. Clamp the load to
min(nFramesToDecode * frameSize, audioFontSample->size - sampleDataOffset).
The ADPCM decoder operates on DMEM, so a partial last frame in DMEM
produces at most a negligible artifact at sound termination.
@koplas
koplas force-pushed the fix-audio-read-overflow branch from e66d11c to 7e7e683 Compare March 19, 2026 15:27
@koplas

koplas commented Mar 19, 2026

Copy link
Copy Markdown
Contributor Author

I added logging to easier identify the issue. Here is the output when running the opening scene on macOS:

./build-cmake-release/soh/soh-macos
[2026-03-19 17:29:02.785] [info] [ArchiveManager.cpp:243] Reading archive: /Users/paulschwabauer/Documents/Shipwright/./soh.o2r
[2026-03-19 17:29:02.786] [info] [ArchiveManager.cpp:275] Adding Archive /Users/paulschwabauer/Documents/Shipwright/./soh.o2r to Archive Manager
[2026-03-19 17:29:02.921] [info] [ArchiveManager.cpp:243] Reading archive: ./oot.o2r
[2026-03-19 17:29:02.935] [info] [ArchiveManager.cpp:275] Adding Archive ./oot.o2r to Archive Manager
[17:29:03.026] [SDLAudioPlayer.cpp:46] [info] SDL Audio initialized: 2 channels, 32000 Hz
[17:29:03.026] [OTRGlobals.cpp:813] [info] Starting Ship of Harkinian version Copper Charlie (9.1.2) (Branch: fix-audio-read-overflow | Commit: aed0321)
[17:29:03.030] [Console.cpp:66] [warning] Attempting to add command set that already exists
[17:29:03.030] [Console.cpp:66] [warning] Attempting to add command get that already exists
[17:29:03.030] [Console.cpp:66] [warning] Attempting to add command help that already exists
[17:29:03.030] [Console.cpp:66] [warning] Attempting to add command clear that already exists
[17:29:03.030] [Console.cpp:66] [warning] Attempting to add command unbind that already exists
[17:29:03.030] [Console.cpp:66] [warning] Attempting to add command bind that already exists
[17:29:03.030] [Console.cpp:66] [warning] Attempting to add command bind-toggle that already exists
[17:29:03.120] [os.cpp:23] [error] Failed add SDL game controller mappings from "./gamecontrollerdb.txt" (Invalid RWops)
[17:29:10.909] [z_demo.c:2215] [info] Cutscene_HandleConditionalTriggers - entranceIndex: 0xcd cutsceneIndex: 0xfff3
[17:29:10.916] [z_scene_otr.cpp:519] [info] Room Init - curRoom.num: 0x0
[17:29:10.916] [z_play_otr.cpp:63] [info] Scene Init - sceneNum: 0x51, entranceIndex: 0xcd
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3366 nFramesToDecode=6 frameSize=9 wanted=54 avail=46 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3366 nFramesToDecode=6 frameSize=9 wanted=54 avail=46 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3366 nFramesToDecode=6 frameSize=9 wanted=54 avail=46 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3366 nFramesToDecode=6 frameSize=9 wanted=54 avail=46 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3366 nFramesToDecode=6 frameSize=9 wanted=54 avail=46 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3384 nFramesToDecode=4 frameSize=9 wanted=36 avail=28 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3366 nFramesToDecode=6 frameSize=9 wanted=54 avail=46 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3366 nFramesToDecode=6 frameSize=9 wanted=54 avail=46 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3384 nFramesToDecode=4 frameSize=9 wanted=36 avail=28 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3384 nFramesToDecode=4 frameSize=9 wanted=36 avail=28 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3366 nFramesToDecode=6 frameSize=9 wanted=54 avail=46 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3384 nFramesToDecode=4 frameSize=9 wanted=36 avail=28 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3384 nFramesToDecode=4 frameSize=9 wanted=36 avail=28 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3384 nFramesToDecode=4 frameSize=9 wanted=36 avail=28 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3384 nFramesToDecode=4 frameSize=9 wanted=36 avail=28 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3366 nFramesToDecode=6 frameSize=9 wanted=54 avail=46 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3384 nFramesToDecode=4 frameSize=9 wanted=36 avail=28 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3366 nFramesToDecode=6 frameSize=9 wanted=54 avail=46 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3384 nFramesToDecode=4 frameSize=9 wanted=36 avail=28 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3384 nFramesToDecode=4 frameSize=9 wanted=36 avail=28 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f19bc000 codec=0 size=16354 offset=16308 nFramesToDecode=6 frameSize=9 wanted=54 avail=46 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3366 nFramesToDecode=6 frameSize=9 wanted=54 avail=46 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3384 nFramesToDecode=4 frameSize=9 wanted=36 avail=28 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3357 nFramesToDecode=7 frameSize=9 wanted=63 avail=55 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3384 nFramesToDecode=4 frameSize=9 wanted=36 avail=28 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3384 nFramesToDecode=4 frameSize=9 wanted=36 avail=28 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3357 nFramesToDecode=7 frameSize=9 wanted=63 avail=55 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3384 nFramesToDecode=4 frameSize=9 wanted=36 avail=28 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3375 nFramesToDecode=5 frameSize=9 wanted=45 avail=37 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3357 nFramesToDecode=7 frameSize=9 wanted=63 avail=55 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3366 nFramesToDecode=6 frameSize=9 wanted=54 avail=46 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3384 nFramesToDecode=4 frameSize=9 wanted=36 avail=28 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3384 nFramesToDecode=4 frameSize=9 wanted=36 avail=28 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3357 nFramesToDecode=7 frameSize=9 wanted=63 avail=55 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3393 nFramesToDecode=3 frameSize=9 wanted=27 avail=19 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3384 nFramesToDecode=4 frameSize=9 wanted=36 avail=28 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3411 nFramesToDecode=1 frameSize=9 wanted=9 avail=1 overflow=8
[17:30:32.048] [z_demo.c:2215] [info] Cutscene_HandleConditionalTriggers - entranceIndex: 0x8d cutsceneIndex: 0xfff2
[17:30:32.052] [z_scene_otr.cpp:519] [info] Room Init - curRoom.num: 0x1
[17:30:32.052] [z_play_otr.cpp:63] [info] Scene Init - sceneNum: 0x17, entranceIndex: 0x8d
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3425c00 codec=0 size=3412 offset=3402 nFramesToDecode=2 frameSize=9 wanted=18 avail=10 overflow=8
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3e3b000 codec=3 size=2716 offset=2715 nFramesToDecode=1 frameSize=5 wanted=5 avail=1 overflow=4
AudioSynth: sample buffer overread prevented: sampleAddr=0x8f3e3b000 codec=3 size=2716 offset=2710 nFramesToDecode=2 frameSize=5 wanted=10 avail=6 overflow=4

@fabienr

fabienr commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

I added logging to easier identify the issue. Here is the output when running the opening scene on macOS:

Same here on OpenBSD, no crash anymore.

But on macOS the sound is smooth and pleasant ?

Is it a truncation from another bug and maybe this is the reason the sound isn't correct on OpenBSD or is it normal to decode one byte of ADPCM instead of a full frame ?

@koplas

koplas commented Mar 20, 2026

Copy link
Copy Markdown
Contributor Author

On macOS the audio is normal for me. Can you apply the following patch and paste the log output here:

diff --git a/include/ship/audio/SDLAudioPlayer.h b/include/ship/audio/SDLAudioPlayer.h
index 8d9a08e..e654418 100644
--- a/include/ship/audio/SDLAudioPlayer.h
+++ b/include/ship/audio/SDLAudioPlayer.h
@@ -1,6 +1,7 @@
 #pragma once
 #include "AudioPlayer.h"
 #include <SDL2/SDL.h>
+#include <chrono>

 namespace Ship {
 class SDLAudioPlayer final : public AudioPlayer {
@@ -19,5 +20,9 @@ class SDLAudioPlayer final : public AudioPlayer {
   private:
     SDL_AudioDeviceID mDevice = 0;
     int32_t mNumChannels = 2;
+    // Diagnostics: track push timing and sample accounting independent of SDL queue size
+    std::chrono::steady_clock::time_point mLastPushTime = {};
+    int mSamplesQueued = 0;
+    uint64_t mFrameCount = 0;
 };
 } // namespace Ship
diff --git a/src/ship/audio/SDLAudioPlayer.cpp b/src/ship/audio/SDLAudioPlayer.cpp
index d9f1e44..f1158e4 100644
--- a/src/ship/audio/SDLAudioPlayer.cpp
+++ b/src/ship/audio/SDLAudioPlayer.cpp
@@ -43,7 +43,13 @@ bool SDLAudioPlayer::DoInit() {
         return false;
     }

-    SPDLOG_INFO("SDL Audio initialized: {} channels, {} Hz", mNumChannels, this->GetSampleRate());
+    SPDLOG_INFO("SDL Audio initialized: driver={} channels={} freq={} samples={} (wanted {})",
+                SDL_GetCurrentAudioDriver(), (int)have.channels, have.freq, (int)have.samples,
+                (int)want.samples);
+
+    mLastPushTime = std::chrono::steady_clock::now();
+    mSamplesQueued = 0;
+    mFrameCount = 0;

     SDL_PauseAudioDevice(mDevice, 0);
     return true;
@@ -54,9 +60,39 @@ int SDLAudioPlayer::Buffered() {
 }

 void SDLAudioPlayer::DoPlay(const uint8_t* buf, size_t len) {
-    if (Buffered() < 6000) {
-        // Don't fill the audio buffer too much in case this happens
-        SDL_QueueAudio(mDevice, buf, len);
+    auto now = std::chrono::steady_clock::now();
+    int sdlBuffered = Buffered();
+    int newSamples = (int)(len / (sizeof(int16_t) * mNumChannels));
+
+    // Track elapsed time since last push to estimate true playback position
+    auto elapsedUs =
+        std::chrono::duration_cast<std::chrono::microseconds>(now - mLastPushTime).count();
+    int drainedSinceLastPush = (int)((elapsedUs / 1e6) * GetSampleRate());
+    mSamplesQueued -= drainedSinceLastPush;
+    if (mSamplesQueued < 0) {
+        mSamplesQueued = 0;
     }
+    mLastPushTime = now;
+
+    // Log every 60 frames to identify queue accounting discrepancy without spamming
+    if (mFrameCount % 60 == 0) {
+        SPDLOG_DEBUG(
+            "[SDL audio] frame={} sdlBuffered={} trackedQueued={} elapsedUs={} drainedEst={} desiredBuffered={}",
+            mFrameCount, sdlBuffered, mSamplesQueued, elapsedUs, drainedSinceLastPush,
+            GetDesiredBuffered());
+    }
+
+    if (sdlBuffered < 6000) {
+        if (SDL_QueueAudio(mDevice, buf, len) != 0) {
+            SPDLOG_WARN("[SDL audio] SDL_QueueAudio failed: {}", SDL_GetError());
+        } else {
+            mSamplesQueued += newSamples;
+        }
+    } else {
+        SPDLOG_DEBUG("[SDL audio] frame={} DROPPED: sdlBuffered={} >= 6000", mFrameCount,
+                     sdlBuffered);
+    }
+
+    mFrameCount++;
 }
 } // namespace Ship

Does the game run smoothly? Can you set the frame limit to 60 fps and check if it always hits the target. If not this seems to be more a performance issue, as the frame-lock between audio and rendering can cause stutter issues.

You can use this patch to verify that the audio thread is too slow:

diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp
index d1b99cb3f..ff62250a3 100644
--- a/soh/soh/OTRGlobals.cpp
+++ b/soh/soh/OTRGlobals.cpp
@@ -1035,6 +1035,9 @@ void OTRAudio_Thread() {

        // 3 is the maximum authentic frame divisor.
        s16 audio_buffer[SAMPLES_HIGH * NUM_AUDIO_CHANNELS * 3];
+
+        auto t0 = std::chrono::steady_clock::now();
+
        for (int i = 0; i < AUDIO_FRAMES_PER_UPDATE; i++) {
            AudioMgr_CreateNextAudioBuffer(audio_buffer + i * (num_audio_samples * NUM_AUDIO_CHANNELS),
                                           num_audio_samples);
@@ -1043,6 +1046,12 @@ void OTRAudio_Thread() {
        AudioPlayer_Play((u8*)audio_buffer,
                         num_audio_samples * (sizeof(int16_t) * NUM_AUDIO_CHANNELS * AUDIO_FRAMES_PER_UPDATE));

+        auto t1 = std::chrono::steady_clock::now();
+        auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count();
+        if (ms > 20) {
+            fprintf(stderr, "[audio] OTRAudio_Thread: slow iteration %lldms\n", (long long)ms);
+        }
+
        audio.processing = false;
        audio.cv_from_thread.notify_one();

@fabienr

fabienr commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

On macOS the audio is normal for me. Can you apply the following patch and paste the log output here:
Does the game run smoothly? Can you set the frame limit to 60 fps and check if it always hits the target. If not this seems to be more a performance issue, as the frame-lock between audio and rendering can cause stutter issues.

I notice a deviation from the FPS rate intended. Setting to 20, the minimum, was running at 16.7 then 25 give 20 ... so it continuously deviates while fps rate can increase up to 40 in my actual setup. And for sure continuous underrun on the SDL audio buffer was visible with your debugging but no audio slow iteration. Now that I know the audio rate is linked to framerate ...

Thanks so much for the debugging tips :)

I don't know a lot about game engine loop in general, but I notice something in gfx_sdl2.cpp ...
I got lucky and the following diff did the trick, audio runs smoothly now.
This also explains why all the games based on libultraship have the same issue on OpenBSD.

Index: libultraship/src/fast/backends/gfx_sdl2.cpp
--- libultraship/src/fast/backends/gfx_sdl2.cpp.orig
+++ libultraship/src/fast/backends/gfx_sdl2.cpp
@@ -652,6 +652,10 @@ void GfxWindowBackendSDL2::SyncFramerateWithTime() con
 #elif defined(__APPLE__)
     // Use macOS scheduler interval on macOS
     left -= 10000UL;
+#elif defined(__OpenBSD__)
+    // OpenBSD commonly runs with hz=100 (10 ms scheduler tick)
+    // Wake up a full tick earlier to prevent desync.
+    left -= 100000UL;
 #endif
     if (left > 0) {
 #ifndef _WIN32

Same as APPLE, 1ms wasn't enough. I chose 10ms based on :

$ sysctl kern.clockrate
kern.clockrate=tick = 10000, hz = 100, profhz = 1000, stathz = 100

I'm not sure this is the proper fix but seeing APPLE doing something similar makes me think that's ok to do the same on OpenBSD (?).

Then we are left with the overread issue. I'm not sure what's the best solution, either aligned memory or remove alignment, but then we need to check bytes left during copy. Still, this looks weird to decode a single byte. Btw we don't care about the garbage because the synthesis will start from a fresh state (loop) and will not consume the other bytes decoded by adpcm in the synthstate ... I think so.

@serprex

serprex commented Mar 21, 2026

Copy link
Copy Markdown
Member

https://man.openbsd.org/OpenBSD-6.8/sysctl.2 we might be able to lookup tick on BSDs

@fabienr can you try Kenix3/libultraship#1023 ? wrote just going off docs

@koplas

koplas commented Mar 21, 2026

Copy link
Copy Markdown
Contributor Author

Then we are left with the overread issue. I'm not sure what's the best solution, either aligned memory or remove alignment, but then we need to check bytes left during copy. Still, this looks weird to decode a single byte. Btw we don't care about the garbage because the synthesis will start from a fresh state (loop) and will not consume the other bytes decoded by adpcm in the synthstate ... I think so.

I want to contain this pull request to fix the undefined behavior and crash in the audio decoder. I guess on original hardware it would read parts of the next sample or simply null bytes. But I doubt the difference can be heard.

Good to hear that you found a fix for the issue. I would not mind if you also port this overread fix to the other projects. If I am on my computer again I will delete the last commit and mark this pull request as ready.

@serprex
serprex requested a review from Malkierian March 21, 2026 14:39
@koplas
koplas force-pushed the fix-audio-read-overflow branch from 7e7e683 to d69b0f7 Compare March 21, 2026 16:04
@koplas
koplas marked this pull request as ready for review March 21, 2026 16:04
@Malkierian

Copy link
Copy Markdown
Contributor

I assume, then, this fixes what is intended now and is ready for merge scrutiny?

@koplas

koplas commented Mar 21, 2026

Copy link
Copy Markdown
Contributor Author

I assume, then, this fixes what is intended now and is ready for merge scrutiny?

Yes, it fixes an issue I tried fixing earlier and I don't want to extend this pull request to contain other fixes.

@serprex
serprex merged commit b6bf97e into HarbourMasters:develop Mar 21, 2026
6 checks passed
@koplas
koplas deleted the fix-audio-read-overflow branch March 22, 2026 16:40
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.

4 participants