[Scummvm-git-logs] scummvm master -> 2650d9aa0f2f3deff794dba3f3f605e21a31097f

dreammaster noreply at scummvm.org
Sun May 24 23:42:39 UTC 2026


This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
a434cc270d MADS: DRAGONSPHERE: In progress implementing sound drivers
60143443a6 MADS: DRAGONSPHERE: Add missing entries to the words.h enum
2650d9aa0f MADS: DRAGONSPHERE: Cleanups and fixes for room 104


Commit: a434cc270d15e80bc7cd0fa053fe4bc1736b42c8
    https://github.com/scummvm/scummvm/commit/a434cc270d15e80bc7cd0fa053fe4bc1736b42c8
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2026-05-25T09:42:29+10:00

Commit Message:
MADS: DRAGONSPHERE: In progress implementing sound drivers

Changed paths:
    engines/mads/madsv2/dragonsphere/asound.cpp
    engines/mads/madsv2/dragonsphere/asound.h
    engines/mads/madsv2/dragonsphere/sound_dragonsphere.cpp
    engines/mads/madsv2/dragonsphere/sound_dragonsphere.h


diff --git a/engines/mads/madsv2/dragonsphere/asound.cpp b/engines/mads/madsv2/dragonsphere/asound.cpp
index 35dd5af3d7e..ae1082f1973 100644
--- a/engines/mads/madsv2/dragonsphere/asound.cpp
+++ b/engines/mads/madsv2/dragonsphere/asound.cpp
@@ -19,10 +19,7 @@
  *
  */
 
-#include "common/endian.h"
-#include "common/file.h"
-#include "common/md5.h"
-#include "common/textconsole.h"
+#include "audio/fmopl.h"
 #include "mads/madsv2/dragonsphere/asound.h"
 
 namespace MADS {
@@ -30,6 +27,1779 @@ namespace MADSV2 {
 namespace Dragonsphere {
 
 
+bool AdlibChannel::_isDisabled;
+
+/*
+  * PATCH_ATTEN_TO_TL  (seg001:0x0092 / offset from _asound_samples base)
+  * patchAttenuation (0-127) -> 6-bit OPL total-level.
+  * The Dragonsphere asm uses PATCH_ATTEN_TO_TL[bx] for the modulator lookup
+  * and unk_12431 - bx (i.e. PATCH_ATTEN_TO_TL[127 - patchAtt]) for the
+  * carrier lookup.
+  */
+static const uint8 PATCH_ATTEN_TO_TL[128] = {
+	63, 54, 49, 45, 42, 40, 38, 36, 34, 33, 32, 31, 30, 29, 28, 27,
+	26, 25, 25, 24, 23, 23, 22, 22, 21, 21, 20, 20, 19, 19, 18, 18,
+	18, 17, 17, 16, 16, 16, 15, 15, 15, 14, 14, 14, 14, 13, 13, 13,
+	12, 12, 12, 12, 11, 11, 11, 11, 11, 10, 10, 10, 10,  9,  9,  9,
+	 9,  9,  8,  8,  8,  8,  8,  7,  7,  7,  7,  7,  7,  6,  6,  6,
+	 6,  6,  6,  5,  5,  5,  5,  5,  5,  5,  4,  4,  4,  4,  4,  4,
+	 4,  3,  3,  3,  3,  3,  3,  3,  3,  2,  2,  2,  2,  2,  2,  2,
+	 2,  2,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  0
+};
+
+/*
+ * VOL_VEL_TO_ATTEN_STEP  (_volVelToAttenStep)
+ * volume or velocity byte (0-127) -> attenuation step (1-32).
+ * Groups of 4 inputs map to the same step.
+ */
+static const uint8 VOL_VEL_TO_ATTEN_STEP[128] = {
+	 1, 1, 1, 1,  2, 2, 2, 2,  3, 3, 3, 3,  4, 4, 4, 4,
+	 5, 5, 5, 5,  6, 6, 6, 6,  7, 7, 7, 7,  8, 8, 8, 8,
+	 9, 9, 9, 9, 10,10,10,10, 11,11,11,11, 12,12,12,12,
+	13,13,13,13, 14,14,14,14, 15,15,15,15, 16,16,16,16,
+	17,17,17,17, 18,18,18,18, 19,19,19,19, 20,20,20,20,
+	21,21,21,21, 22,22,22,22, 23,23,23,23, 24,24,24,24,
+	25,25,25,25, 26,26,26,26, 27,27,27,27, 28,28,28,28,
+	29,29,29,29, 30,30,30,30, 31,31,31,31, 32,32,32,32
+};
+
+/*
+ * SEMITONE_FREQ_TABLE  (_semitoneFreqTable)
+ * OPL F-number base for each semitone within an octave.
+ */
+static const uint16 SEMITONE_FREQ_TABLE[12] = {
+	0x0200, 0x021E, 0x023F, 0x0261, 0x0285, 0x02AB,
+	0x02D4, 0x02FF, 0x032D, 0x035D, 0x0390, 0x03C7
+};
+
+/*
+ * VOICE_SLOTS  (byte_1239B in the binary, also used as the operator-reg
+ * index table for command6/7)
+ *
+ * Layout for each voice: { slot0 (modulator), slot1 (carrier) }
+ * The writeVolume loop uses:
+ *   pass 0 -> VOICE_SLOTS[ch][0]  (modulator)
+ *   pass 1 -> VOICE_SLOTS[ch][1]  (carrier)
+ * The alg!=0 single-op path (loc_11692) goes directly to VOICE_SLOTS[ch][1].
+ */
+static const uint8 VOICE_SLOTS[ADLIB_CHANNEL_COUNT][2] = {
+	{  0,  3 }, {  1,  4 }, {  2,  5 },
+	{  6,  9 }, {  7, 10 }, {  8, 11 },
+	{ 12, 15 }, { 13, 16 }, { 14, 17 }
+};
+
+/*
+ * SLOT_TO_REG_OFFSET  (_slotToRegOffset)
+ * operator-slot index (0-17) -> OPL register group offset.
+ */
+static const uint8 SLOT_TO_REG_OFFSET[18] = {
+	 0,  1,  2,  3,  4,  5,
+	 8,  9, 10, 11, 12, 13,
+	16, 17, 18, 19, 20, 21
+};
+
+/*
+ * byte_1239B  - all 22 operator TL register indices muted/restored by
+ * command6 and command7.  They cover every OPL operator slot (0x40-0x55).
+ */
+static const uint8 ALL_OP_TL_REGS[22] = {
+	0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+	0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+	0x50, 0x51, 0x52, 0x53, 0x54, 0x55
+};
+
+/* Null / silence sound-data placeholder stream. */
+static uint8 ADLIB_NULLDATA[] = {
+	0x00, 0x01, 0xF3, 0x00, 0x00, 0xEF, 0x00, 0x00,
+	0xF5, 0x00, 0x00, 0x00, 0xF7, 0x00, 0xF8, 0x1D,
+	0xFF, 0x00, 0xF4, 0x6E, 0x2A, 0x1C, 0xF4, 0x5F,
+	0x2A, 0x1C, 0x2A, 0x1C, 0x2A, 0x1C, 0xFF, 0xFF,
+	0x00, 0x00, 0x00, 0x00
+};
+
+/* OPL version flag (_adlib_v5660 / opl_version_flag).
+ * 0x18 = OPL3 (patch-attenuation-aware writeVolume path). */
+constexpr uint16 OPL_VERSION_FLAG = 0x18;
+
+/* Rhythm-mode flags (from data segment). */
+constexpr bool RHYTHM_HI_HAT = true;
+constexpr bool RHYTHM_CYMBAL = true;
+constexpr bool RHYTHM_ENABLE = true;
+
+AdlibSample::AdlibSample(Common::SeekableReadStream &s) {
+	_attackRate = s.readByte();
+	_decayRate = s.readByte();
+	_sustainLevel = s.readByte();
+	_releaseRate = s.readByte();
+	_egTyp = s.readByte();
+	_ksr = s.readByte();
+	_totalLevel = s.readByte();
+	_scalingLevel = s.readByte();
+	_waveformSelect = s.readByte();
+	_freqMultiple = s.readByte();
+	_feedback = s.readByte();
+	_ampMod = s.readByte();
+	_vib = s.readByte();
+	_alg = s.readByte();
+	_freqSweepInit = s.readByte();
+	_reserved = s.readByte();
+	_freqMask = s.readUint16LE();
+	_freqBase = s.readUint16LE();
+	_outerLoopPtr = s.readUint16LE();
+}
+
+void AdlibChannel::reset() {
+	/* Zero the scalar "live" fields only.
+	 *
+	 * From AdlibChannel_reset in the binary: clears _activeCount, _pitchBend,
+	 * _volumeFadeStep, _vibratoDepth, _arpCounterReload (field_13),
+	 * _writeVolumePending (field_11), _transpose, _patchAttenuation,
+	 * _pendingStop, _velocity, _volume, _freqSweepCounter, _noiseFreqMask,
+	 * _freqAccum, _freqStep, _loopStartPtr, _pSrc.
+	 * All other fields are left as-is (they are overwritten by load() or
+	 * by the bytecode stream before being read).
+	 */
+	_activeCount = 0;
+	_pitchBend = 0;
+	_volumeFadeStep = 0;
+	_vibratoDepth = 0;
+	_arpCounterReload = 0;   /* field_13 */
+	_writeVolumePending = 0;   /* field_11 */
+	_transpose = 0;
+	_patchAttenuation = 0;
+	_pendingStop = 0;
+	_velocity = 0;
+	_volume = 0;
+	_freqSweepCounter = 0;
+	_noiseFreqMask = 0;
+	_freqAccum = 0;
+	_freqStep = 0;
+	_loopStartPtr = nullptr;
+	_pSrc = nullptr;
+}
+
+void AdlibChannel::load(byte *soundData) {
+	_isDisabled = true;
+
+	/* Zero all bytes in the struct. */
+	memset(this, 0, sizeof(AdlibChannel));
+
+	/* Initialise all loop/source pointers to the supplied data block. */
+	_loopStartPtr = soundData;
+	_pSrc = soundData;
+	_innerLoopPtr = soundData;
+	_outerLoopPtr = soundData;
+	_soundData = soundData;
+
+	/* Start at maximum attenuation (silent) and mark the channel active. */
+	_patchAttenuation = 0x40;
+	_activeCount = 1;
+
+	_isDisabled = false;
+}
+
+void AdlibChannel::setPtr2(byte *ptr) {
+	/* Hard-redirect both read pointers, then arm a one-step fade. */
+	_loopStartPtr = ptr;
+	_pSrc = ptr;
+	_volumeFadeStep = 0xFF;   /* -1 signed: fade down */
+	_fadePeriodReload = 1;
+	_fadePeriodCounter = 1;
+}
+
+void AdlibChannel::enable() {
+	/* AdlibChannel_enable: mark pending-stop on active channels only. */
+	if (_activeCount == 0)
+		return;
+	_pendingStop = 0xFF;
+	_soundData = ADLIB_NULLDATA;
+}
+
+void AdlibChannel::processChannelFade() {
+	if (_activeCount == 0)
+		return;
+	if (_pendingStop == 0)
+		return;
+
+	/* Channel has fully faded when both velocity and volume are zero. */
+	if (_velocity == 0 && _volume == 0) {
+		_loopStartPtr = ADLIB_NULLDATA;
+		_pSrc = ADLIB_NULLDATA;
+		_pendingStop = 0;
+		return;
+	}
+
+	/* Arm a quick fade-down (period 2, step -1). */
+	_volumeFadeStep = 0xFF;
+	_fadePeriodReload = 2;
+	if (_fadePeriodCounter == 0)
+		_fadePeriodCounter = 1;
+}
+
+ASound::ASound(Audio::Mixer *mixer, OPL::OPL *opl, const Common::Path &filename,
+	int dataOffset, int dataSize)
+	: SoundDriver(mixer, opl, filename, dataOffset, dataSize) {
+	AdlibChannel::_isDisabled = false;
+
+	/* Standard OPL timer-reset sequence. */
+	write(4, 0x60);
+	write(4, 0x80);
+	write(2, 0xFF);
+	write(4, 0x21);
+	write(4, 0x60);
+	write(4, 0x80);
+
+	Common::fill(_adlibPorts, _adlibPorts + 256, 0);
+	command0();
+
+	_opl->start(new Common::Functor0Mem<void, ASound>(this, &ASound::onTimer),
+		CALLBACKS_PER_SECOND);
+}
+
+int ASound::stop() {
+	command0();
+	int result = _pollResult;
+	_pollResult = 0;
+	return result;
+}
+
+int ASound::poll() {
+	update();
+	int result = _pollResult;
+	_pollResult = 0;
+	return result;
+}
+
+void ASound::noise() {
+	Common::StackLock slock(_driverMutex);
+	for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i)
+		noise_inner(i);
+}
+
+int ASound::command0() {
+	/* Push _isDisabled, temporarily force to 0xFFFF while resetting. */
+	uint16 savedDisabled = _isDisabled;
+	_isDisabled = 0xFFFF;
+
+	/* 1. Reset all 9 channels. */
+	for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i)
+		_channels[i]->reset();
+
+	/* 2. Mute all operator TL registers (0x40 down to 0x4F, via adlib_write2
+	 *    with value 0x3F, stepping from 0x4F down while reg >= 0x40). */
+	for (int reg = 0x4F; reg >= 0x40; --reg)
+		write((uint8)reg, 0x3F);
+
+	/* 3. Zero registers 0xFF down to 0x60. */
+	for (int reg = 0xFF; reg >= 0x60; --reg)
+		write((uint8)reg, 0x00);
+
+	/* 4. Zero registers 0x3F down to 0x01. */
+	for (int reg = 0x3F; reg >= 0x01; --reg)
+		write((uint8)reg, 0x00);
+
+	/* 5. Waveform Select Enable. */
+	write(0x01, 0x20);
+
+	/* 6. Reset tick callback. */
+	resetCallback();
+
+	_isDisabled = savedDisabled;
+	return 0;
+}
+
+int ASound::command1() {
+	/* Fade all channels: music 0-6 (command3) then SFX 7-8 (command5). */
+	command3();
+	command5();
+	return 0;
+}
+
+int ASound::command2() {
+	/* Hard-redirect music channels 0-6 to null stream with setPtr2. */
+	_channel0.setPtr2(ADLIB_NULLDATA);
+	_channel1.setPtr2(ADLIB_NULLDATA);
+	_channel2.setPtr2(ADLIB_NULLDATA);
+	_channel3.setPtr2(ADLIB_NULLDATA);
+	_channel4.setPtr2(ADLIB_NULLDATA);
+	_channel5.setPtr2(ADLIB_NULLDATA);
+	_channel6.setPtr2(ADLIB_NULLDATA);
+	return 0;
+}
+
+int ASound::command3() {
+	/* Pending-stop music channels 0-6 (natural envelope fade via enable). */
+	_channel0.enable();
+	_channel1.enable();
+	_channel2.enable();
+	_channel3.enable();
+	_channel4.enable();
+	_channel5.enable();
+	_channel6.enable();
+	return 0;
+}
+
+int ASound::command4() {
+	/* Hard-redirect SFX channels 7-8. */
+	_channel7.setPtr2(ADLIB_NULLDATA);
+	_channel8.setPtr2(ADLIB_NULLDATA);
+	return 0;
+}
+
+int ASound::command5() {
+	/* Pending-stop SFX channels 7-8. */
+	_channel7.enable();
+	_channel8.enable();
+	return 0;
+}
+
+int ASound::command6() {
+	/* Guard: already disabled. */
+	if (_isDisabled == 0xFFFF)
+		return 0;
+	_isDisabled = 0xFFFF;
+
+	/* Save each channel's _freqSweepCounter -> _savedSweepCounter, then zero it. */
+	for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i) {
+		AdlibChannel *ch = _channels[i];
+		ch->_savedSweepCounter = ch->_freqSweepCounter;
+		ch->_freqSweepCounter = 0;
+	}
+
+	/* Mute all 22 operator TL registers. */
+	for (int i = 0; i < 22; ++i)
+		adlib_channelOff(ALL_OP_TL_REGS[i]);
+
+	return 0;
+}
+
+int ASound::command7() {
+	/* Guard: only resume from a paused state. */
+	if (_isDisabled != 0xFFFF)
+		return 0;
+
+	/* Restore all operator volumes from the _adlibPorts shadow
+	 * (asound_playMusicC: just re-writes whatever was last written). */
+	for (int i = 0; i < 22; ++i)
+		adlib_channelOn(ALL_OP_TL_REGS[i]);
+
+	/* Restore each channel's sweep counter and check whether any was non-zero. */
+	uint8 anySweep = 0;
+	for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i) {
+		AdlibChannel *ch = _channels[i];
+		ch->_freqSweepCounter = ch->_savedSweepCounter;
+		anySweep |= ch->_savedSweepCounter;
+	}
+
+	if (anySweep != 0)
+		signalSoundPlaying();
+
+	_isDisabled = 0;
+	return 0;
+}
+
+int ASound::command8() {
+	/* Returns non-zero if any channel is currently active.
+	 * Clears byte_12393 (music-only flag) first so all 9 channels are checked. */
+	_musicOnlyFlag = 0;
+	uint8 result = 0;
+	for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i)
+		result |= _channels[i]->_activeCount;
+	return result;
+}
+
+int ASound::command18() {
+	/* Re-entrant background-music launcher (asound_command18 in the binary).
+	 * Fades everything, then dispatches back through the command table using
+	 * _musicIndex (word_12370) as the command ID. */
+	command1();
+	return command(_musicIndex, 0);
+}
+
+void ASound::callFunction(uint16 offset) {
+	error("Unsupported call to sound driver function at offset %.4x", offset);
+}
+
+void ASound::write(uint8 reg, uint8 value) {
+	_adlibPorts[reg] = value;
+	_opl->writeReg(reg, value);
+}
+
+void ASound::onTimer() {
+	Common::StackLock slock(_driverMutex);
+	poll();
+}
+
+uint16 ASound::getRandomNumber() {
+	/* asound_getRandomNumber: ax = ror3(0x9248 + seed). */
+	uint16 ax = (uint16)(0x9248u + _randomSeed);
+	ax = (uint16)((ax >> 3) | (ax << 13));
+	_randomSeed = ax;
+	return ax;
+}
+
+void ASound::adlib_channelOff(uint8 portIndex) {
+	/* sub_1018F: OR the register with 0x3F (force max attenuation),
+	 * then write back both to _adlibPorts and to the OPL chip.
+	 * Note: unlike the Phantom driver, the original value is NOT preserved
+	 * in _adlibPorts - the ORed value is stored back. */
+	uint8 val = _adlibPorts[portIndex] | 0x3F;
+	_adlibPorts[portIndex] = val;
+	_opl->writeReg(portIndex, val);
+}
+
+void ASound::adlib_channelOn(uint8 portIndex) {
+	/* asound_playMusicC: re-writes whatever _adlibPorts already holds.
+	 * command7 simply replays the shadow without recalculating anything. */
+	_opl->writeReg(portIndex, _adlibPorts[portIndex]);
+}
+
+void ASound::signalSoundPlaying() {
+	/* asound_signalSoundPlaying: set resultFlag/pollResult to non-zero. */
+	if (_resultFlag == (int16)0xFFFF)
+		return;
+	_resultFlag = (int16)0xFFFF;
+	_pollResult = (int16)0xFFFF;
+}
+
+void ASound::clearCallback() {
+	_callbackFnPtr = nullptr;
+	_callbackCounter = 0;
+	_callbackPeriod = 0;
+}
+
+void ASound::tickCallback() {
+	if (_callbackPeriod == 0)
+		return;
+	if (--_callbackCounter != 0)
+		return;
+
+	_callbackCounter = _callbackPeriod;
+	if (_callbackFnPtr == nullptr)
+		return;
+
+	auto fn = _callbackFnPtr;
+	_callbackFnPtr = nullptr;
+	(this->*fn)();
+}
+
+void ASound::writeVolume() {
+	AdlibChannel *ch = _activeChannelPtr;
+	uint8 chanNum = _activeChannelNumber;
+
+	/* Compute combined attenuation step from volume and velocity. */
+	uint8  volIdx = ch->_volume;
+	uint8  velIdx = ch->_velocity;
+	int16  volStep = (int16)(uint16)VOL_VEL_TO_ATTEN_STEP[volIdx];
+	int16  velStep = (int16)(uint16)VOL_VEL_TO_ATTEN_STEP[velIdx];
+	int16  var4 = volStep + velStep - 1;   /* var_4: combined step (shared) */
+
+	/* Check _alg of the first sample to determine loop count. */
+	AdlibSample *smpFirst = &_samples[ch->_sampleIndex * 2];
+	int passes = (smpFirst->_alg == 0) ? 2 : 1;
+
+	/* si and di track the two TL values written per pass for caching. */
+	int16 finalSi = 0, finalDi = 0;
+
+	/* var_6 = loop counter (0, then 1 for the two-pass case).
+	 * For alg != 0 we jump straight into the carrier path (var_6 stays at
+	 * effectively 1: VOICE_SLOTS[ch][1]). */
+	for (int var6 = (passes == 1 ? 1 : 0); var6 < 2; ++var6) {
+
+		/* Reload var_2 = var_4 at the start of each pass (loc_11642). */
+		int16 var2 = var4;
+
+		/* Select the operator slot. */
+		uint8 slot = VOICE_SLOTS[chanNum][var6];
+		uint8 regOff = SLOT_TO_REG_OFFSET[slot];
+		uint16 tlReg = (uint16)regOff + 0x40;   /* var_8 */
+
+		/* KSL bits from the port shadow. */
+		int16 kslBits = (int16)((uint16)_adlibPorts[tlReg] & 0xC0);   /* var_A */
+
+		int16 si, di;
+
+		if (OPL_VERSION_FLAG < 0x18) {
+			/* ---- OPL2 simple path (loc_1167C / loc_1167C equivalent) ---- */
+			int16 tl = (int16)0x3F - var2;
+			tl |= kslBits;
+			si = tl;
+			di = tl;
+			/* adlib_write2(8, tlReg, tl) */
+			write((uint8)tlReg, (uint8)tl);
+		} else {
+			/* ---- OPL3 patch-attenuation path (loc_115BE / loc_116D4) ---- */
+			uint8 pa = ch->_patchAttenuation;
+
+			/* Modulator TL (first register, offset 0): */
+			int16 tlMod = (int16)(uint16)PATCH_ATTEN_TO_TL[pa];
+			/* -(tlMod - var2) = var2 - tlMod */
+			int16 atten1 = var2 - tlMod;   /* ax = si initially */
+			si = atten1;
+			if (si < 0)       si = 0;
+			else if (si > 63) si = 63;
+			int16 reg0val = ((int16)0x3F - si) | kslBits;
+			si = reg0val;
+			write((uint8)tlReg, (uint8)reg0val);
+
+			/* Carrier TL (second register, offset 2):
+			 * unk_12431 - bx (where bx = pa) == PATCH_ATTEN_TO_TL[127 - pa]. */
+			int16 tlCar = (int16)(uint16)PATCH_ATTEN_TO_TL[127 - pa];
+			/* di = var_2 - tlCar  (var_4 for alg!=0, var_2=var_4 reload for alg==0) */
+			di = var2 - tlCar;
+			int16 diClamped = di;
+			if (diClamped < 0)        diClamped = 0;
+			else if (diClamped > 0x3F) diClamped = 0x3F;
+			int16 reg2val = ((int16)0x3F - diClamped) | kslBits;
+			di = reg2val;
+			write((uint8)(tlReg + 2), (uint8)reg2val);
+		}
+
+		/* For the alg!=0 single-pass case we only run this once (var6==1).
+		 * For the alg==0 two-pass case, record both sets. */
+		if (var6 == 1) {
+			finalSi = si;
+			finalDi = di;
+		} else {
+			/* var6==0: record si/di for the mod pass; di is overwritten on
+			 * the carrier pass so we use si for the high byte of field_2A. */
+			finalSi = si;
+			finalDi = di;
+		}
+	}
+
+	/* Cache the written TL bytes in field_2A:
+	 *   dl = di & 0x3F  (carrier / second register)
+	 *   dh = si & 0x3F  (modulator / first register)
+	 * Packed as a word at offset 0x2A. */
+	ch->_cachedCarrierTL = (uint8)(finalDi & 0x3F);   /* dl -> field_2A low byte  */
+	/* The high byte (si & 0x3F) is stored in _savedFreqSweep (offset 0x2B)
+	 * by the assembly.  We write it separately to avoid clobbering the real
+	 * savedFreqSweep which lives there - the original asm stores both bytes
+	 * as a word at [bx+2Ah], meaning field_2A=dl and field_2B=dh=si&0x3F.
+	 * In our C++ layout _savedFreqSweep is at 0x2B; we shadow the dh byte
+	 * into it only when writing volume, consistent with the original. */
+	ch->_savedFreqSweep = (uint8)(finalSi & 0x3F);
+}
+
+void ASound::writeFrequency() {
+	AdlibChannel *ch = _activeChannelPtr;
+	uint8 chanNum = _activeChannelNumber;
+	uint16 aReg = (uint16)chanNum + 0xA0;
+	uint16 bReg = (uint16)chanNum + 0xB0;
+
+	/* Note is 1-based; _octaveTranspose shifts by whole octaves. */
+	int note = (int)ch->_note + (int)ch->_octaveTranspose - 1;
+	int octave = note / 12;
+	int semi = note % 12;
+	if (semi < 0) {
+		semi += 12; --octave;
+	}
+
+	/* F-number from table, with optional signed transpose offset. */
+	int16 fnum = (int16)SEMITONE_FREQ_TABLE[semi] + (int16)(int8)ch->_transpose;
+
+	write((uint8)aReg, (uint8)fnum);
+
+	uint8 bVal = _adlibPorts[bReg] & 0x20;          /* preserve key-on bit */
+	bVal |= (uint8)((fnum >> 8) & 0x03);            /* F-num bits 9:8 */
+	bVal |= (uint8)(((uint8)octave & 0x07) << 2);   /* block/octave bits 4:2 */
+	write((uint8)bReg, bVal);
+}
+
+void ASound::writePitchBend() {
+	AdlibChannel *ch = _activeChannelPtr;
+	uint8 chanNum = _activeChannelNumber;
+	uint16 aReg = (uint16)chanNum + 0xA0;
+	uint16 bReg = (uint16)chanNum + 0xB0;
+
+	/* Reconstruct the 10-bit F-number from the port shadow. */
+	uint16 fnum = (uint16)_adlibPorts[aReg]
+		| ((uint16)(_adlibPorts[bReg] & 0x1F) << 8);
+
+	int16 bent = (int16)fnum + (int16)(int8)ch->_pitchBend;
+
+	write((uint8)aReg, (uint8)bent);
+
+	/* Preserve block + key-on, update F-num[9:8]. */
+	uint8 bVal = _adlibPorts[bReg] & 0x20;
+	bVal |= (uint8)((bent >> 8) & 0x03);
+	write((uint8)bReg, bVal);
+}
+
+void ASound::writeArpeggio() {
+	AdlibChannel *ch = _activeChannelPtr;
+	uint8 chanNum = _activeChannelNumber;
+	uint16 aReg = (uint16)chanNum + 0xA0;
+	uint16 bReg = (uint16)chanNum + 0xB0;
+
+	/* dl = _note + _octaveTranspose + _writeVolumePending - 1 */
+	int note = (int)ch->_note + (int)ch->_octaveTranspose
+		+ (int)ch->_writeVolumePending - 1;
+	int octave = note / 12;
+	int semi = note % 12;
+	if (semi < 0) {
+		semi += 12; --octave;
+	}
+
+	uint16 freqEntry = SEMITONE_FREQ_TABLE[semi];
+	uint8  fnHigh = (uint8)((freqEntry >> 8) & 0x03);
+	uint8  block = (uint8)(octave & 0x07);
+	uint8  bVal = (uint8)((block << 2) | fnHigh) | (_adlibPorts[bReg] & 0x20);
+
+	write((uint8)aReg, (uint8)freqEntry);
+	write((uint8)bReg, bVal);
+}
+
+void ASound::updateOctave() {
+	uint8 chanNum = _activeChannelNumber;
+	uint16 bReg = (uint16)chanNum + 0xB0;
+	write((uint8)bReg, _adlibPorts[bReg] & 0xDF);
+}
+
+void ASound::noteOn() {
+	AdlibChannel *ch = _activeChannelPtr;
+	AdlibSample *smp = &_samples[ch->_sampleIndex];
+
+	writeVolume();
+
+	ch->_freqSweepCounter = smp->_freqSweepInit;
+
+	if (ch->_freqSweepCounter != 0) {
+		/* Sweep is pending; signal that sound is active but don't key on yet. */
+		signalSoundPlaying();
+		return;
+	}
+
+	/* No sweep - write frequency and set key-on immediately. */
+	writeFrequency();
+
+	uint8 chanNum = _activeChannelNumber;
+	uint16 bReg = (uint16)chanNum + 0xB0;
+	write((uint8)bReg, _adlibPorts[bReg] | 0x20);
+}
+
+void ASound::writeSampleRegs() {
+	AdlibSample *smp = _samplePtr;
+	uint16 base = _currentOpBase;
+
+	/* 0xBD: rhythm mode flags. */
+	uint8 bdVal = _adlibPorts[0xBD] & 0x3F;
+	if (RHYTHM_HI_HAT) bdVal |= 0x80;
+	if (RHYTHM_CYMBAL) bdVal |= 0x40;
+	write(0xBD, bdVal);
+
+	/* 0x08: note-select / rhythm enable. */
+	write(0x08, RHYTHM_ENABLE ? 0x40 : 0x00);
+
+	/* 0xC0+voice: feedback and algorithm.
+	 * asm: feedback << 1, then alg==1 -> bit0=0, else -> bit0=1. */
+	write((uint8)(_activeChannelReg + 0xC0),
+		(uint8)((smp->_feedback << 1) | (smp->_alg == 1 ? 0 : 1)));
+
+	/* 0x60+base: attack / decay. */
+	write((uint8)(base + 0x60),
+		(uint8)(((smp->_attackRate & 0x0F) << 4) | (smp->_decayRate & 0x0F)));
+
+	/* 0x80+base: sustain level / release rate. */
+	write((uint8)(base + 0x80),
+		(uint8)(((smp->_sustainLevel & 0x0F) << 4) | (smp->_releaseRate & 0x0F)));
+
+	/* 0x20+base: AM/VIB/EGT/KSR/mult flags. */
+	uint8 amVal = smp->_freqMultiple & 0x0F;
+	if (smp->_egTyp == 1) amVal |= 0x20;
+	if (smp->_vib == 1) amVal |= 0x40;
+	if (smp->_ksr == 1) amVal |= 0x10;
+	if (smp->_ampMod == 1) amVal |= 0x80;
+	write((uint8)(base + 0x20), amVal);
+
+	/* 0xE0+base: waveform select. */
+	write((uint8)(base + 0xE0), (uint8)(smp->_waveformSelect & 0x03));
+}
+
+void ASound::loadSample() {
+	AdlibChannel *ch = _activeChannelPtr;
+	uint8 chanNum = _activeChannelNumber;
+
+	/* Phase 1: Silence modulator operator (slot 0). */
+	uint8 mSlot = VOICE_SLOTS[chanNum][0];
+	uint8 mRegOff = SLOT_TO_REG_OFFSET[mSlot];
+	write((uint8)(mRegOff + 0x80), 0xFF);
+	write((uint8)(mRegOff + 0x40), 63);
+
+	/* Phase 2: Silence carrier operator (slot 1). */
+	uint8 cSlot = VOICE_SLOTS[chanNum][1];
+	uint8 cRegOff = SLOT_TO_REG_OFFSET[cSlot];
+	write((uint8)(cRegOff + 0x80), 0xFF);
+	write((uint8)(cRegOff + 0x40), 63);
+
+	/* Phase 3: Write first sample (modulator) registers. */
+	_activeChannelReg = (uint16)chanNum;
+	_currentOpBase = mRegOff;
+	_samplePtr = &_samples[ch->_sampleIndex * 2];
+	writeSampleRegs();
+
+	/* Phase 4: Write second sample (carrier) registers. */
+	_currentOpBase = cRegOff;
+	_samplePtr = &_samples[ch->_sampleIndex * 2 + 1];
+	writeSampleRegs();
+
+	/* Phase 5: Write carrier TL (KSL | 0x3F for full attenuation initially). */
+	write((uint8)(cRegOff + 0x40),
+		(uint8)((_samplePtr->_scalingLevel << 6) | 0x3F));
+
+	/* Phase 6: Use the first sample again for the modulator TL and for
+	 * sweep/frequency initialisation. */
+	AdlibSample *smp3 = &_samples[ch->_sampleIndex * 2];
+	uint16        mTLReg = (uint16)mRegOff + 0x40;
+
+	if (smp3->_alg == 0) {
+		write((uint8)mTLReg, (uint8)((smp3->_scalingLevel << 6) | 0x3F));
+	} else {
+		/* alg != 0: negate (totalLevel - 63) to get the TL value. */
+		uint8 tl = (uint8)(-(int8)(smp3->_totalLevel - 63));
+		write((uint8)mTLReg, (uint8)((smp3->_scalingLevel << 6) | (tl & 0x3F)));
+	}
+
+	/* Copy sweep/frequency parameters from the sample into the channel. */
+	ch->_freqSweepCounter = smp3->_freqSweepInit;
+	ch->_noiseFreqMask = smp3->_freqMask;
+	ch->_freqAccum = smp3->_freqBase;
+	ch->_freqStep = smp3->_outerLoopPtr;
+}
+
+void ASound::update() {
+	if (_isDisabled)
+		return;
+
+	(void)getRandomNumber();
+	++_frameNumber2;
+
+	pollAllChannels();
+	tickCallback();       /* asound_updateCallback */
+	updateAllChannels();
+
+	_anySweepActive = 0;
+
+	for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i)
+		update1(i);
+
+	/* If no sweep is active, flag the result as "done". */
+	if (!_anySweepActive) {
+		if (_resultFlag != (int16)0xFFFF) {
+			_resultFlag = (int16)0xFFFF;
+			_pollResult = (int16)0xFFFF;
+		}
+	}
+}
+
+void ASound::update1(int channelIndex) {
+	/* asound_update1: called with cx = channelIndex+1 in the original loop.
+	 * Accesses channel via the adlib_channels pointer table. */
+	AdlibChannel *ch = _channels[channelIndex];
+
+	if (ch->_freqSweepCounter == 0)
+		return;
+
+	_anySweepActive = 1;
+	ch->_freqAccum += ch->_freqStep;
+	ch->_freqSweepCounter--;
+
+	if (ch->_freqSweepCounter != 0)
+		return;
+
+	/* Sweep finished: write zero frequency to silence this OPL voice. */
+	uint8 voice = (uint8)channelIndex;
+	write((uint8)(voice + 0xA0), 0x00);
+	write((uint8)(voice + 0xB0), 0x00);
+}
+
+void ASound::setFrequency(uint8 voice, uint16 freq) {
+	/* asound_setFrequency: writes A0+v and B0+v (with key-on). */
+	write((uint8)(voice + 0xA0), (uint8)(freq & 0xFF));
+	uint8 bVal = (_adlibPorts[voice + 0xB0] & 0x20)
+		| (uint8)((freq >> 8) & 0x03)
+		| 0x20;
+	write((uint8)(voice + 0xB0), bVal);
+}
+
+void ASound::noise_inner(int channelIndex) {
+	/* adlib_noise_inner */
+	AdlibChannel *ch = _channels[channelIndex];
+
+	if (ch->_freqSweepCounter == 0)
+		return;
+
+	uint16 rnd = getRandomNumber();
+	uint16 freq = (rnd & ch->_noiseFreqMask) + ch->_freqAccum;
+	setFrequency((uint8)channelIndex, freq);
+}
+
+void ASound::updateAllChannels() {
+	for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i)
+		_channels[i]->processChannelFade();
+}
+
+void ASound::findFreeChannel(byte *soundData) {
+	_findChannelMode = 0;
+
+	/* Scan channels 0-6 for an empty slot. */
+	for (int ch = 0; ch < 7; ++ch) {
+		if (_channels[ch]->_activeCount == 0) {
+			_channels[ch]->load(soundData);
+			return;
+		}
+	}
+
+	/* _findChannelMode==0: fall through to full search. */
+	findFreeChannelFull(soundData);
+}
+
+void ASound::findFreeChannelFull(byte *soundData) {
+	_findChannelMode = 2;
+
+	/* Scan channels 7-8 for an empty slot. */
+	if (_channel7._activeCount == 0) {
+		_channel7.load(soundData); return;
+	}
+	if (_channel8._activeCount == 0) {
+		_channel8.load(soundData); return;
+	}
+
+	/* Scan ch8 then ch7 for pending-stop (pre-emptible). */
+	if (_channel8._pendingStop == 0xFF) {
+		_channel8.load(soundData); return;
+	}
+	if (_channel7._pendingStop == 0xFF) {
+		_channel7.load(soundData); return;
+	}
+
+	/* If _findChannelMode allows it, scan ch6..ch0 for pending-stop. */
+	if (_findChannelMode == 0) {
+		if (_channel6._pendingStop == 0xFF) {
+			_channel6.load(soundData); return;
+		}
+		if (_channel5._pendingStop == 0xFF) {
+			_channel5.load(soundData); return;
+		}
+		if (_channel4._pendingStop == 0xFF) {
+			_channel4.load(soundData); return;
+		}
+		if (_channel3._pendingStop == 0xFF) {
+			_channel3.load(soundData); return;
+		}
+		if (_channel2._pendingStop == 0xFF) {
+			_channel2.load(soundData); return;
+		}
+		if (_channel1._pendingStop == 0xFF) {
+			_channel1.load(soundData); return;
+		}
+		if (_channel0._pendingStop == 0xFF) {
+			_channel0.load(soundData); return;
+		}
+	}
+}
+
+int ASound::isMusicChannelsActive() {
+	_musicOnlyFlag = 1;
+	uint8 result = 0;
+	for (int i = 0; i <= 6; ++i)
+		result |= _channels[i]->_activeCount;
+	return result;
+}
+
+int ASound::isAnyChannelActive() {
+	_musicOnlyFlag = 0;
+	uint8 result = 0;
+	for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i)
+		result |= _channels[i]->_activeCount;
+	return result;
+}
+
+void ASound::pollAllChannels() {
+	_activeChannelNumber = 0;
+	for (int ch = 0; ch < ADLIB_CHANNEL_COUNT; ++ch) {
+		_activeChannelPtr = _channels[ch];
+		pollActiveChannel();
+		/* Note: _activeChannelNumber is incremented at the end of
+		 * pollActiveChannel, not here. */
+	}
+}
+
+bool ASound::isSoundActive(byte *ptr) const {
+	for (int ch = 0; ch < ADLIB_CHANNEL_COUNT; ++ch) {
+		if (_channels[ch]->_activeCount && _channels[ch]->_soundData == ptr)
+			return true;
+	}
+	return false;
+}
+
+uint16 ASound::readWord_impl() {
+	/* asound_readWord: advance pSrc past the low byte, read lo then hi. */
+	uint16 lo = *++pSrc;
+	uint16 hi = *++pSrc;
+	return lo | (hi << 8);
+}
+
+void ASound::pollActiveChannel() {
+	AdlibChannel *ch = _activeChannelPtr;
+
+	if (ch->_activeCount == 0) {
+		++_activeChannelNumber;
+		return;
+	}
+
+	/* byte_16A0A: volume-dirty flag.  Cleared here, set by various opcodes
+	 * and by the fade/vibrato sections; causes writeVolume at the end. */
+	bool volDirty = false;
+
+	/* ---- Key-on delay countdown ---- */
+	if (ch->_keyOnDelay != 0) {
+		ch->_keyOnDelay--;
+		ch = _activeChannelPtr;
+		if (ch->_keyOnDelay == 0)
+			updateOctave();
+	}
+
+	/* ---- Duration countdown ---- */
+	ch = _activeChannelPtr;
+	ch->_activeCount--;
+	ch = _activeChannelPtr;
+	if (ch->_activeCount != 0)
+		goto post_keyon;
+
+	/* ================================================================
+	 * Command-dispatch loop.
+	 * Re-entered (via goto dispatch) after each command that does not
+	 * consume a duration tick.  Falls through to the note-event path
+	 * when a note byte is encountered.
+	 * ================================================================ */
+dispatch:
+	{
+		ch = _activeChannelPtr;
+		pSrc = ch->_pSrc;
+
+		uint8 b = *pSrc;
+
+		if (!(b & 0x80))
+			goto note_event;
+
+		/* Decode group (si) and sub-opcode (di). */
+		uint8 si = b & 0x70;
+		uint8 di = b & 0x0F;
+
+		if (si == 0x00) {
+			/* ============================================================
+			 * opcodes1  (group 0, 0x8_)
+			 * Called with: pSrc pointing at the command byte.
+			 * On entry, opcodes1 does: inc pSrc (skip past command byte).
+			 * On exit, sets _pSrc = pSrc + 1 (def_10CDC).
+			 * ============================================================ */
+			pSrc++;   /* skip command byte */
+
+			switch (di) {
+
+			case 0x0: /* set sampleIndex, call loadSample */
+				ch->_sampleIndex = *pSrc;
+				loadSample();
+				volDirty = true;
+				/* def_10CDC: _pSrc = pSrc + 1 */
+				ch->_pSrc = pSrc + 1;
+				goto dispatch;
+
+			case 0x1: /* set velocity */
+			{
+				uint8 vel = *pSrc;
+				if (ch->_pendingStop != 0) {
+					/* If current velocity (signed) > new vel (signed), clamp down. */
+					if ((int16)(int8)ch->_velocity > (int16)(int8)vel)
+						ch->_velocity = vel;
+				} else {
+					ch->_velocity = vel;
+				}
+				volDirty = true;
+				ch->_pSrc = pSrc + 1;
+				goto dispatch;
+			}
+
+			case 0x2: /* set volume */
+			{
+				uint8 vol = *pSrc;
+				if (ch->_pendingStop != 0) {
+					/* Don't raise volume above current when fading out. */
+					uint16 curVol = (uint16)ch->_volume;
+					if (curVol <= (uint16)vol)
+						goto op2_set_vol;
+					/* else fall through and skip the set */
+					volDirty = true;
+					ch->_pSrc = pSrc + 1;
+					goto dispatch;
+				}
+op2_set_vol:
+				ch->_volume = vol;
+				volDirty = true;
+				ch->_pSrc = pSrc + 1;
+				goto dispatch;
+			}
+
+			case 0x3: /* set patchAttenuation */
+				ch->_patchAttenuation = *pSrc;
+				volDirty = true;   /* opcodes1 case 3 jumps to loc_10B56 -> byte_16A0A=1 */
+				ch->_pSrc = pSrc + 1;
+				goto dispatch;
+
+			case 0x4: /* set fade: fadePeriodReload, volumeFadeStep */
+				if (ch->_pendingStop != 0) {
+					/* Skip this opcode entirely when pending-stop is set. */
+					ch->_pSrc = pSrc + 2;
+					goto dispatch;
+				}
+				ch->_fadePeriodReload = *pSrc++;
+				ch->_volumeFadeStep = *pSrc;
+				ch->_fadePeriodCounter = 1;
+				ch->_pSrc = pSrc + 1;
+				goto dispatch;
+
+			case 0x5: /* set vibrato: vibPeriodReload, vibPeriodCounter=reload, vibratoDepth, vibratoMode */
+				ch->_vibPeriodReload = *pSrc++;
+				ch->_vibPeriodCounter = ch->_vibPeriodReload;
+				ch->_vibratoDepth = *pSrc++;
+				ch->_vibratoMode = *pSrc;
+				ch->_pSrc = pSrc + 1;
+				goto dispatch;
+
+			case 0x6: /* set transpose */
+				ch->_transpose = *pSrc;
+				ch->_pSrc = pSrc + 1;
+				goto dispatch;
+
+			case 0x7: /* set octaveTranspose */
+				ch->_octaveTranspose = *pSrc;
+				ch->_pSrc = pSrc + 1;
+				goto dispatch;
+
+			case 0x8: /* set field_11 (_writeVolumePending) */
+				ch->_writeVolumePending = *pSrc;
+				ch->_pSrc = pSrc + 1;
+				goto dispatch;
+
+			case 0x9: /* set arpeggio: field_12->field_C, pitchBend, field_13 */
+				ch->_arpPeriodReload = *pSrc++;
+				ch->_arpPeriodCounter = ch->_arpPeriodReload;   /* field_C = field_12 */
+				ch->_pitchBend = *pSrc++;
+				ch->_arpCounterReload = *pSrc;
+				ch->_pSrc = pSrc + 1;
+				goto dispatch;
+
+			case 0xA: /* skip forward by N+3 bytes (from the command byte) */
+			{
+				uint8 skip = *pSrc;
+				/* pSrc currently points at the byte after the command byte.
+				 * Advance _pSrc by (skip + 3) from the *command* byte position,
+				 * which is pSrc-1.  So _pSrc += skip + 3 from original _pSrc,
+				 * which was command byte.  But def_10CDC does _pSrc = pSrc+1
+				 * giving +2 past command.  Then skip+3 total from command: */
+				 /* asm: si = skip; ax = si + 3; add pSrc, ax; then def_10CDC
+				  * does pSrc+1.  So net advance from the command byte =
+				  * skip + 3 + 1 = skip + 4.  But pSrc already points one past
+				  * command byte, so _pSrc = pSrc + skip + 3. */
+				ch->_pSrc = pSrc + (uint16)skip + 3;
+				goto dispatch;
+			}
+
+			case 0xB: /* set durationOverride, clear noteOffset */
+				ch->_durationOverride = *pSrc;
+				ch->_noteOffset = 0;
+				ch->_pSrc = pSrc + 1;
+				goto dispatch;
+
+			case 0xC: /* set noteOffset, clear durationOverride */
+				ch->_noteOffset = *pSrc;
+				ch->_durationOverride = 0;
+				ch->_pSrc = pSrc + 1;
+				goto dispatch;
+
+			default:
+				/* Unknown sub-opcode: advance past the single operand byte. */
+				ch->_pSrc = pSrc + 1;
+				goto dispatch;
+			}
+		}
+
+		if (si == 0x10) {
+			/* ============================================================
+			 * opcodes2  (group 1, 0x9_)
+			 * Loop control, restart, branch / call.
+			 * On exit: either sets _pSrc directly or falls to def_10EAC
+			 * (which just returns without touching _pSrc - the handlers
+			 * themselves are responsible for updating _pSrc).
+			 * ============================================================ */
+			switch (di) {
+
+			case 0x0: /* inner loop */
+			{
+				ch = _activeChannelPtr;
+				if (ch->_innerLoopCount == 0) {
+					pSrc++;   /* advance to count byte */
+					uint8 cnt = *pSrc;
+					if (cnt == 0) {
+						ch->_pSrc += 2;
+						ch = _activeChannelPtr;
+						ch->_innerLoopPtr = ch->_pSrc;
+						ch->_innerLoopCount = 0;
+						goto dispatch;
+					}
+					ch->_innerLoopCount = (uint16)cnt;
+					/* Jump to innerLoopPtr (loc_10D5A). */
+					ch->_pSrc = ch->_innerLoopPtr;
+					goto dispatch;
+				}
+				/* Count is non-zero: decrement. */
+				ch->_innerLoopCount--;
+				ch = _activeChannelPtr;
+				if (ch->_innerLoopCount == 0) {
+					/* Loop done: advance past the two-byte opcode. */
+					ch->_pSrc += 2;
+					ch = _activeChannelPtr;
+					ch->_innerLoopPtr = ch->_pSrc;
+					goto dispatch;
+				}
+				/* Still looping: jump back. */
+				ch->_pSrc = ch->_innerLoopPtr;
+				goto dispatch;
+			}
+
+			case 0x1: /* outer loop */
+			{
+				ch = _activeChannelPtr;
+				if (ch->_outerLoopCount == 0) {
+					pSrc++;
+					uint8 cnt = *pSrc;
+					if (cnt == 0) {
+						ch->_pSrc += 2;
+						ch = _activeChannelPtr;
+						ch->_outerLoopPtr = ch->_pSrc;
+						ch->_innerLoopPtr = ch->_pSrc;
+						ch->_innerLoopCount = 0;
+						ch->_outerLoopCount = 0;
+						goto dispatch;
+					}
+					ch->_outerLoopCount = (uint16)cnt;
+					/* Jump to outerLoopPtr. */
+					ch->_pSrc = ch->_outerLoopPtr;
+					ch = _activeChannelPtr;
+					ch->_innerLoopPtr = ch->_outerLoopPtr;
+					goto dispatch;
+				}
+				/* Count non-zero: decrement. */
+				ch->_outerLoopCount--;
+				ch = _activeChannelPtr;
+				if (ch->_outerLoopCount == 0) {
+					ch->_pSrc += 2;
+					ch = _activeChannelPtr;
+					ch->_outerLoopPtr = ch->_pSrc;
+					ch->_innerLoopPtr = ch->_pSrc;
+					goto dispatch;
+				}
+				ch->_pSrc = ch->_outerLoopPtr;
+				ch = _activeChannelPtr;
+				ch->_innerLoopPtr = ch->_outerLoopPtr;
+				goto dispatch;
+			}
+
+			case 0x2: /* restart: reset all loop pointers to _soundData */
+			{
+				ch = _activeChannelPtr;
+				uint16 sd = (uint16)(uintptr_t)ch->_soundData;
+				ch->_loopStartPtr = ch->_soundData;
+				ch->_pSrc = ch->_soundData;
+				ch->_innerLoopPtr = ch->_soundData;
+				ch->_outerLoopPtr = ch->_soundData;
+				(void)sd;
+				goto dispatch;
+			}
+
+			case 0x3: /* set sound-data pointer (abs word offset), reset all loops */
+			{
+				byte *ptr = &_soundData[readWord_impl()];
+				ch = _activeChannelPtr;
+				ch->_loopStartPtr = ptr;
+				ch->_pSrc = ptr;
+				ch->_innerLoopPtr = ptr;
+				ch->_outerLoopPtr = ptr;
+				ch->_soundData = ptr;
+				goto dispatch;
+			}
+
+			case 0x4: /* branch unconditional */
+			{
+				byte *dest = &_soundData[readWord_impl()];
+				ch = _activeChannelPtr;
+				ch->_pSrc = dest;
+				goto dispatch;
+			}
+
+			case 0x5: /* branch with return-address save */
+			{
+				byte *dest = &_soundData[readWord_impl()];
+				ch = _activeChannelPtr;
+				ch->_branchTargetPtr = ch->_pSrc + 3;
+				ch->_pSrc = dest;
+				goto dispatch;
+			}
+
+			case 0x6: /* return from branch */
+			{
+				ch = _activeChannelPtr;
+				if (ch->_branchTargetPtr != nullptr) {
+					ch->_pSrc = ch->_branchTargetPtr;
+					ch->_branchTargetPtr = nullptr;
+				} else {
+					ch->_pSrc++;
+				}
+				goto dispatch;
+			}
+
+			default:
+				/* Unknown sub-opcode: no _pSrc advance (def_10EAC). */
+				goto dispatch;
+			}
+		}
+
+		if (si == 0x20) {
+			/* ============================================================
+			 * opcodes3  (group 2, 0xA_)
+			 * Tempo/callback globals, random table operations,
+			 * indirect-table operations, call-by-address, nullsub.
+			 *
+			 * opcodes3 receives di (sub-opcode) but NOT the already-
+			 * incremented pSrc; each case increments pSrc itself as needed.
+			 * ============================================================ */
+			switch (di) {
+
+			case 0x0: /* random pick from packed table -> write to stream */
+			{
+				/* Format: [cmd] [tblSize] [entry0..N-1] [targetSlot]
+				 * pSrc points at the command byte on entry. */
+				pSrc++;
+				uint8  tblSize = *pSrc;
+				pSrc++;   /* now at entry[0] */
+				(void)getRandomNumber();
+				uint16 rnd = _randomSeed & 0x7FFF;
+				int16  idx = (int16)((int16)rnd % (int16)(uint16)tblSize);
+				uint8  chosen = *(pSrc + idx);
+				uint8  target = *(pSrc + tblSize);
+				*(pSrc + (uint16)target + 1) = chosen;
+				ch = _activeChannelPtr;
+				ch->_pSrc += (uint16)tblSize + 3;
+				goto dispatch;
+			}
+
+			case 0x1: /* random range [lo..hi] -> write to stream */
+			{
+				/* Format: [cmd] [lo] [hi] [targetSlot] ... */
+				pSrc++;
+				uint8 lo = *pSrc++;
+				uint8 hi = *pSrc++;
+				uint16 range = (uint16)(hi - lo) + 1;
+				/* pSrc now at targetSlot byte. */
+				(void)getRandomNumber();
+				uint16 rnd = _randomSeed & 0x7FFF;
+				uint8  res = (uint8)((int16)rnd % (int16)range) + lo;
+				uint8  slot = *pSrc;
+				*(pSrc + (uint16)slot + 1) = res;
+				ch = _activeChannelPtr;
+				ch->_pSrc += 4;   /* command+lo+hi+slot = 4 bytes consumed */
+				goto dispatch;
+			}
+
+			case 0x2: /* indirect table read from scriptVar -> write to stream */
+			{
+				/* Format: [cmd] [varIdx] [tableSlot] [table...] */
+				pSrc++;
+				uint8 varIdx = *pSrc++;
+				uint8 tableSlot = *pSrc++;
+				/* pSrc now points at the inline table. */
+				uint8 idxVal = _scriptVars[varIdx];
+				uint8 chosen = *(pSrc + idxVal);
+				uint8 target = *(pSrc + tableSlot);
+				*(pSrc + (uint16)target + 1) = chosen;
+				ch = _activeChannelPtr;
+				ch->_pSrc += (uint16)tableSlot + 4;
+				goto dispatch;
+			}
+
+			case 0x3: /* call function by address (near call in original) */
+			{
+				uint16 fnOffset = readWord_impl();
+				callFunction(fnOffset);
+				ch = _activeChannelPtr;
+				ch->_pSrc += 3;
+				goto dispatch;
+			}
+
+			case 0x4: /* nullsub_1 with byte arg */
+			{
+				/* "call near ptr aAsoundDriverAn+33h" - the target is a no-op. */
+				pSrc++;
+				/* (void)*pSrc; */
+				ch = _activeChannelPtr;
+				ch->_pSrc += 2;
+				goto dispatch;
+			}
+
+			case 0x5: /* advance _pSrc by 4 (from command byte) */
+			{
+				/* loc_10F55 is shared with case 1's epilogue: _pSrc += 4. */
+				ch = _activeChannelPtr;
+				ch->_pSrc += 4;
+				goto dispatch;
+			}
+
+			case 0x6: /* set word_124F2 (_tempoFineStep) */
+			{
+				pSrc++;
+				uint8 val = *pSrc;
+				_tempoFineStep = (uint16)val;
+				ch = _activeChannelPtr;
+				ch->_pSrc += 2;
+				goto dispatch;
+			}
+
+			case 0x7: /* set word_124F0 (_tempoCoarseStep) */
+			{
+				pSrc++;
+				uint8 val = *pSrc;
+				_tempoCoarseStep = (uint16)val;
+				ch = _activeChannelPtr;
+				ch->_pSrc += 2;
+				goto dispatch;
+			}
+
+			case 0x8: /* set word_124EE (_tempoPeriod), enable tick callback */
+			{
+				uint16 period = readWord_impl();
+				_tempoPeriod = period;
+				ch = _activeChannelPtr;
+				ch->_pSrc += 3;
+				_tempoEnabled = 1;
+				_tempoTickCounter = 1;
+				goto dispatch;
+			}
+
+			case 0x9: /* set word_124F4 (_tempoShift) */
+			{
+				pSrc++;
+				uint8 val = *pSrc;
+				_tempoShift = (uint16)val;
+				ch = _activeChannelPtr;
+				ch->_pSrc += 2;
+				goto dispatch;
+			}
+
+			default:
+				goto dispatch;
+			}
+		}
+
+		if (si == 0x30) {
+			/* ============================================================
+			 * opcodes4  (group 3, 0xB_)
+			 * Script-variable load (imm or var), store to stream, inc, dec.
+			 * Each case increments pSrc itself, then sets _pSrc.
+			 *
+			 * Switch on di (0-4); di >= 5 falls through to return.
+			 * ============================================================ */
+			switch (di) {
+
+			case 0x0: /* var[dst] = imm */
+			{
+				pSrc++;
+				uint8 dst = *pSrc++;
+				uint8 imm = *pSrc;
+				_scriptVars[dst] = imm;
+				ch = _activeChannelPtr;
+				ch->_pSrc += 3;
+				goto dispatch;
+			}
+
+			case 0x1: /* var[dst] = var[src] */
+			{
+				pSrc++;
+				uint8 dst = *pSrc++;
+				uint8 src = *pSrc;
+				_scriptVars[dst] = _scriptVars[src];
+				ch = _activeChannelPtr;
+				ch->_pSrc += 3;
+				goto dispatch;
+			}
+
+			case 0x2: /* stream[tableSlot+1] = var[src] */
+			{
+				/* asm: [bx+di+1] = cl, where bx=pSrc, di=tableSlot. */
+				pSrc++;
+				uint8 src = *pSrc++;
+				uint8 tableSlot = *pSrc;
+				*(pSrc + (uint16)tableSlot + 1) = _scriptVars[src];
+				ch = _activeChannelPtr;
+				ch->_pSrc += 3;
+				goto dispatch;
+			}
+
+			case 0x3: /* var[dst]++ */
+			{
+				pSrc++;
+				uint8 dst = *pSrc;
+				_scriptVars[dst]++;
+				ch = _activeChannelPtr;
+				ch->_pSrc += 2;
+				goto dispatch;
+			}
+
+			case 0x4: /* var[dst]-- */
+			{
+				pSrc++;
+				uint8 dst = *pSrc;
+				_scriptVars[dst]--;
+				ch = _activeChannelPtr;
+				ch->_pSrc += 2;
+				goto dispatch;
+			}
+
+			default:
+				goto dispatch;
+			}
+		}
+
+		if (si == 0x40) {
+			/* ============================================================
+			 * opcodes5  (group 4, 0xC_)
+			 * Script-variable ALU: add/sub/mul/div/rem/and/or/xor.
+			 *
+			 * Entry: inc pSrc, read dst (si), inc pSrc, read src (di).
+			 * Then _pSrc += 3 (command + dst + src).
+			 * 16 sub-opcodes (0-15).
+			 *
+			 * For sub-opcodes 0,2,4: src is an immediate (di from the byte).
+			 * For sub-opcodes 1,3,5: src is _scriptVars[di].
+			 * For 6-9: signed or unsigned division/remainder.
+			 * For 10-15: bitwise AND/OR/XOR, imm or var.
+			 * ============================================================ */
+			pSrc++;
+			uint8 dst = *pSrc++;
+			uint8 src = *pSrc;   /* immediate value or var index */
+			ch = _activeChannelPtr;
+			ch->_pSrc += 3;
+
+			switch (di) {
+			case 0x0: _scriptVars[dst] += src;                                       break;
+			case 0x1: _scriptVars[dst] += _scriptVars[src];                          break;
+			case 0x2: _scriptVars[dst] -= src;                                       break;
+			case 0x3: _scriptVars[dst] -= _scriptVars[src];                          break;
+			case 0x4: /* mul ax=di, mul [si] -> al stored */
+				_scriptVars[dst] = (uint8)((uint16)src * (uint16)_scriptVars[dst]);  break;
+			case 0x5:
+				_scriptVars[dst] = (uint8)((uint16)_scriptVars[src] * (uint16)_scriptVars[dst]); break;
+			case 0x6: /* signed idiv by imm -> quotient in al */
+				_scriptVars[dst] = (uint8)((int8)_scriptVars[dst] / (int8)src);      break;
+			case 0x7: /* unsigned div by var -> quotient in al */
+				_scriptVars[dst] = _scriptVars[dst] / _scriptVars[src];              break;
+			case 0x8: /* signed idiv by imm -> remainder in dl (stored to dst) */
+				_scriptVars[dst] = (uint8)((int8)_scriptVars[dst] % (int8)src);      break;
+			case 0x9: /* unsigned div by var -> remainder in ah (stored to dst) */
+				_scriptVars[dst] = _scriptVars[dst] % _scriptVars[src];              break;
+			case 0xA: _scriptVars[dst] &= src;                                       break;
+			case 0xB: _scriptVars[dst] &= _scriptVars[src];                          break;
+			case 0xC: _scriptVars[dst] |= src;                                       break;
+			case 0xD: _scriptVars[dst] |= _scriptVars[src];                          break;
+			case 0xE: _scriptVars[dst] ^= src;                                       break;
+			case 0xF: _scriptVars[dst] ^= _scriptVars[src];                          break;
+			default: break;
+			}
+			goto dispatch;
+		}
+
+		if (si == 0x50) {
+			/* ============================================================
+			 * opcodes6  (group 5, 0xD_)
+			 * Conditional branch: compare var[si] vs immediate (di as imm byte).
+			 *
+			 * Format: [cmd] [varIdx] [imm] [addrLo] [addrHi]
+			 * Entry: inc pSrc, read varIdx (si reg), inc pSrc, read imm (di reg).
+			 * Comparison: var[varIdx] vs imm.
+			 *
+			 * If condition met: call asound_readWord (reads addrLo/addrHi
+			 *   from current pSrc position), set _pSrc = &_soundData[addr].
+			 * If not met: _pSrc += 5 (command+varIdx+imm+addr word = 5 bytes).
+			 *
+			 * var_2 is used as a flag: 0 = branch taken, 1 = not taken.
+			 * (Confusingly, var_2 = 1 means "taken" in opcodes7 but
+			 *  0 means "taken" in opcodes6 - see def_11292 / def_11360 difference.)
+			 *
+			 * opcodes6: var_2=1 -> taken (call readWord, set pSrc),
+			 *            var_2=0 -> not taken (_pSrc += 5).
+			 * ============================================================ */
+			pSrc++;
+			uint8 varIdx = *pSrc++;
+			uint8 imm = *pSrc;
+			uint8 vval = _scriptVars[varIdx];
+			bool  taken = false;
+
+			switch (di) {
+			case 0x0: taken = ((uint16)vval == (uint16)imm);                    break; /* == */
+			case 0x1: taken = ((uint16)vval != (uint16)imm);                    break; /* != */
+			case 0x2: taken = ((int16)(uint16)vval >= (int16)(uint16)imm);      break; /* jge -> taken if >= */
+			case 0x3: taken = ((int16)(uint16)vval <= (int16)(uint16)imm);      break; /* jle -> taken if <= */
+			case 0x4: taken = (_scriptVars[varIdx] != _scriptVars[imm]);        break; /* jnz after cmp [di],[si] */
+			case 0x5: taken = (_scriptVars[varIdx] == _scriptVars[imm]);        break; /* jz  after cmp [di],[si] */
+			case 0x6: taken = (_scriptVars[imm] <= _scriptVars[varIdx]);        break; /* jbe -> [di]<=[si] taken */
+			case 0x7: taken = (_scriptVars[imm] >= _scriptVars[varIdx]);        break; /* jnb -> [di]>=[si] taken */
+			default:  break;
+			}
+
+			ch = _activeChannelPtr;
+			if (taken) {
+				ch->_pSrc = &_soundData[readWord_impl()];
+			} else {
+				ch->_pSrc += 5;
+			}
+			goto dispatch;
+		}
+
+		if (si == 0x60) {
+			/* ============================================================
+			 * opcodes7  (group 6, 0xE_)
+			 * Conditional branch: compare var[si] vs var[di] (both scriptVars).
+			 *
+			 * Format: [cmd] [varIdxA] [varIdxB] [addrLo] [addrHi]
+			 * Entry: inc pSrc, read varIdxA (si), inc pSrc, read varIdxB (di).
+			 * var_2 = 0 initially.
+			 *
+			 * If condition met: var_2 = 1 -> branch taken:
+			 *   _pSrc = pSrc + 5; _branchTargetPtr = pSrc + 5;
+			 *   call readWord -> _pSrc = &_soundData[addr].
+			 * If not met (var_2 = 0): _pSrc += 5 (skip condition + word).
+			 *
+			 * Note: unlike opcodes6, opcodes7 DOES save a branch-target
+			 * (_branchTargetPtr = _pSrc + 5) on the taken path.
+			 * ============================================================ */
+			pSrc++;
+			uint8 idxA = *pSrc++;
+			uint8 idxB = *pSrc;
+			uint8 va = _scriptVars[idxA];
+			uint8 vb = (uint8)idxB;   /* di byte used as an immediate in some cases... */
+
+			/* Actually in opcodes7 the layout is var vs var:
+			 * si = varIdxA, di = varIdxB (both are variable indices). */
+			vb = _scriptVars[idxB];
+
+			bool taken = false;
+			switch (di & 0x07) {
+			case 0x0: taken = ((uint16)va != (uint16)vb);  break; /* jnz after cmp ax,di (case 0: jz -> NOT taken if ==; var_2 stays 0; but then def path: var_2==0 -> skip. Wait - let me re-read.) */
+				/* Re-reading loc_112F0: cmp ax,di; jz loc_11352 (-> var_2=1=taken).
+				 * So case 0: taken = (va == vb). */
+			default: break;
+			}
+
+			/* Actually re-reading carefully:
+			 * case 0 (loc_112F0): cmp ax,di; jz -> loc_11352 (var_2=1, taken)
+			 *                      else -> loc_112FA (ax=0, var_2=0, not taken)
+			 * case 1 (loc_11302): cmp ax,di; jz -> loc_1130A -> (jz loc_112FA, not taken)
+			 *                     else -> loc_11284 (var_2=1, taken)
+			 * -> case 1: taken = (va != vb)
+			 * case 2 (loc_1130E): jge -> loc_112FA (not taken); else loc_11284 (taken)
+			 * -> taken = (va < vb) (signed)
+			 * case 3 (loc_1131A): jle -> not taken; else taken
+			 * -> taken = (va > vb) (signed)
+			 * case 4 (loc_11326): cmp [di],al; jnz -> loc_112FA (not taken); else loc_11284 (taken)
+			 * -> taken = (_scriptVars[idxB] == va) (already same as case 0 with vars swapped)
+			 * case 5 (loc_11332): cmp [di],al; jz->loc_1130A (not taken if ==, taken if !=)
+			 * -> taken = (_scriptVars[idxB] != va)
+			 * case 6 (loc_1133C): jbe -> not taken; else taken -> taken = (_scriptVars[idxB] > va)
+			 * case 7 (loc_11348): jnb -> not taken; else taken -> taken = (_scriptVars[idxB] < va)
+			 */
+			switch (di) {
+			case 0x0: taken = (va == vb);               break;
+			case 0x1: taken = (va != vb);               break;
+			case 0x2: taken = ((int8)va < (int8)vb);   break;
+			case 0x3: taken = ((int8)va > (int8)vb);   break;
+			case 0x4: taken = (vb == va);               break;
+			case 0x5: taken = (vb != va);               break;
+			case 0x6: taken = (vb > va);                break;
+			case 0x7: taken = (vb < va);                break;
+			default:  break;
+			}
+
+			ch = _activeChannelPtr;
+			if (taken) {
+				/* Save return target (pSrc+5 from the command byte). */
+				ch->_branchTargetPtr = ch->_pSrc + 5;
+				ch->_pSrc = &_soundData[readWord_impl()];
+			} else {
+				ch->_pSrc += 5;
+			}
+			goto dispatch;
+		}
+
+		/* Unknown group: skip. */
+		goto dispatch;
+	}
+
+note_event:
+	{
+		/* ---- Note event: [note][duration] ---- */
+		ch = _activeChannelPtr;
+		pSrc = ch->_pSrc;
+		ch->_note = *pSrc;
+		pSrc++;
+		ch->_activeCount = *pSrc;
+		pSrc++;
+		ch->_pSrc += 2;
+
+		ch = _activeChannelPtr;
+		if (ch->_note == 0 || ch->_activeCount == 0) {
+			updateOctave();
+			goto post_keyon;
+		}
+
+		/* ---- Key-on ---- */
+		ch = _activeChannelPtr;
+		if (ch->_durationOverride != 0)
+			ch->_keyOnDelay = ch->_durationOverride;
+		else
+			ch->_keyOnDelay = ch->_activeCount - ch->_noteOffset;
+		noteOn();
+	}
+
+post_keyon:
+	{
+		ch = _activeChannelPtr;
+
+		/* ---- Arpeggio counter (field_13 / _arpCounterReload) ---- */
+		if (ch->_arpCounterReload != 0) {
+			/* Decrement the arpeggio period counter (field_C / _arpPeriodCounter). */
+			ch->_arpPeriodCounter--;
+			ch = _activeChannelPtr;
+			if (ch->_arpPeriodCounter == 0) {
+				/* Reload from field_12 (_arpPeriodReload). */
+				ch->_arpPeriodCounter = ch->_arpPeriodReload;
+				/* Call sub_117E8 (writeArpeggio - writes the arpeggio frequency). */
+				writeArpeggio();
+			}
+			ch = _activeChannelPtr;
+			/* Decrement the repeat counter unless it is the infinite sentinel 0xFF. */
+			if (ch->_arpCounterReload != 0xFF)
+				ch->_arpCounterReload--;
+		}
+
+		/* ---- Write-volume pending (field_11 / _writeVolumePending) ---- */
+		ch = _activeChannelPtr;
+		if (ch->_writeVolumePending != 0) {
+			/* sub_11856 was already called by writeArpeggio above (or this is
+			 * a standalone field_11 set via opcode 8).  Clear the flag. */
+			writeArpeggio();
+			ch = _activeChannelPtr;
+			ch->_writeVolumePending = 0;
+		}
+
+		/* ---- Fade / volume step ---- */
+		ch = _activeChannelPtr;
+		if (ch->_fadePeriodCounter != 0) {
+			ch->_fadePeriodCounter--;
+			ch = _activeChannelPtr;
+			if (ch->_fadePeriodCounter == 0) {
+				ch->_fadePeriodCounter = ch->_fadePeriodReload;
+				ch = _activeChannelPtr;
+				if (ch->_volumeFadeStep != 0) {
+					if (ch->_pendingStop != 0) {
+						/* Pending-stop fade: step velocity and volume down. */
+						if (ch->_velocity > 0)
+							ch->_velocity += ch->_volumeFadeStep;
+						ch = _activeChannelPtr;
+						if (ch->_volume != 0)
+							ch->_volume += ch->_volumeFadeStep;
+					} else {
+						/* Normal fade: step velocity, clamp to [0, 0x7F]. */
+						if ((int8)ch->_volumeFadeStep > 0) {
+							ch->_velocity += ch->_volumeFadeStep;
+							ch = _activeChannelPtr;
+							if ((uint16)ch->_velocity > 0x7F)
+								ch->_velocity = 0x7F;
+						} else {
+							ch->_velocity += ch->_volumeFadeStep;
+							ch = _activeChannelPtr;
+							if ((int8)ch->_velocity < 0)
+								ch->_velocity = 0;
+						}
+					}
+					volDirty = true;   /* byte_16A0A = 1 */
+				}
+			}
+		}
+
+		/* ---- Vibrato ---- */
+		ch = _activeChannelPtr;
+		if (ch->_vibPeriodCounter != 0) {
+			ch->_vibPeriodCounter--;
+			ch = _activeChannelPtr;
+			if (ch->_vibPeriodCounter == 0) {
+				ch->_vibPeriodCounter = ch->_vibPeriodReload;
+				ch = _activeChannelPtr;
+
+				if ((int8)ch->_vibratoDepth > 0) {
+					/* Positive vibrato: ceiling at 0x7F. */
+					uint8 sum = ch->_vibratoDepth + ch->_patchAttenuation;
+					if (sum > 0x7F) {
+						if (ch->_vibratoMode == 0xFF) {
+							/* One-shot: clamp and stop. */
+							ch->_vibratoDepth = 0;
+							ch->_patchAttenuation = 0x7F;
+						} else if (sum == 0x80 && ch->_vibratoDepth != 1) {
+							/* Soft bounce at 0x80: patchAtten = 0x7F - vibratoDepth. */
+							ch->_patchAttenuation = 0x7F - ch->_vibratoDepth;
+						} else {
+							ch->_vibratoDepth = (uint8)(-(int8)ch->_vibratoDepth);
+						}
+					}
+					ch = _activeChannelPtr;
+					ch->_patchAttenuation += ch->_vibratoDepth;
+					volDirty = true;
+
+				} else if ((int8)ch->_vibratoDepth < 0) {
+					/* Negative vibrato: floor at 0. */
+					uint8 sum = ch->_vibratoDepth + ch->_patchAttenuation;
+					/* Note: sum is uint8 wrapping, but the asm uses "or al,al; jge"
+					 * treating the result as signed. */
+					if ((int8)sum < 0) {
+						if (ch->_vibratoMode == 0xFF) {
+							ch->_vibratoDepth = 0;
+							ch->_patchAttenuation = 0;
+						} else if (sum == 0xFF && ch->_vibratoDepth != 0xFF) {
+							/* Soft bounce: patchAtten = neg(vibratoDepth). */
+							ch->_patchAttenuation = (uint8)(-(int8)ch->_vibratoDepth);
+						} else {
+							ch->_vibratoDepth = (uint8)(-(int8)ch->_vibratoDepth);
+						}
+					}
+					ch = _activeChannelPtr;
+					ch->_patchAttenuation += ch->_vibratoDepth;
+					volDirty = true;
+				}
+				/* vibratoDepth == 0: nothing to do. */
+			}
+		}
+
+		if (volDirty)
+			writeVolume();
+	}
+
+	++_activeChannelNumber;
+}
+
+void ASound::playSound(int offset) {
+	findFreeChannelFull(loadData(offset));
+}
+
 } // namespace Dragonsphere
 } // namespace MADSV2
 } // namespace MADS
diff --git a/engines/mads/madsv2/dragonsphere/asound.h b/engines/mads/madsv2/dragonsphere/asound.h
index b33f2cf3187..e1ecf5f3f50 100644
--- a/engines/mads/madsv2/dragonsphere/asound.h
+++ b/engines/mads/madsv2/dragonsphere/asound.h
@@ -22,15 +22,625 @@
 #ifndef MADS_DRAGONSPHERE_ASOUND_H
 #define MADS_DRAGONSPHERE_ASOUND_H
 
-#include "mads/madsv2/phantom/asound.h"
+#include "common/mutex.h"
+#include "common/queue.h"
+#include "common/util.h"
+#include "mads/core/sound_manager.h"
 
 namespace MADS {
 namespace MADSV2 {
 namespace Dragonsphere {
 
-using Phantom::ASound;
-using Phantom::AdlibChannel;
-using Phantom::AdlibSample;
+#define ADLIB_CHANNEL_COUNT 9
+
+struct AdlibChannel {
+	static bool _isDisabled;
+
+	// ---- byte fields (offsets 0x00 - 0x13) --------------------------------
+
+	uint8  _activeCount = 0; // 0x00  duration tick countdown; 0 = channel idle
+	uint8  _pitchBend = 0; // 0x01  signed pitch-bend offset applied by writePitchBend
+	uint8  _volumeFadeStep = 0; // 0x02  signed per-period volume/velocity delta (0xFF = fade out)
+	uint8  _vibratoDepth = 0; // 0x03  signed LFO depth; negated each period to oscillate
+	uint8  _note = 0; // 0x04  MIDI note number for current event
+	uint8  _sampleIndex = 0; // 0x05  index into _samples[] selecting the OPL patch
+	uint8  _volume = 0; // 0x06  channel volume (0-127)
+	uint8  _noteOffset = 0; // 0x07  subtracted from _activeCount to shorten note duration
+	uint8  _durationOverride = 0; // 0x08  when non-zero, overrides the stream's duration byte
+	uint8  _keyOnDelay = 0; // 0x09  countdown before the OPL key-on bit is cleared (gate time)
+	uint8  _fadePeriodCounter = 0; // 0x0A  counts down to 0 before applying _volumeFadeStep
+	uint8  _fadePeriodReload = 0; // 0x0B  reload value for _fadePeriodCounter
+	uint8  _arpPeriodCounter = 0; // 0x0C  arpeggio tick counter (reloaded from _arpPeriodReload)
+	uint8  _vibPeriodCounter = 0; // 0x0D  LFO tick counter; when it reaches 0 a vibrato step fires
+	uint8  _vibPeriodReload = 0; // 0x0E  reload value for _vibPeriodCounter
+	uint8  _patchAttenuation = 0; // 0x0F  per-note attenuation offset added on top of the patch TL
+	uint8  _velocity = 0; // 0x10  note velocity (0-127); used together with _volume for TL
+	uint8  _writeVolumePending = 0; // 0x11  non-zero -> call sub_11856 (secondary freq write) this tick
+	uint8  _arpPeriodReload = 0; // 0x12  arpeggio period reload value (set by opcode 9)
+	uint8  _arpCounterReload = 0; // 0x13  arpeggio counter reload (decremented; 0xFF = infinite)
+
+	// ---- pointer fields (offsets 0x14 - 0x1F, word-sized in original) ----
+
+	byte *_loopStartPtr = nullptr; // 0x14  start of the current loop body
+	byte *_pSrc = nullptr; // 0x16  current read pointer into the sound-data stream
+	byte *_innerLoopPtr = nullptr; // 0x18  inner-loop restart address
+	byte *_outerLoopPtr = nullptr; // 0x1A  outer-loop restart address
+	byte *_soundData = nullptr; // 0x1C  base address of this channel's sound-data block
+	byte *_branchTargetPtr = nullptr; // 0x1E  target of the most recent branch/jump opcode
+
+	// ---- word counter fields (offsets 0x20 - 0x29) ----------------------
+
+	uint16 _innerLoopCount = 0; // 0x20  remaining inner-loop iterations (0 = infinite until opcode)
+	uint16 _outerLoopCount = 0; // 0x22  remaining outer-loop iterations (0 = infinite until opcode)
+	uint16 _noiseFreqMask = 0; // 0x24  AND mask applied to the random number in noise mode
+	uint16 _freqAccum = 0; // 0x26  frequency sweep accumulator (base frequency + swept offset)
+	uint16 _freqStep = 0; // 0x28  per-tick increment added to _freqAccum during a sweep
+
+	// ---- sweep / pause state (offsets 0x2A - 0x2D) ----------------------
+
+	// 0x2A-0x2B  Last computed OPL total-level bytes written by writeVolume.
+	//            lo byte (0x2A) = carrier TL nibble; hi byte (0x2B) = modulator TL nibble.
+	//            Written at the end of asound_writeVolume so that command7 (resume) can
+	//            restore operator levels without a full recalculation.
+	uint8  _cachedCarrierTL = 0; // 0x2A  cached carrier total-level (6-bit, 0 = loudest)
+	uint8  _savedFreqSweep = 0; // 0x2B  copy of _freqSweepCounter saved by command6 (pause)
+	uint8  _freqSweepCounter = 0; // 0x2C  countdown for frequency-sweep ticks; 0 = sweep done
+	uint8  _savedSweepCounter = 0; // 0x2D  second save slot: _freqSweepCounter is mirrored here
+	//       by command6/7 so command7 can restore the live value
+
+// ---- tuning / control (offsets 0x2E - 0x31) ------------------------
+
+	uint8  _transpose = 0; // 0x2E  semitone offset added when looking up _semitoneFreqTable
+	uint8  _octaveTranspose = 0; // 0x2F  octave shift: added to _note before octave calculation
+	uint8  _pendingStop = 0; // 0x30  0xFF = channel is fading out and will go idle when silent
+	uint8  _vibratoMode = 0; // 0x31  0xFF = one-shot vibrato (clamp & stop); else = continuous
+
+	/**
+	 * Zeroes the "live" fields of one channel without touching the
+	 * loop/sound-data pointers (those are handled separately by load).
+	 */
+	void reset();
+
+	/**
+	 * Clears the entire channel struct, then initialises all loop/source
+	 * pointers to the supplied sound-data address, sets _patchAttenuation to
+	 * 0x40 (maximum, silent), and marks the channel active (_activeCount = 1).
+	 */
+	void load(byte *soundData);
+
+	/**
+	 * Redirects _loopStartPtr and _pSrc to the null/silence stream and arms a
+	 * one-step volume fade (_volumeFadeStep = 0xFF, _fadePeriodReload/Counter = 1)
+	 * so the channel fades out gracefully over the next tick.
+	 */
+	void setPtr2(byte *ptr);
+
+	/**
+	 * Marks the channel pending-stop (_pendingStop = 0xFF) and redirects
+	 * _soundData to the null/silence stream.  Unlike setPtr2 this does not
+	 * touch _volumeFadeStep; the channel fades naturally via processChannelFade.
+	 */
+	void enable();
+
+	/**
+	 * Called every frame for each channel.  When _pendingStop is set, waits
+	 * until both _velocity and _volume are zero before marking the channel idle
+	 * (_activeCount = 0); otherwise applies _volumeFadeStep to velocity/volume
+	 * each _fadePeriodReload ticks and clamps the result to [0, 0x7F].
+	 */
+	void processChannelFade();
+};
+
+// ---------------------------------------------------------------------------
+// AdlibSample  (unchanged from Return of the Phantom, 0x16 bytes)
+// ---------------------------------------------------------------------------
+struct AdlibSample {
+	uint8  _attackRate = 0;
+	uint8  _decayRate = 0;
+	uint8  _sustainLevel = 0;
+	uint8  _releaseRate = 0;
+	uint8  _egTyp = 0;
+	uint8  _ksr = 0;
+	uint8  _totalLevel = 0;
+	uint8  _scalingLevel = 0;
+	uint8  _waveformSelect = 0;
+	uint8  _freqMultiple = 0;
+	uint8  _feedback = 0;
+	uint8  _ampMod = 0;
+	uint8  _vib = 0;
+	uint8  _alg = 0;
+	uint8  _freqSweepInit = 0; // initial _freqSweepCounter value; 0 = immediate key-on
+	uint8  _reserved = 0;
+	uint16 _freqMask = 0; // loaded into channel _noiseFreqMask
+	uint16 _freqBase = 0; // loaded into channel _freqAccum
+	uint16 _outerLoopPtr = 0; // loaded into channel _freqStep
+
+	AdlibSample() {
+	}
+	AdlibSample(Common::SeekableReadStream &s);
+};
+
+// ---------------------------------------------------------------------------
+// ASound  -  Dragonsphere Adlib sound driver base class
+//
+// Command dispatch table layout (from off_11A14 / off_11A26 / off_11A2E /
+// funcs_12251 / off_11A64 in the disassembly):
+//
+//   Table 1  off_11A14  commands  0- 8   (max=8,    base=0,    9 entries)
+//   Table 2  off_11A26  commands 16-19   (max=0x13, base=0x10, 4 entries)
+//             command16  = background-music dispatcher (calls command18)
+//             command17  = play specific piece (loads 7 channels direct)
+//             command18  = re-entrant music launcher (reads word_12370 to pick a sub-command)
+//             command19  = no-op (asound_command98)
+//   Table 3  off_11A2E  commands 24-32   (max=0x20, base=0x18, 9 entries)
+//   Table 4  funcs_12251 commands 32-49  (max=0x31, base=0x20, 18 entries)
+//             Includes asound_command32-48 (music pieces / SFX loaders)
+//             and asound_command98 (no-op) in the last slot.
+//   Table 5  off_11A64  commands 64-101  (max=0x65, base=0x40, 38 entries)
+//             Includes asound_command64-101 (single-shot SFX loaders via
+//             findFreeChannel / findFreeChannelFull) and two no-ops.
+//
+// The driver also exposes:
+//   asound_command90 / 91  - two-voice SFX (findFreeChannelFull x2)
+//   asound_command95       - four-voice music piece (findFreeChannel x4)
+//   sub_11F98              - two-voice SFX (findFreeChannelFull x2)
+//
+// word_12370 tracks the "current music index" used by command18 to select
+// which music-piece loader to call.
+// ---------------------------------------------------------------------------
+class ASound : public SoundDriver {
+protected:
+	/** Member-function pointer type for deferred sound-loader callbacks. */
+	typedef void (ASound::*CallbackFunction)();
+
+private:
+	// ---- callback / tick state ------------------------------------------
+	uint16 _callbackCounter = 0;  // per-tick countdown
+	uint16 _callbackPeriod = 0;  // reload value; 0 = callback disabled
+	// Pointer to the deferred sound-loader called when the
+	// counter fires (stored as uint16 in the original; typed as a member
+	// function pointer in the C++ port).
+	CallbackFunction _callbackFnPtr = nullptr;
+
+	// ---- active-channel context (set by pollAllChannels) ----------------
+	AdlibChannel *_activeChannelPtr = nullptr;
+	uint8          _activeChannelNumber = 0;
+	uint16         _activeChannelReg = 0; // 0xA0+ch register for the active channel
+	uint16         _currentOpBase = 0; // operator register base for the active channel
+
+	// ---- per-frame working state ----------------------------------------
+	AdlibSample *_samplePtr = nullptr; // patch being loaded/written
+	byte *pSrc = nullptr; // current read pointer (mirrors channel _pSrc)
+	int16          _pollResult = 0;
+	int16          _resultFlag = 0;
+	uint16         _randomSeed = 0x4D2;
+
+	// ---- driver-wide flags ----------------------------------------------
+	uint16 _isDisabled = 0; // non-zero while the engine is paused (command6)
+	uint8  _findChannelMode = 0; // 0=full search, 1=ch0-5 only, 2=ch6-8 then pending
+
+	// ---- per-channel sweep shadows (for channel 5 special-casing) -------
+	//      Not present as separate globals in Dragonsphere; the channel
+	//      struct fields _savedFreqSweep / _savedSweepCounter handle this.
+
+	// ---- misc globals ---------------------------------------------------
+	uint8  _anySweepActive = 0; // set to 1 if any channel has _freqSweepCounter > 0
+	int    _frameNumber2 = 0; // secondary frame counter incremented every update
+
+	// ---- script / sequencer registers -----------------------------------
+	uint8  _scriptVars[32] = {}; // byte_16A10: 32 general-purpose script registers
+
+	// ---- music-index tracker (word_12370) --------------------------------
+	// Tracks which music piece was last launched by command18.
+	uint16 _musicIndex = 0;
+
+	// ---- tempo / sequencer state (from opcodes3 group 2 handlers) --------
+	uint8  _musicOnlyFlag = 0;     // byte_12393: 1=music-only check, 0=all channels
+	uint16 _tempoFineStep = 0;     // word_124F2: fine tempo step (opcode A6)
+	uint16 _tempoCoarseStep = 0;   // word_124F0: coarse tempo step (opcode A7)
+	uint16 _tempoPeriod = 0;       // word_124EE: tempo period in ticks (opcode A8)
+	uint8  _tempoEnabled = 0;      // non-zero when tempo tick is active (opcode A8)
+	uint16 _tempoTickCounter = 0;  // countdown for tempo callback
+	uint16 _tempoShift = 0;        // word_124F4: tempo shift (opcode A9)
+
+	// =========================================================================
+	// Private helpers
+	// =========================================================================
+
+	/** Timer callback entry point; calls update(). */
+	void onTimer();
+
+	/** Zeros _callbackFnPtr, _callbackCounter, and _callbackPeriod. */
+	void clearCallback();
+	void resetCallback() {
+		clearCallback();
+	}
+
+	/**
+	 * Forces one OPL operator to maximum attenuation (total-level = 0x3F)
+	 * while preserving its upper KSL bits.  'portIndex' is the register index
+	 * into _adlibPorts (i.e. the 0x40-range operator register).
+	 * Used by command6 to mute all operator pairs during a pause.
+	 */
+	void adlib_channelOff(uint8 portIndex);
+
+	/**
+	 * Computes and writes the total-level registers for the active channel
+	 * (asound_writeVolume).
+	 *
+	 * Two top-level paths depending on the _alg field of the first sample:
+	 *   _alg == 0  (FM): both operators contribute; loop twice writing
+	 *              VOICE_SLOTS[ch][0] then VOICE_SLOTS[ch][1].
+	 *   _alg != 0  (additive): only the carrier (slot 1) carries volume.
+	 *
+	 * Within each pass, two inner paths based on OPL version:
+	 *   < 0x18  (OPL2): TL = clamp(0x3F - var_2, 0, 0x3F).
+	 *   >= 0x18 (OPL3): patch-attenuation-aware mapping using
+	 *              PATCH_ATTEN_TO_TL and VOL_VEL_TO_ATTEN_STEP tables;
+	 *              writes two registers (offset 0 and offset 2).
+	 *
+	 * Caches the two written TL bytes in _cachedCarrierTL / _savedFreqSweep
+	 * so command7 can restore levels without a full recalculation.
+	 */
+	void writeVolume();
+
+	/**
+	 * Derives the OPL F-number and block (octave) from _note, _octaveTranspose,
+	 * and _transpose using _semitoneFreqTable, then writes registers 0xA0+ch
+	 * and 0xB0+ch (with key-on bit set).
+	 */
+	void writeFrequency();
+
+	/**
+	 * Applies _pitchBend as a signed offset to the already-computed frequency
+	 * registers without a full note recalculation.
+	 */
+	void writePitchBend();
+
+	/**
+	 * Arpeggio frequency write (sub_11856).
+	 * Called from pollActiveChannel when _writeVolumePending (field_11) is set.
+	 * Computes a modified frequency from _note + _octaveTranspose + field_11 - 1
+	 * and writes it to the OPL registers, preserving the key-on bit.
+	 */
+	void writeArpeggio();
+
+	/**
+	 * Clears the key-on bit (bit 5) of the 0xB0+ch register, silencing the
+	 * note while preserving pitch.  Called during _keyOnDelay countdown.
+	 */
+	void updateOctave();
+
+	/**
+	 * Arms the frequency sweep counter from the sample definition, then - if
+	 * _freqSweepCounter is zero - immediately triggers the note by writing
+	 * volume and frequency registers and setting the key-on bit.
+	 */
+	void noteOn();
+
+	/**
+	 * Writes all OPL operator registers for the patch pointed to by _samplePtr,
+	 * using _currentOpBase as the operator base.  Covers ADSR, KSL/TL,
+	 * feedback/algorithm, AM/VIB flags, and waveform-select registers.
+	 */
+	void writeSampleRegs();
+
+	/**
+	 * Loads all OPL registers for the patch assigned to the active channel,
+	 * covering both modulator and carrier operators, and sets up the channel's
+	 * sweep/frequency state from the AdlibSample definition.
+	 */
+	void loadSample();
+
+	/**
+	 * Main per-frame update: increments frame counters, polls all channels,
+	 * fires the tick callback, calls updateAllChannels, then runs the per-channel
+	 * frequency-sweep tick for all 9 voices.
+	 */
+	void update();
+
+	/**
+	 * Per-channel frequency-sweep tick for channel 'channelIndex' (0-based).
+	 * When _freqSweepCounter > 0: adds _freqStep to _freqAccum, decrements the
+	 * counter, and - when it reaches zero - zeroes the voice's frequency registers.
+	 * Sets _anySweepActive when the counter is still non-zero.
+	 */
+	void update1(int channelIndex);
+
+	/**
+	 * Writes a frequency value directly to the OPL registers for 'voice'.
+	 * The high byte selects the block/octave; the low byte is the F-number LSB.
+	 */
+	void setFrequency(uint8 voice, uint16 freq);
+
+	/**
+	 * Noise-channel inner tick: picks a random frequency offset masked by
+	 * _noiseFreqMask, adds _freqAccum, and writes the result to the voice
+	 * frequency registers.
+	 */
+	void noise_inner(int channelIndex);
+
+	/**
+	 * Calls processChannelFade for all 9 channels each frame.
+	 */
+	void updateAllChannels();
+
+	/**
+	 * Advances pSrc by one, reads two bytes little-endian, and returns the word.
+	 * Used by the bytecode opcodes that take 16-bit operands.
+	 */
+	uint16 readWord_impl();
+
+	/**
+	 * Deferred-callback tick: decrements _callbackCounter; when it reaches zero,
+	 * reloads it from _callbackPeriod and calls _callbackFnPtr (if non-null),
+	 * then clears _callbackFnPtr so it fires exactly once.
+	 */
+	void tickCallback();
+
+protected:
+	// ---- nine AdlibChannel instances ------------------------------------
+	AdlibChannel _channel0, _channel1, _channel2;
+	AdlibChannel _channel3, _channel4, _channel5;
+	AdlibChannel _channel6, _channel7, _channel8;
+	AdlibChannel *_channels[ADLIB_CHANNEL_COUNT] = {
+		&_channel0, &_channel1, &_channel2,
+		&_channel3, &_channel4, &_channel5,
+		&_channel6, &_channel7, &_channel8
+	};
+
+	uint8  _adlibPorts[0x100] = { 0 };
+	Common::Array<AdlibSample> _samples;
+
+protected:
+	// =========================================================================
+	// Protected helpers  (used by ASound1–ASound9)
+	// =========================================================================
+
+	/**
+	 * Checks whether any of channels 0-6 (or 0-8 when _musicOnlyFlag is clear)
+	 * have a non-zero _activeCount.  Returns non-zero if sound is playing.
+	 * This is 'sub_1061A' in the disassembly.
+	 */
+	int isMusicChannelsActive();
+
+	/**
+	 * Like isMusicChannelsActive but scans all 9 channels unconditionally
+	 * (clears the ch0-6-only flag first).  This is 'sub_1064E'.
+	 */
+	int isAnyChannelActive();
+
+	/**
+	 * Schedule fn as the next deferred-load callback.
+	 * Does NOT touch _callbackCounter or _callbackPeriod — those are preserved
+	 * from the previous loader so the callback fires on the right beat.
+	 * Cast the derived-class member-function pointer with reinterpret_cast.
+	 */
+	void scheduleCallback(CallbackFunction fn) { _callbackFnPtr = fn; }
+
+	/**
+	 * Arm the periodic timer and clear any pending callback pointer.
+	 * Call at the head of every immediate-load function (symmetric counter/period).
+	 */
+	void resetCallbackTimer(uint16 period) {
+		_callbackFnPtr = nullptr;
+		_callbackCounter = period;
+		_callbackPeriod = period;
+	}
+
+	/**
+	 * Arm the periodic timer with separate counter and period values.
+	 * Used by command44 which sets counter=0x60 but period=0xE0.
+	 */
+	void resetCallbackTimerEx(uint16 counter, uint16 period) {
+		_callbackFnPtr = nullptr;
+		_callbackCounter = counter;
+		_callbackPeriod = period;
+	}
+
+	/** Set the music-piece index (word_12370) read by command18. */
+	void setMusicIndex(uint16 idx) { _musicIndex = idx; }
+	/** Read the current music-piece index. */
+	uint16 getMusicIndex() const { return _musicIndex; }
+
+	/** Write one script-variable register (byte_16A10[idx]). */
+	void setScriptVar(int idx, uint8 val) { _scriptVars[idx] = val; }
+	/**
+	 * Writes (reg, value) to the OPL chip and updates the _adlibPorts shadow
+	 * array so subsequent reads return the last-written value.
+	 */
+	void write(uint8 reg, uint8 value);
+
+	/** Updates and returns _randomSeed using a simple linear-feedback shift. */
+	uint16 getRandomNumber();
+
+	/**
+	 * Restores one OPL operator by re-writing the value already stored in the
+	 * _adlibPorts shadow array.  command7 uses this to resume playback after
+	 * a command6 pause without recalculating any TL values.
+	 * 'portIndex' is the operator register index into _adlibPorts.
+	 */
+	void adlib_channelOn(uint8 portIndex);
+
+	/** Sets _pollResult and _resultFlag to indicate that sound is playing. */
+	void signalSoundPlaying();
+
+	/**
+	 * Iterates over all 9 channels, sets _activeChannelPtr / _activeChannelNumber,
+	 * and calls pollActiveChannel for each one.
+	 */
+	void pollAllChannels();
+
+	/**
+	 * Per-channel bytecode interpreter, called once per frame per active channel.
+	 *
+	 * Sound data is a sequence of (note, duration) byte pairs plus command bytes
+	 * with the high bit set (0x80-0xFF).  The upper nibble of a command byte
+	 * (after masking out the high bit) selects one of seven opcode groups:
+	 *
+	 *   0x0_  -> opcodes1  (patch/velocity/volume/vibrato/transpose/arpeggio)
+	 *   0x1_  -> opcodes2  (inner/outer loop control, restart, branch/call)
+	 *   0x2_  -> opcodes3  (tempo, script-variable arithmetic, call-by-address)
+	 *   0x3_  -> opcodes4  (script-variable load/store/copy/inc/dec)
+	 *   0x4_  -> opcodes5  (script-variable ALU: add/sub/mul/div with imm or var)
+	 *   0x5_  -> opcodes6  (extended: random-range, indexed table read/write)
+	 *   0x6_  -> opcodes7  (driver-level calls: command dispatch, etc.)
+	 *
+	 * The lower nibble is passed as the sub-opcode to each group handler.
+	 * Note bytes (high bit clear) consume one duration tick per call.
+	 */
+	void pollActiveChannel();
+
+	/** Returns true if the sound data block at 'ptr' is already playing. */
+	bool isSoundActive(byte *ptr) const;
+
+	/**
+	 * Scans channels 0-6 for an empty slot (_activeCount == 0) and calls
+	 * load() on the first one found.  Falls through to findFreeChannelFull
+	 * for channels 6-8 when _findChannelMode != 1.
+	 */
+	void findFreeChannel(byte *soundData);
+
+	/**
+	 * Extends the search to channels 7-8, then checks for pending-stop
+	 * channels (which can be pre-empted), working in reverse priority order
+	 * (ch8, ch7, ch6 ... ch0).
+	 */
+	void findFreeChannelFull(byte *soundData);
+
+	/** Returns a pointer to the sound data at the given offset. */
+	byte *loadData(int offset) {
+		return &_soundData[offset];
+	}
+
+protected:
+	// =========================================================================
+	// Core commands (0-8)  -  identical in purpose to Return of the Phantom
+	// =========================================================================
+
+	/**
+	 * command0: Full hardware reset.
+	 *   1. Reset all 9 channels.
+	 *   2. Mute all operator TL registers (0x40-0x55) to 0x3F.
+	 *   3. Zero remaining operator registers (0x60-0xFF and 0x01-0x3F).
+	 *   4. Write Waveform Select Enable (register 0x01 = 0x20).
+	 *   5. Reset the tick callback.
+	 */
+	int command0();
+
+	/**
+	 * command1: Fade out all channels.
+	 *   Calls command3 (fade music channels 0-6) then command5 (fade SFX 7-8).
+	 */
+	int command1();
+
+	/**
+	 * command2: Fade out music channels 0-6.
+	 *   Calls AdlibChannel::setPtr2 on each, redirecting to the null stream
+	 *   and arming a one-step fade.
+	 */
+	int command2();
+
+	/**
+	 * command3: Fade out music channels 0-6 with pending-stop.
+	 *   Calls AdlibChannel::enable on channels 0-6.
+	 */
+	int command3();
+
+	/**
+	 * command4: Fade out SFX channels 7-8.
+	 *   Calls AdlibChannel::setPtr2 on channels 7 and 8.
+	 */
+	int command4();
+
+	/**
+	 * command5: Stop SFX channels 7-8 with pending-stop flag.
+	 *   Calls AdlibChannel::enable on channels 7 and 8, letting each channel
+	 *   finish its current OPL envelope before going idle.
+	 */
+	int command5();
+
+	/**
+	 * command6: Pause playback.
+	 *   Saves each channel's _freqSweepCounter into _savedSweepCounter, zeroes
+	 *   _freqSweepCounter on all channels, then mutes all 22 operator TL
+	 *   registers (the byte_1239B table covers all operator slots 0x40-0x55).
+	 *   Sets _isDisabled to prevent further updates.
+	 */
+	int command6();
+
+	/**
+	 * command7: Resume playback.
+	 *   Restores operator volumes for all channels from _adlibPorts shadow,
+	 *   copies _savedSweepCounter back to _freqSweepCounter on all channels,
+	 *   signals sound playing if any channel was active, then clears _isDisabled.
+	 */
+	int command7();
+
+	/**
+	 * command8: Returns non-zero if any of the 9 channels has a non-zero
+	 *   _activeCount (i.e. sound is currently playing).
+	 *   Also clears the music-only flag (byte_12393 = 0) so the check covers
+	 *   all 9 channels.
+	 */
+	int command8();
+
+	/**
+	 * Calls a function at a fixed offset within the sound driver.
+	 * @param offset		Offset of the function
+	 */
+	virtual void callFunction(uint16 offset);
+
+	// =========================================================================
+	// Music-index launcher (called via command18)
+	// =========================================================================
+
+	/**
+	 * command18: Re-entrant music launcher.
+	 *   First calls command1 to fade current output, then branches on _musicIndex
+	 *   (word_12370):
+	 *     <= 0x12  -> calls off_11A26 table (commands 16-19)
+	 *     > 0x12  -> calls funcs_12251 table (commands 32-49), index = _musicIndex - 0x20
+	 */
+	int command18();
+
+public:
+	/**
+	 * Constructor.
+	 * @param mixer       Mixer instance
+	 * @param opl         OPL chip instance
+	 * @param filename    Path to the .DR1 (or equivalent) sound-driver file
+	 * @param dataOffset  Offset in the file of the data segment
+	 * @param dataSize    Size of the data segment
+	 */
+	ASound(Audio::Mixer *mixer, OPL::OPL *opl, const Common::Path &filename,
+		int dataOffset, int dataSize);
+
+	~ASound() override {
+	}
+
+	/** Stop all currently playing sounds (wraps command0). */
+	virtual int stop() override;
+
+	/** Main poll method; drives the per-frame update. */
+	int poll() override;
+
+	/**
+	 * Noise channel tick: for each of the 9 channels calls noise_inner,
+	 * which randomises the voice frequency each frame using _noiseFreqMask.
+	 */
+	void noise() override;
+
+	/**
+	 * Starts playback of the sound data at the given byte offset within the
+	 * driver's data segment, using findFreeChannelFull to select a channel.
+	 */
+	void playSound(int offset);
+
+	void setVolume(int volume) override {
+		// TODO: implement if needed
+	}
+};
 
 } // namespace Dragonsphere
 } // namespace MADSV2
diff --git a/engines/mads/madsv2/dragonsphere/sound_dragonsphere.cpp b/engines/mads/madsv2/dragonsphere/sound_dragonsphere.cpp
index d4ddedd0b02..c6e9a011c2d 100644
--- a/engines/mads/madsv2/dragonsphere/sound_dragonsphere.cpp
+++ b/engines/mads/madsv2/dragonsphere/sound_dragonsphere.cpp
@@ -89,318 +89,704 @@ void DragonSoundManager::loadDriver(int sectionNumber) {
 /* ASound1  (asound.dr1)                                                  *
  *-----------------------------------------------------------------------*/
 
-const ASound1::CommandPtr ASound1::_commandList[40] = {
-	&ASound1::command0,  &ASound1::command1,  &ASound1::command2,  &ASound1::command3,
-	&ASound1::command4,  &ASound1::command5,  &ASound1::command6,  &ASound1::command7,
-	&ASound1::command8,  nullptr,             nullptr,             nullptr,
-	nullptr,             nullptr,             nullptr,             nullptr,
-	&ASound1::command16, nullptr,             nullptr,             nullptr,
-	nullptr,             nullptr,             nullptr,             nullptr,
-	&ASound1::command24, &ASound1::command25, &ASound1::command26, &ASound1::command27,
-	nullptr,             nullptr,             nullptr,             nullptr,
-	&ASound1::command32, &ASound1::command33, &ASound1::command34, &ASound1::command35,
-	&ASound1::command36, &ASound1::command37, &ASound1::command38, &ASound1::command39
+// Cast a derived-class void loader to the base CallbackFunction type.
+// Safe under single inheritance: same function address, only static type changes.
+#define MAKE_CALLBACK(cls, fn) \
+	reinterpret_cast<ASound::CallbackFunction>(&cls::fn)
+
+const ASound1::CommandPtr ASound1::_commandList[102] = {
+	// commands 0-8  (off_11A14)
+	&ASound1::command0,   &ASound1::command1,   &ASound1::command2,   &ASound1::command3,
+	&ASound1::command4,   &ASound1::command5,   &ASound1::command6,   &ASound1::command7,
+	&ASound1::command8,
+	// 9-15 absent
+	nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+	// commands 16-18, 19=no-op  (off_11A26)
+	&ASound1::command16,  &ASound1::command17,  &ASound1::command18,  nullptr,
+	// 20-23 absent
+	nullptr, nullptr, nullptr, nullptr,
+	// commands 24-31, then no-op for slot 32  (off_11A2E)
+	&ASound1::command24,  &ASound1::command25,  &ASound1::command26,  &ASound1::command27,
+	&ASound1::command28,  &ASound1::command29,  &ASound1::command30,  &ASound1::command31,
+	// commands 32-48, 49=no-op  (funcs_12251)
+	&ASound1::command32,  &ASound1::command33,  &ASound1::command34,  &ASound1::command35,
+	&ASound1::command36,  &ASound1::command37,  &ASound1::command38,  &ASound1::command39,
+	&ASound1::command40,  &ASound1::command41,  &ASound1::command42,  &ASound1::command43,
+	&ASound1::command44,  &ASound1::command45,  &ASound1::command46,  &ASound1::command47,
+	&ASound1::command48,  nullptr,
+	// 50-63 absent
+	nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+	nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+	// commands 64-101  (off_11A64); slot 92 and slot 98 are no-ops
+	&ASound1::command64,  &ASound1::command65,  &ASound1::command66,  &ASound1::command67,
+	&ASound1::command68,  &ASound1::command69,  &ASound1::command70,  &ASound1::command71,
+	&ASound1::command72,  &ASound1::command73,  &ASound1::command74,  &ASound1::command75,
+	&ASound1::command76,  &ASound1::command77,  &ASound1::command78,  &ASound1::command79,
+	&ASound1::command80,  &ASound1::command81,  &ASound1::command82,  &ASound1::command83,
+	&ASound1::command84,  &ASound1::command85,  &ASound1::command86,  &ASound1::command87,
+	&ASound1::command88,  &ASound1::command89,  &ASound1::command90,  &ASound1::command91,
+	nullptr,              &ASound1::command93,  &ASound1::command94,  &ASound1::command95,
+	&ASound1::command96,  &ASound1::command97,  nullptr,              &ASound1::command99,
+	&ASound1::command100, &ASound1::command101
 };
 
 ASound1::ASound1(Audio::Mixer *mixer, OPL::OPL *opl)
 		: ASound(mixer, opl, "asound.dr1", 0x2520, 0x49e0) {
-	// Load sound samples
 	auto samplesStream = getDataStream(0x1dc);
 	for (int i = 0; i < 182; ++i)
 		_samples.push_back(AdlibSample(samplesStream));
 }
 
 int ASound1::command(int commandId, int param) {
-	if (commandId > 39 || !_commandList[commandId])
+	if (commandId > 101 || !_commandList[commandId])
 		return 0;
-	
 	return (this->*_commandList[commandId])();
 }
 
 // commands 0-8: delegate to base ASound
-int ASound1::command0() { return ASound::command0(); }
-int ASound1::command1() { return ASound::command1(); }
-int ASound1::command2() { return ASound::command2(); }
-int ASound1::command3() { return ASound::command3(); }
-int ASound1::command4() { return ASound::command4(); }
-int ASound1::command5() { return ASound::command5(); }
-int ASound1::command6() { return ASound::command6(); }
-int ASound1::command7() { return ASound::command7(); }
-int ASound1::command8() { return ASound::command8(); }
-
-int ASound1::commandMusic0() {
-	ASound::command1();
-	_channels[0]->load(loadData(0x1ECA));
-	_channels[1]->load(loadData(0x1FBF));
-	_channels[2]->load(loadData(0x2037));
-	_channels[3]->load(loadData(0x20EE));
-	_channels[4]->load(loadData(0x219B));
-	_channels[5]->load(loadData(0x21AF));
-	return 0;
-}
+int ASound1::command0()  { return ASound::command0(); }
+int ASound1::command1()  { return ASound::command1(); }
+int ASound1::command2()  { return ASound::command2(); }
+int ASound1::command3()  { return ASound::command3(); }
+int ASound1::command4()  { return ASound::command4(); }
+int ASound1::command5()  { return ASound::command5(); }
+int ASound1::command6()  { return ASound::command6(); }
+int ASound1::command7()  { return ASound::command7(); }
+int ASound1::command8()  { return ASound::command8(); }
 
-int ASound1::commandMusic1() {
+// ---------------------------------------------------------------------------
+// command16 - music piece A (castle interior theme)
+// isSoundActive guard + isMusicChannelsActive deferred-callback pattern.
+// counter = period = 0x90; sets musicIndex = 0x10.
+// ---------------------------------------------------------------------------
+void ASound1::loadCommand16() {
+	resetCallbackTimer(0x90);
+	setMusicIndex(0x10);
 	ASound::command1();
-	_channels[0]->load(loadData(0x3418));
-	_channels[1]->load(loadData(0x34EB));
-	_channels[2]->load(loadData(0x359B));
-	_channels[3]->load(loadData(0x3658));
-	_channels[4]->load(loadData(0x3667));
-	_channels[5]->load(loadData(0x3677));
-	return 0;
+	_channels[0]->load(loadData(0x262C));
+	_channels[1]->load(loadData(0x26DA));
+	_channels[2]->load(loadData(0x2752));
+	_channels[3]->load(loadData(0x27CE));
+	_channels[4]->load(loadData(0x2826));
+	_channels[5]->load(loadData(0x284D));
+	_channels[6]->load(loadData(0x286D));
 }
 
-int ASound1::commandMusic2() {
-	ASound::command1();
-	_channels[0]->load(loadData(0x3688));
-	_channels[1]->load(loadData(0x387B));
-	_channels[2]->load(loadData(0x3A01));
-	_channels[3]->load(loadData(0x3BC6));
-	_channels[4]->load(loadData(0x3D31));
-	_channels[5]->load(loadData(0x3D41));
+int ASound1::command16() {
+	byte *pData = loadData(0x262C);
+	if (!isSoundActive(pData)) {
+		if (!isMusicChannelsActive())
+			loadCommand16();
+		else
+			scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand16));
+	}
 	return 0;
 }
 
-int ASound1::commandMusic3() {
+// ---------------------------------------------------------------------------
+// command17 - special music piece (7 channels)
+// Uses isAnyChannelActive: if any channel busy, skip entirely.
+// No isSoundActive guard; no deferred callback.
+// counter = period = 0x60; no musicIndex update.
+// ---------------------------------------------------------------------------
+int ASound1::command17() {
+	if (isAnyChannelActive())
+		return 0;
+	resetCallbackTimer(0x60);
 	ASound::command1();
-	_channels[0]->load(loadData(0x3D52));
-	_channels[1]->load(loadData(0x3FD3));
-	_channels[2]->load(loadData(0x41FF));
-	_channels[3]->load(loadData(0x420C));
-	_channels[4]->load(loadData(0x4219));
-	_channels[5]->load(loadData(0x4229));
+	_channels[0]->load(loadData(0x43CE));
+	_channels[1]->load(loadData(0x4403));
+	_channels[2]->load(loadData(0x4436));
+	_channels[3]->load(loadData(0x444F));
+	_channels[4]->load(loadData(0x4482));
+	_channels[5]->load(loadData(0x4490));
+	_channels[6]->load(loadData(0x44A8));
 	return 0;
 }
 
 // ---------------------------------------------------------------------------
-// command16 - random background music
-//
-// If channel 0 is active and already playing one of the five known music
-// pieces (identified by their starting offset in field_17), leave it alone.
-// Otherwise pick a piece at random: the original uses getRandomNumber() & 7,
-// discarding 0 and indexing a four-entry table repeated twice (entries 1-3
-// -> pieces 1-3, entries 4-7 -> same pieces again with entry 4 wrapping to
-// piece 0).  We reproduce this with a modulo-4 on a non-zero value.
+// command18 - re-entrant music launcher (delegate to base ASound::command18)
 // ---------------------------------------------------------------------------
-int ASound1::command16() {
-	if (_channels[0]->_activeCount) {
-		// Special offset checks
-		int f = _channels[0]->_loopStartPtr - &_soundData[0];
-
-		if (f == 0 || f == 0x1ECA || f == 0x21C4 ||
-		    f == 0x3418 || f == 0x3688 || f == 0x3D52)
-			return 0;
-	}
-
-	int idx;
-	do {
-		idx = getRandomNumber() & 3;
-	} while (idx == 0);
-	_musicIndex = idx;
-
-	typedef int (ASound1::*MusicPtr)();
-	static const MusicPtr musicTable[4] = {
-		&ASound1::commandMusic0,
-		&ASound1::commandMusic1,
-		&ASound1::commandMusic2,
-		&ASound1::commandMusic3
-	};
-	return (this->*musicTable[idx])();
-}
+int ASound1::command18() { return ASound::command18(); }
 
 // ---------------------------------------------------------------------------
-// commands 24-27 - upper channel pool
+// commands 24-31 - sound effects via findFreeChannelFull
 // ---------------------------------------------------------------------------
-
 int ASound1::command24() {
-	playSound(0x173A);
-	playSound(0x176D);
+	findFreeChannelFull(loadData(0x44E1));
+	findFreeChannelFull(loadData(0x4516));
 	return 0;
 }
 
 int ASound1::command25() {
-	playSound(0x179B);
-	playSound(0x17C7);
+	findFreeChannelFull(loadData(0x4546));
+	findFreeChannelFull(loadData(0x4574));
 	return 0;
 }
 
-int ASound1::command26() {
-	playSound(0x17F5);
-	return 0;
-}
+int ASound1::command26() { findFreeChannelFull(loadData(0x45A4)); return 0; }
+int ASound1::command27() { findFreeChannelFull(loadData(0x45B0)); return 0; }
+int ASound1::command28() { findFreeChannelFull(loadData(0x46CF)); return 0; }
+int ASound1::command29() { findFreeChannelFull(loadData(0x45E4)); return 0; }
+int ASound1::command30() { findFreeChannelFull(loadData(0x461C)); return 0; }
 
-int ASound1::command27() {
-	playSound(0x1801);
+// command31 - writes transposition byte 0x67 into the sound block before loading
+int ASound1::command31() {
+	*loadData(0x4680) = 0x67;
+	findFreeChannelFull(loadData(0x467D));
 	return 0;
 }
 
 // ---------------------------------------------------------------------------
-// commands 32-39
+// commands 32-48 - music piece loaders
+// All use: isSoundActive guard → isMusicChannelsActive → defer or load.
+// Exceptions: command35 uses command3 (not command1); command43/48 are
+// special (no deferred callback; modify sound data bytes directly);
+// command44 uses asymmetric counter/period; command46 uses isAnyChannelActive
+// as the outer guard instead of isSoundActive.
 // ---------------------------------------------------------------------------
 
-// command32 - no guard, no fade, load ch0-5
+void ASound1::loadCommand32() {
+	resetCallbackTimer(0xB0);
+	ASound::command1();
+	_channels[0]->load(loadData(0x299A));
+	_channels[1]->load(loadData(0x29E3));
+	_channels[2]->load(loadData(0x2A45));
+	_channels[3]->load(loadData(0x2A6F));
+	_channels[4]->load(loadData(0x2A9F));
+	_channels[5]->load(loadData(0x2AAE));
+}
+
 int ASound1::command32() {
-	_channels[0]->load(loadData(0x2522));
-	_channels[1]->load(loadData(0x255D));
-	_channels[2]->load(loadData(0x2591));
-	_channels[3]->load(loadData(0x25BB));
-	_channels[4]->load(loadData(0x25E7));
-	_channels[5]->load(loadData(0x2613));
+	byte *pData = loadData(0x299A);
+	if (!isSoundActive(pData)) {
+		if (!isMusicChannelsActive())
+			loadCommand32();
+		else
+			scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand32));
+	}
 	return 0;
 }
 
-// command33 - isSoundActive guard, command1, load ch0-5
+void ASound1::loadCommand33() {
+	resetCallbackTimer(0xB0);
+	setMusicIndex(0x21);
+	ASound::command1();
+	_channels[0]->load(loadData(0x2ABE));
+	_channels[1]->load(loadData(0x2B64));
+	_channels[2]->load(loadData(0x2BC8));
+	_channels[3]->load(loadData(0x2C03));
+	_channels[4]->load(loadData(0x2C82));
+	_channels[5]->load(loadData(0x2C9A));
+}
+
 int ASound1::command33() {
-	byte *pData = loadData(0x266C);
+	byte *pData = loadData(0x2ABE);
 	if (!isSoundActive(pData)) {
-		ASound::command1();
-		_channels[0]->load(pData);
-		_channels[1]->load(loadData(0x2929));
-		_channels[2]->load(loadData(0x2B1D));
-		_channels[3]->load(loadData(0x2D37));
-		_channels[4]->load(loadData(0x2EC3));
-		_channels[5]->load(loadData(0x3033));
+		if (!isMusicChannelsActive())
+			loadCommand33();
+		else
+			scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand33));
 	}
 	return 0;
 }
 
-// command34 - isSoundActive guard, stop(), load ch0-5
+void ASound1::loadCommand34() {
+	resetCallbackTimer(0x50);
+	ASound::command1();
+	_channels[0]->load(loadData(0x3A74));
+	_channels[1]->load(loadData(0x3AE1));
+	_channels[2]->load(loadData(0x3AFD));
+	_channels[3]->load(loadData(0x3B19));
+	_channels[4]->load(loadData(0x3B26));
+	_channels[5]->load(loadData(0x3B33));
+}
+
 int ASound1::command34() {
-	byte *pData = loadData(0x1852);
+	byte *pData = loadData(0x3A74);
 	if (!isSoundActive(pData)) {
-		stop();
-		_channels[0]->load(pData);
-		_channels[1]->load(loadData(0x1AA9));
-		_channels[2]->load(loadData(0x1BC4));
-		_channels[3]->load(loadData(0x1CF1));
-		_channels[4]->load(loadData(0x1DF2));
-		_channels[5]->load(loadData(0x1EBE));
+		if (!isMusicChannelsActive())
+			loadCommand34();
+		else
+			scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand34));
 	}
 	return 0;
 }
 
-// command35 - isSoundActive guard, command2 (lower-bank fade),
-// load ch0-5
+// command35 uses command3 (pending-stop fade) instead of command1
+void ASound1::loadCommand35() {
+	resetCallbackTimer(0x60);
+	ASound::command3();
+	_channels[0]->load(loadData(0x38A4));
+	_channels[1]->load(loadData(0x393C));
+	_channels[2]->load(loadData(0x39C2));
+	_channels[3]->load(loadData(0x3A0E));
+	_channels[4]->load(loadData(0x3A3E));
+	_channels[5]->load(loadData(0x3A54));
+	_channels[6]->load(loadData(0x3A63));
+}
+
 int ASound1::command35() {
-	byte *pData = loadData(0x0C36);
+	byte *pData = loadData(0x393C);
 	if (!isSoundActive(pData)) {
-		ASound::command2();
-		_channels[0]->load(pData);
-		_channels[1]->load(loadData(0x0D7F));
-		_channels[2]->load(loadData(0x0E48));
-		_channels[3]->load(loadData(0x0F10));
-		_channels[4]->load(loadData(0x0FB2));
-		_channels[5]->load(loadData(0x1096));
+		if (!isMusicChannelsActive())
+			loadCommand35();
+		else
+			scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand35));
 	}
 	return 0;
 }
 
-// command36 - isSoundActive guard, command2 (lower-bank fade),
-// load ch0-5
+void ASound1::loadCommand36() {
+	resetCallbackTimer(0x80);
+	ASound::command1();
+	_channels[0]->load(loadData(0x3F36));
+	_channels[1]->load(loadData(0x3FFA));
+	_channels[2]->load(loadData(0x40E9));
+	_channels[3]->load(loadData(0x417D));
+	_channels[4]->load(loadData(0x41ED));
+	_channels[5]->load(loadData(0x41FC));
+}
+
 int ASound1::command36() {
-	byte *pData = loadData(0x1190);
+	byte *pData = loadData(0x3F36);
 	if (!isSoundActive(pData)) {
-		ASound::command2();
-		_channels[0]->load(pData);
-		_channels[1]->load(loadData(0x12D7));
-		_channels[2]->load(loadData(0x13AA));
-		_channels[3]->load(loadData(0x1476));
-		_channels[4]->load(loadData(0x1528));
-		_channels[5]->load(loadData(0x1614));
+		if (!isMusicChannelsActive())
+			loadCommand36();
+		else
+			scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand36));
 	}
 	return 0;
 }
 
-// command37 - isSoundActive guard, command1, four loadAny
-// calls starting from channel 0
+void ASound1::loadCommand37() {
+	resetCallbackTimer(0xC0);
+	ASound::command1();
+	_channels[0]->load(loadData(0x4220));
+	_channels[1]->load(loadData(0x4285));
+	_channels[2]->load(loadData(0x42F0));
+	_channels[3]->load(loadData(0x42FF));
+	_channels[4]->load(loadData(0x4310));
+	_channels[5]->load(loadData(0x431C));
+}
+
 int ASound1::command37() {
-	byte *pData = loadData(0x3220);
+	byte *pData = loadData(0x4220);
 	if (!isSoundActive(pData)) {
-		ASound::command1();
-		findFreeChannel(pData);
-		findFreeChannel(loadData(0x326A));
-		findFreeChannel(loadData(0x3293));
-		findFreeChannel(loadData(0x32AC));
+		if (!isMusicChannelsActive())
+			loadCommand37();
+		else
+			scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand37));
 	}
 	return 0;
 }
 
-// command38 - alias for commandMusic0; also the direct dispatch
-// target for command 38.
+void ASound1::loadCommand38() {
+	resetCallbackTimer(0x60);
+	ASound::command1();
+	_channels[0]->load(loadData(0x12CA));
+	_channels[1]->load(loadData(0x13BF));
+	_channels[2]->load(loadData(0x14A2));
+	_channels[3]->load(loadData(0x15DB));
+	_channels[4]->load(loadData(0x16D3));
+	_channels[5]->load(loadData(0x1715));
+}
+
 int ASound1::command38() {
-	return commandMusic0();
+	byte *pData = loadData(0x12CA);
+	if (!isSoundActive(pData)) {
+		if (!isMusicChannelsActive())
+			loadCommand38();
+		else
+			scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand38));
+	}
+	return 0;
+}
+
+void ASound1::loadCommand39() {
+	resetCallbackTimer(0xB0);
+	ASound::command1();
+	_channels[0]->load(loadData(0x179C));
+	_channels[1]->load(loadData(0x17F1));
+	_channels[2]->load(loadData(0x1857));
+	_channels[3]->load(loadData(0x18B1));
+	_channels[4]->load(loadData(0x18C0));
+	_channels[5]->load(loadData(0x18CF));
 }
 
-// command39 - isSoundActive guard, command1, load ch0-5
 int ASound1::command39() {
-	byte *pData = loadData(0x423A);
+	byte *pData = loadData(0x179C);
 	if (!isSoundActive(pData)) {
-		ASound::command1();
-		_channels[0]->load(pData);
-		_channels[1]->load(loadData(0x43DF));
-		_channels[2]->load(loadData(0x44F7));
-		_channels[3]->load(loadData(0x45ED));
-		_channels[4]->load(loadData(0x46F9));
-		_channels[5]->load(loadData(0x48AF));
+		if (!isMusicChannelsActive())
+			loadCommand39();
+		else
+			scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand39));
+	}
+	return 0;
+}
+
+void ASound1::loadCommand40() {
+	resetCallbackTimer(0xA8);
+	ASound::command1();
+	_channels[0]->load(loadData(0x18DE));
+	_channels[1]->load(loadData(0x1A57));
+	_channels[2]->load(loadData(0x1C0D));
+	_channels[3]->load(loadData(0x1ED6));
+	_channels[4]->load(loadData(0x20FE));
+	_channels[5]->load(loadData(0x239B));
+}
+
+int ASound1::command40() {
+	byte *pData = loadData(0x18DE);
+	if (!isSoundActive(pData)) {
+		if (!isMusicChannelsActive())
+			loadCommand40();
+		else
+			scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand40));
 	}
 	return 0;
 }
 
+void ASound1::loadCommand41() {
+	resetCallbackTimer(0x90);
+	ASound::command1();
+	_channels[0]->load(loadData(0x23D2));
+	_channels[1]->load(loadData(0x2440));
+	_channels[2]->load(loadData(0x261C));
+	_channels[3]->load(loadData(0x24BA));
+	_channels[4]->load(loadData(0x259A));
+	_channels[5]->load(loadData(0x25EF));
+}
+
+int ASound1::command41() {
+	byte *pData = loadData(0x23D2);
+	if (!isSoundActive(pData)) {
+		if (!isMusicChannelsActive())
+			loadCommand41();
+		else
+			scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand41));
+	}
+	return 0;
+}
+
+void ASound1::loadCommand42() {
+	resetCallbackTimer(144);
+	setMusicIndex(0x29);
+	ASound::command1();
+	_channels[0]->load(loadData(0x1192));
+	_channels[1]->load(loadData(0x11C8));
+	_channels[2]->load(loadData(0x11FB));
+	_channels[3]->load(loadData(0x123C));
+	_channels[4]->load(loadData(0x126E));
+	_channels[5]->load(loadData(0x128B));
+	_channels[6]->load(loadData(0x12A1));
+}
+
+int ASound1::command42() {
+	byte *pData = loadData(0x1192);
+	if (!isSoundActive(pData)) {
+		if (!isMusicChannelsActive())
+			loadCommand42();
+		else
+			scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand42));
+	}
+	return 0;
+}
+
+// ---------------------------------------------------------------------------
+// loadMusicPitchBend - shared loader for command43 and command48
+// Resets timer to 0x54, fades current music, loads the pitch-bend piece
+// (6 channels starting at 0x287C).
+// ---------------------------------------------------------------------------
+void ASound1::loadMusicPitchBend() {
+	resetCallbackTimer(0x54);
+	ASound::command1();
+	_channels[0]->load(loadData(0x287C));
+	_channels[1]->load(loadData(0x28C8));
+	_channels[2]->load(loadData(0x290F));
+	_channels[3]->load(loadData(0x2938));
+	_channels[4]->load(loadData(0x295B));
+	_channels[5]->load(loadData(0x298C));
+}
+
+// command43 - set pitch-bend variant byte to 0x2F (bent), mark script var,
+// then start the pitch-bend piece only if it is not already playing.
+int ASound1::command43() {
+	*loadData(0x28C9) = 0x2F;
+	setScriptVar(14, 1);
+	if (!isSoundActive(loadData(0x287C))) {
+		loadMusicPitchBend();
+		setMusicIndex(0x10);
+	}
+	return 0;
+}
+
+// command44 - asymmetric timer: counter=0x60 but period=0xE0
+void ASound1::loadCommand44() {
+	resetCallbackTimerEx(0x60, 0xE0);
+	ASound::command1();
+	_channels[0]->load(loadData(0x3B40));
+	_channels[1]->load(loadData(0x3B9E));
+	_channels[2]->load(loadData(0x3F26));
+	_channels[3]->load(loadData(0x3C25));
+	_channels[4]->load(loadData(0x3DB4));
+	_channels[5]->load(loadData(0x3E8B));
+}
+
+int ASound1::command44() {
+	byte *pData = loadData(0x3B40);
+	if (!isSoundActive(pData)) {
+		if (!isMusicChannelsActive())
+			loadCommand44();
+		else
+			scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand44));
+	}
+	return 0;
+}
+
+void ASound1::loadCommand45() {
+	resetCallbackTimer(0x60);
+	setMusicIndex(0x2D);
+	ASound::command1();
+	_channels[0]->load(loadData(0x32A0));
+	_channels[1]->load(loadData(0x33E1));
+	_channels[2]->load(loadData(0x34CE));
+	_channels[3]->load(loadData(0x35DB));
+	_channels[4]->load(loadData(0x36CB));
+	_channels[5]->load(loadData(0x3873));
+}
+
+int ASound1::command45() {
+	byte *pData = loadData(0x32A0);
+	if (!isSoundActive(pData)) {
+		if (!isMusicChannelsActive())
+			loadCommand45();
+		else
+			scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand45));
+	}
+	return 0;
+}
+
+// command46 - uses isAnyChannelActive as the outer guard (not isSoundActive),
+// then isMusicChannelsActive for the defer/immediate decision.
+void ASound1::loadCommand46() {
+	resetCallbackTimer(0x90);
+	ASound::command1();
+	_channels[0]->load(loadData(0x432E));
+	_channels[1]->load(loadData(0x4385));
+	_channels[2]->load(loadData(0x43B8));
+	_channels[3]->load(loadData(0x43C3));
+}
+
+int ASound1::command46() {
+	if (isAnyChannelActive())
+		return 0;
+	if (!isMusicChannelsActive())
+		loadCommand46();
+	else
+		scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand46));
+	return 0;
+}
+
+void ASound1::loadCommand47() {
+	resetCallbackTimer(0x60);
+	ASound::command1();
+	_channels[0]->load(loadData(0x12C4));
+	_channels[1]->load(loadData(0x13B2));
+	_channels[2]->load(loadData(0x149C));
+	_channels[3]->load(loadData(0x15D5));
+	_channels[4]->load(loadData(0x16F4));
+	_channels[5]->load(loadData(0x1759));
+}
+
+int ASound1::command47() {
+	byte *pData = loadData(0x12C4);
+	if (!isSoundActive(pData)) {
+		if (!isMusicChannelsActive())
+			loadCommand47();
+		else
+			scheduleCallback(MAKE_CALLBACK(ASound1, loadCommand47));
+	}
+	return 0;
+}
+
+// command48 - set pitch-bend variant byte to 0x1B (normal), then load if not active
+int ASound1::command48() {
+	*loadData(0x28C9) = 0x1B;
+	setScriptVar(14, 0);
+	if (!isSoundActive(loadData(0x287C))) {
+		loadMusicPitchBend();
+		setMusicIndex(0x28);
+	}
+	return 0;
+}
+
+// ---------------------------------------------------------------------------
+// commands 64-101 - SFX loaders via findFreeChannelFull / findFreeChannel /
+// direct channel load.
+// ---------------------------------------------------------------------------
+int ASound1::command64()  { findFreeChannelFull(loadData(0x2D20)); return 0; }
+int ASound1::command65()  { findFreeChannelFull(loadData(0x2D35)); return 0; }
+int ASound1::command66()  { findFreeChannelFull(loadData(0x2D3F)); return 0; }
+int ASound1::command67()  { findFreeChannelFull(loadData(0x2D4B)); return 0; }
+int ASound1::command68()  { findFreeChannelFull(loadData(0x2D59)); return 0; }
+
+int ASound1::command69() {
+	findFreeChannelFull(loadData(0x2D78));
+	findFreeChannelFull(loadData(0x2D8E));
+	return 0;
+}
+
+int ASound1::command70()  { findFreeChannelFull(loadData(0x2DA0)); return 0; }
+
+// command71 - load directly into channel 8
+int ASound1::command71() { _channels[8]->load(loadData(0x2DB9)); return 0; }
+
+int ASound1::command72()  { findFreeChannelFull(loadData(0x2DC7)); return 0; }
+int ASound1::command73()  { findFreeChannelFull(loadData(0x2DAC)); return 0; }
+
+int ASound1::command74() {
+	findFreeChannelFull(loadData(0x2DD5));
+	findFreeChannelFull(loadData(0x2DDE));
+	return 0;
+}
+
+int ASound1::command75()  { findFreeChannelFull(loadData(0x2DF2)); return 0; }
+int ASound1::command76()  { findFreeChannelFull(loadData(0x2E0D)); return 0; }
+
+int ASound1::command77() {
+	findFreeChannelFull(loadData(0x2E1C));
+	findFreeChannelFull(loadData(0x2E28));
+	return 0;
+}
+
+int ASound1::command78()  { findFreeChannelFull(loadData(0x2E4C)); return 0; }
+int ASound1::command79()  { findFreeChannelFull(loadData(0x2E34)); return 0; }
+int ASound1::command80()  { findFreeChannelFull(loadData(0x2E5E)); return 0; }
+
+// command81 - loads the same data block twice (as in the original)
+int ASound1::command81() {
+	findFreeChannelFull(loadData(0x2EA4));
+	findFreeChannelFull(loadData(0x2EA4));
+	return 0;
+}
+
+int ASound1::command82()  { findFreeChannelFull(loadData(0x2ECB)); return 0; }
+int ASound1::command83()  { findFreeChannelFull(loadData(0x2EE3)); return 0; }
+int ASound1::command84()  { findFreeChannelFull(loadData(0x2EF5)); return 0; }
+int ASound1::command85()  { findFreeChannelFull(loadData(0x2EFF)); return 0; }
+
+// command86 - load directly into channel 7
+int ASound1::command86() { _channels[7]->load(loadData(0x2F0C)); return 0; }
+
+int ASound1::command87() {
+	findFreeChannelFull(loadData(0x2D67));
+	findFreeChannelFull(loadData(0x2D89));
+	return 0;
+}
+
+int ASound1::command88()  { findFreeChannelFull(loadData(0x2F24)); return 0; }
+int ASound1::command89()  { findFreeChannelFull(loadData(0x2F2E)); return 0; }
+
+int ASound1::command90() {
+	findFreeChannelFull(loadData(0x2CAA));
+	findFreeChannelFull(loadData(0x2CD4));
+	return 0;
+}
+
+int ASound1::command91() {
+	findFreeChannelFull(loadData(0x2CDF));
+	findFreeChannelFull(loadData(0x2D15));
+	return 0;
+}
+
+// command92 = nullptr (no-op, slot mapped to null in _commandList)
+
+int ASound1::command93() {
+	findFreeChannelFull(loadData(0x2F3C));
+	findFreeChannelFull(loadData(0x2F45));
+	return 0;
+}
+
+int ASound1::command94()  { findFreeChannelFull(loadData(0x2F57)); return 0; }
+
+// command95 - four-voice piece using findFreeChannel (not Full)
+int ASound1::command95() {
+	findFreeChannel(loadData(0x2F6B));
+	findFreeChannel(loadData(0x2FD7));
+	findFreeChannel(loadData(0x302E));
+	findFreeChannel(loadData(0x3045));
+	return 0;
+}
+
+int ASound1::command96()  { findFreeChannelFull(loadData(0x305C)); return 0; }
+
+// command97 = sub_11F98 in original
+int ASound1::command97() {
+	findFreeChannelFull(loadData(0x307E));
+	findFreeChannelFull(loadData(0x3094));
+	return 0;
+}
+
+// command98 = nullptr (no-op, slot mapped to null in _commandList)
+
+int ASound1::command99()  { findFreeChannelFull(loadData(0x30B0)); return 0; }
+int ASound1::command100() { findFreeChannelFull(loadData(0x30BA)); return 0; }
+int ASound1::command101() { findFreeChannelFull(loadData(0x30C4)); return 0; }
+
 /*-----------------------------------------------------------------------*/
 
 /*-----------------------------------------------------------------------*/
 /* ASound2  (asound.dr2)                                                  *
  *-----------------------------------------------------------------------*/
 
-const ASound2::CommandPtr ASound2::_commandList[73] = {
-	// commands 0-8  (asound_commands1)
+const ASound2::CommandPtr ASound2::_commandList[76] = {
+	// 0-8: delegate to base
 	&ASound2::command0,  &ASound2::command1,  &ASound2::command2,  &ASound2::command3,
 	&ASound2::command4,  &ASound2::command5,  &ASound2::command6,  &ASound2::command7,
 	&ASound2::command8,
-	// 9-15 absent
+	// 9-15: nullptr
 	nullptr,             nullptr,             nullptr,
 	nullptr,             nullptr,             nullptr,             nullptr,
-	// command 16  (asound_commands2)
-	&ASound2::command16,
-	// 17-23 absent
-	nullptr,             nullptr,             nullptr,
+	// 16-19: off_11A26
+	&ASound2::command16, &ASound2::command17, &ASound2::command18, nullptr,
+	// 20-23: nullptr
 	nullptr,             nullptr,             nullptr,             nullptr,
-	// commands 24-27  (asound_commands3)
+	// 24-31: off_11A2E
 	&ASound2::command24, &ASound2::command25, &ASound2::command26, &ASound2::command27,
-	// 28-31 absent
-	nullptr,             nullptr,             nullptr,             nullptr,
-	// commands 32-35  (asound_commands4)
+	&ASound2::command28, &ASound2::command29, &ASound2::command30, &ASound2::command31,
+	// 32-36: funcs_11C87 (slot 36 = no-op)
 	&ASound2::command32, &ASound2::command33, &ASound2::command34, &ASound2::command35,
-	// 36-63 absent
+	nullptr,
+	// 37-63: nullptr
 	nullptr,             nullptr,             nullptr,             nullptr,
 	nullptr,             nullptr,             nullptr,             nullptr,
 	nullptr,             nullptr,             nullptr,             nullptr,
 	nullptr,             nullptr,             nullptr,             nullptr,
 	nullptr,             nullptr,             nullptr,             nullptr,
 	nullptr,             nullptr,             nullptr,             nullptr,
-	nullptr,             nullptr,             nullptr,             nullptr,
-	// commands 64-72  (asound_commands5)
-	&ASound2::command64, &ASound2::command65, &ASound2::command66, &ASound2::command67,
-	&ASound2::command68, &ASound2::command69, &ASound2::command70, &ASound2::command71,
-	&ASound2::command72
+	nullptr,             nullptr,             nullptr,
+	// 64-75: off_11A4A (slots 73-75 = no-ops)
+	&ASound2::command64,    &ASound2::command65, &ASound2::command66,    &ASound2::command67,
+	&ASound2::command68,    &ASound2::command69_70, &ASound2::command69_70, &ASound2::command71,
+	&ASound2::command72,    nullptr,             nullptr,             nullptr
 };
 
 ASound2::ASound2(Audio::Mixer *mixer, OPL::OPL *opl)
 		: ASound(mixer, opl, "asound.dr2", 0x1fa0, 0x2950) {
-	// Load sound samples
 	auto samplesStream = getDataStream(0x1dc);
 	for (int i = 0; i < 182; ++i)
 		_samples.push_back(AdlibSample(samplesStream));
 }
 
 int ASound2::command(int commandId, int param) {
-	if (commandId > 72 || !_commandList[commandId])
+	if (commandId > 75 || !_commandList[commandId])
 		return 0;
-
+	if (commandId >= 32 && commandId < 64)
+		setMusicIndex(commandId);
 	return (this->*_commandList[commandId])();
 }
 
-// commands 0-8: delegate to base ASound
 int ASound2::command0() { return ASound::command0(); }
 int ASound2::command1() { return ASound::command1(); }
 int ASound2::command2() { return ASound::command2(); }
@@ -411,165 +797,228 @@ int ASound2::command6() { return ASound::command6(); }
 int ASound2::command7() { return ASound::command7(); }
 int ASound2::command8() { return ASound::command8(); }
 
-// ---------------------------------------------------------------------------
-// command16 - isSoundActive guard, command1, load ch0-5
-// ---------------------------------------------------------------------------
+void ASound2::loadCommand16() {
+	resetCallbackTimer(0x60);
+	setMusicIndex(0x10);
+	ASound::command1();
+	_channels[0]->load(loadData(0x2584));
+	_channels[1]->load(loadData(0x25E0));
+	_channels[2]->load(loadData(0x2630));
+	_channels[3]->load(loadData(0x2640));
+}
+
 int ASound2::command16() {
-	byte *pData = loadData(0x0C36);
+	byte *pData = loadData(0x2584);
 	if (!isSoundActive(pData)) {
+		if (isMusicChannelsActive())
+			scheduleCallback(MAKE_CALLBACK(ASound2, loadCommand16));
+		else
+			loadCommand16();
+	}
+	return 0;
+}
+
+int ASound2::command17() {
+	byte *pData = loadData(0x2268);
+	if (!isSoundActive(pData)) {
+		resetCallbackTimer(0x60);
 		ASound::command1();
 		_channels[0]->load(pData);
-		_channels[1]->load(loadData(0x0C8E));
-		_channels[2]->load(loadData(0x0CF4));
-		_channels[3]->load(loadData(0x0D4E));
-		_channels[4]->load(loadData(0x0DA3));
-		_channels[5]->load(loadData(0x0DB1));
+		_channels[1]->load(loadData(0x229D));
+		_channels[2]->load(loadData(0x22D0));
+		_channels[3]->load(loadData(0x22E9));
+		_channels[4]->load(loadData(0x231C));
+		_channels[5]->load(loadData(0x232A));
+		_channels[8]->load(loadData(0x2342));
 	}
 	return 0;
 }
 
-// ---------------------------------------------------------------------------
-// commands 24-27 (asound_commands3) - upper channel pool
-// ---------------------------------------------------------------------------
+int ASound2::command18() {
+	ASound::command1();
+	if (getMusicIndex() <= 18)
+		return command16();
+	return (this->*_commandList[getMusicIndex()])();
+}
 
 int ASound2::command24() {
-	playSound(0x1A4A);
-	playSound(0x1A7D);
+	playSound(0x237B);
+	playSound(0x23B0);
 	return 0;
 }
 
 int ASound2::command25() {
-	playSound(0x1AAB);
-	playSound(0x1AD7);
+	playSound(0x23E0);
+	playSound(0x240E);
 	return 0;
 }
 
 int ASound2::command26() {
-	playSound(0x1B05);
+	playSound(0x243E);
 	return 0;
 }
 
 int ASound2::command27() {
-	playSound(0x1B11);
+	playSound(0x244A);
 	return 0;
 }
 
-// ---------------------------------------------------------------------------
-// commands 32-35 (asound_commands4)
-// ---------------------------------------------------------------------------
+int ASound2::command28() {
+	playSound(0x2569);
+	return 0;
+}
 
-// command32 - command1, six loadAny calls from channel 0
-int ASound2::command32() {
+int ASound2::command29() {
+	playSound(0x247E);
+	return 0;
+}
+
+int ASound2::command30() {
+	playSound(0x24B6);
+	return 0;
+}
+
+int ASound2::command31() {
+	*loadData(0x251A) = 0x67;
+	playSound(0x2517);
+	return 0;
+}
+
+void ASound2::loadCommand32() {
+	resetCallbackTimer(0x60);
 	ASound::command1();
-	findFreeChannel(loadData(0x1BE4));
-	findFreeChannel(loadData(0x1CB7));
-	findFreeChannel(loadData(0x1E1E));
-	findFreeChannel(loadData(0x1EC8));
-	findFreeChannel(loadData(0x1ED8));
-	findFreeChannel(loadData(0x1EEF));
+	_channels[0]->load(loadData(0x1192));
+	_channels[1]->load(loadData(0x1201));
+	_channels[2]->load(loadData(0x127A));
+	_channels[3]->load(loadData(0x1360));
+	_channels[4]->load(loadData(0x144A));
+	_channels[5]->load(loadData(0x1514));
+}
+
+int ASound2::command32() {
+	byte *pData = loadData(0x1192);
+	if (!isSoundActive(pData)) {
+		if (isMusicChannelsActive())
+			scheduleCallback(MAKE_CALLBACK(ASound2, loadCommand32));
+		else
+			loadCommand32();
+	}
 	return 0;
 }
 
-// command33 - isSoundActive guard, command1, load ch0-7
+void ASound2::loadCommand33() {
+	resetCallbackTimer(0x60);
+	ASound::command1();
+	_channels[0]->load(loadData(0x155E));
+	_channels[1]->load(loadData(0x162B));
+	_channels[2]->load(loadData(0x16E9));
+	_channels[3]->load(loadData(0x17B2));
+	_channels[4]->load(loadData(0x189C));
+	_channels[5]->load(loadData(0x1921));
+	_channels[8]->load(loadData(0x19DF));
+}
+
 int ASound2::command33() {
-	byte *pData = loadData(0x1B62);
+	byte *pData = loadData(0x155E);
 	if (!isSoundActive(pData)) {
-		ASound::command1();
-		_channels[0]->load(pData);
-		_channels[1]->load(loadData(0x1B97));
-		_channels[2]->load(loadData(0x1BA5));
-		_channels[3]->load(loadData(0x1BB3));
-		_channels[4]->load(loadData(0x1BC1));
-		_channels[5]->load(loadData(0x1BC5));
-		_channels[6]->load(loadData(0x1BC9));
-		_channels[7]->load(loadData(0x1BD5));
+		if (isMusicChannelsActive())
+			scheduleCallback(MAKE_CALLBACK(ASound2, loadCommand33));
+		else
+			loadCommand33();
 	}
 	return 0;
 }
 
-// command34 - isSoundActive guard, command1, load ch0-6
+void ASound2::loadCommand34() {
+	resetCallbackTimer(0x60);
+	ASound::command1();
+	_channels[0]->load(loadData(0x1FDA));
+	_channels[1]->load(loadData(0x2071));
+	_channels[2]->load(loadData(0x20C0));
+	_channels[3]->load(loadData(0x2116));
+	_channels[4]->load(loadData(0x2126));
+	_channels[5]->load(loadData(0x2180));
+	_channels[8]->load(loadData(0x2190));
+}
+
 int ASound2::command34() {
-	byte *pData = loadData(0x0DC0);
+	byte *pData = loadData(0x1FDA);
 	if (!isSoundActive(pData)) {
-		ASound::command1();
-		_channels[0]->load(pData);
-		_channels[1]->load(loadData(0x0FAF));
-		_channels[2]->load(loadData(0x1206));
-		_channels[3]->load(loadData(0x139A));
-		_channels[4]->load(loadData(0x1565));
-		_channels[5]->load(loadData(0x1833));
-		_channels[6]->load(loadData(0x18CD));
+		if (isMusicChannelsActive())
+			scheduleCallback(MAKE_CALLBACK(ASound2, loadCommand34));
+		else
+			loadCommand34();
 	}
 	return 0;
 }
 
-// command35 - isSoundActive guard, command1, load ch0-6
+void ASound2::loadCommand35() {
+	resetCallbackTimerEx(0xC0, 0x50);
+	ASound::command1();
+	_channels[0]->load(loadData(0x1A2C));
+	_channels[1]->load(loadData(0x1B74));
+	_channels[2]->load(loadData(0x1C06));
+	_channels[3]->load(loadData(0x1CE3));
+	_channels[4]->load(loadData(0x1DDE));
+	_channels[5]->load(loadData(0x1E91));
+	_channels[8]->load(loadData(0x1F62));
+}
+
 int ASound2::command35() {
-	byte *pData = loadData(0x1F02);
+	byte *pData = loadData(0x1A2C);
 	if (!isSoundActive(pData)) {
-		ASound::command1();
-		_channels[0]->load(pData);
-		_channels[1]->load(loadData(0x1F66));
-		_channels[2]->load(loadData(0x1F70));
-		_channels[3]->load(loadData(0x1F8D));
-		_channels[4]->load(loadData(0x1FCE));
-		_channels[5]->load(loadData(0x1FF7));
-		_channels[6]->load(loadData(0x202E));
+		if (isMusicChannelsActive())
+			scheduleCallback(MAKE_CALLBACK(ASound2, loadCommand35));
+		else
+			loadCommand35();
 	}
 	return 0;
 }
 
-// ---------------------------------------------------------------------------
-// commands 64-72 (asound_commands5) - upper channel pool
-// ---------------------------------------------------------------------------
-
 int ASound2::command64() {
-	playSound(0x1928);
+	playSound(0x219E);
+	playSound(0x21A8);
 	return 0;
 }
 
 int ASound2::command65() {
-	playSound(0x193C);
+	playSound(0x1F92);
+	playSound(0x1FAB);
+	playSound(0x1FBE);
+	playSound(0x1FC9);
 	return 0;
 }
 
 int ASound2::command66() {
-	playSound(0x1946);
-	playSound(0x195C);
+	playSound(0x21B2);
 	return 0;
 }
 
 int ASound2::command67() {
-	playSound(0x196D);
+	playSound(0x21BC);
+	playSound(0x21C5);
 	return 0;
 }
 
-// no-op
 int ASound2::command68() {
+	playSound(0x21E7);
 	return 0;
 }
 
-int ASound2::command69() {
-	playSound(0x197F);
-	playSound(0x19A5);
-	playSound(0x19CB);
-	return 0;
-}
-
-int ASound2::command70() {
-	playSound(0x19E5);
-	playSound(0x19F1);
+int ASound2::command69_70() {
+	playSound(0x24EF);
+	playSound(0x2503);
 	return 0;
 }
 
 int ASound2::command71() {
-	playSound(0x19FF);
+	playSound(0x21FC);
 	return 0;
 }
 
 int ASound2::command72() {
-	playSound(0x1A0D);
-	playSound(0x1A10);
+	playSound(0x2242);
+	playSound(0x224F);
 	return 0;
 }
 
@@ -1393,25 +1842,32 @@ int ASound6::command8() {
 
 /*-----------------------------------------------------------------------*/
 
-const ASound9::CommandPtr ASound9::_commandList[72] = {
-	&ASound9::command0,  &ASound9::command1,  &ASound9::command2,  &ASound9::command3,
-	&ASound9::command4,  &ASound9::command5,  &ASound9::command6,  &ASound9::command7,
-	&ASound9::command8,  nullptr,             nullptr,             nullptr,
-	nullptr,             nullptr,             nullptr,             nullptr,
-	&ASound9::command16, nullptr,             nullptr,             nullptr,
-	nullptr,             nullptr,             nullptr,             nullptr,
-	&ASound9::command24, &ASound9::command25, &ASound9::command26, &ASound9::command27,
-	nullptr,             nullptr,             nullptr,             nullptr,
-	&ASound9::command32, &ASound9::command33, &ASound9::command34, &ASound9::command35,
-	&ASound9::command36, &ASound9::command37, &ASound9::command38, &ASound9::command39,
-	nullptr,             nullptr,             nullptr,             nullptr,
-	nullptr,             nullptr,             nullptr,             nullptr,
-	nullptr,             nullptr,             nullptr,             nullptr,
-	nullptr,             nullptr,             nullptr,             nullptr,
-	nullptr,             nullptr,             nullptr,             nullptr,
-	nullptr,             nullptr,             nullptr,             nullptr,
-	&ASound9::command64, &ASound9::command65, &ASound9::command66, &ASound9::command67,
-	&ASound9::command68, &ASound9::command69, &ASound9::command70, &ASound9::command71
+const ASound9::CommandPtr ASound9::_commandList[65] = {
+	// 0-8: delegate to base
+	&ASound9::command0,    &ASound9::command1,    &ASound9::command2,    &ASound9::command3,
+	&ASound9::command4,    &ASound9::command5,    &ASound9::command6,    &ASound9::command7,
+	&ASound9::command8,
+	// 9-15: nullptr (not in any dispatch range)
+	nullptr,               nullptr,               nullptr,               nullptr,
+	nullptr,               nullptr,               nullptr,
+	// 16-19: nullptr (off_11A26, all null)
+	nullptr,               nullptr,               nullptr,               nullptr,
+	// 20-23: nullptr
+	nullptr,               nullptr,               nullptr,               nullptr,
+	// 24-31: nullptr (off_11A2E, all null)
+	nullptr,               nullptr,               nullptr,               nullptr,
+	nullptr,               nullptr,               nullptr,               nullptr,
+	// 32-63: music/SFX (off_11A3E)
+	&ASound9::command32,   &ASound9::command33_47, &ASound9::command34,  &ASound9::command35,
+	&ASound9::command36,   &ASound9::command37,   &ASound9::command38,   &ASound9::command39,
+	&ASound9::command40,   &ASound9::command41,   &ASound9::command42,   &ASound9::command43,
+	nullptr,               &ASound9::command45,   &ASound9::command46,   &ASound9::command33_47,
+	&ASound9::command48,   &ASound9::command49,   &ASound9::command50,   &ASound9::command51,
+	&ASound9::command52,   &ASound9::command53,   &ASound9::command54,   &ASound9::command55,
+	nullptr,               &ASound9::command57,   &ASound9::command58,   &ASound9::command59,
+	nullptr,               &ASound9::command61,   &ASound9::command62,   &ASound9::command63,
+	// 64: nullptr (off_11A80, single null entry)
+	nullptr,
 };
 
 ASound9::ASound9(Audio::Mixer *mixer, OPL::OPL *opl) :
@@ -1423,207 +1879,559 @@ ASound9::ASound9(Audio::Mixer *mixer, OPL::OPL *opl) :
 }
 
 int ASound9::command(int commandId, int param) {
-	if (commandId > 71 || !_commandList[commandId])
+	if (commandId > 64 || !_commandList[commandId])
 		return 0;
-	
 	return (this->*_commandList[commandId])();
 }
 
-// commands 0-8: delegate to base ASound
-int ASound9::command0() {
-	return ASound::command0();
+// Commands 0-8: delegate to base
+int ASound9::command0() { return ASound::command0(); }
+int ASound9::command1() { return ASound::command1(); }
+int ASound9::command2() { return ASound::command2(); }
+int ASound9::command3() { return ASound::command3(); }
+int ASound9::command4() { return ASound::command4(); }
+int ASound9::command5() { return ASound::command5(); }
+int ASound9::command6() { return ASound::command6(); }
+int ASound9::command7() { return ASound::command7(); }
+int ASound9::command8() { return ASound::command8(); }
+
+// ---------------------------------------------------------------------------
+// Music command 32 — deferred loader (isMusicChannelsActive pattern B)
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand32() {
+	ASound::command1();
+	resetCallbackTimerEx(0x62, 0x54);
+	_channels[0]->load(loadData(0x1192));
+	_channels[1]->load(loadData(0x11DA));
+	_channels[2]->load(loadData(0x128F));
+	_channels[3]->load(loadData(0x12C2));
+	_channels[4]->load(loadData(0x13FB));
+	_channels[5]->load(loadData(0x156B));
+	_channels[8]->load(loadData(0x14EA));
 }
-int ASound9::command1() {
-	return ASound::command1();
+
+int ASound9::command32() {
+	if (isMusicChannelsActive())
+		scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand32));
+	else
+		loadCommand32();
+	return 0;
 }
-int ASound9::command2() {
-	return ASound::command2();
+
+// ---------------------------------------------------------------------------
+// Music command 33/47 — shared deferred loader
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand33_47() {
+	ASound::command1();
+	resetCallbackTimerEx(0x54, 0x46);
+	_channels[0]->load(loadData(0x21A6));
+	_channels[1]->load(loadData(0x2219));
+	_channels[2]->load(loadData(0x225F));
+	_channels[4]->load(loadData(0x239A));
+	_channels[5]->load(loadData(0x23FC));
+	_channels[6]->load(loadData(0x2563));
+	_channels[3]->load(loadData(0x264A));
+	_channels[7]->load(loadData(0x2657));
+	_channels[8]->load(loadData(0x231E));
 }
-int ASound9::command3() {
-	return ASound::command3();
+
+int ASound9::command33_47() {
+	if (isMusicChannelsActive())
+		scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand33_47));
+	else
+		loadCommand33_47();
+	return 0;
 }
-int ASound9::command4() {
-	return ASound::command4();
+
+// ---------------------------------------------------------------------------
+// Music command 34
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand34() {
+	ASound::command1();
+	resetCallbackTimer(0x38);
+	_channels[0]->load(loadData(0x2664));
+	_channels[1]->load(loadData(0x285C));
+	_channels[2]->load(loadData(0x2A61));
+	_channels[8]->load(loadData(0x2C64));
+	_channels[4]->load(loadData(0x2DE3));
+	_channels[5]->load(loadData(0x2EE6));
+	_channels[6]->load(loadData(0x2F31));
 }
-int ASound9::command5() {
-	return ASound::command5();
+
+int ASound9::command34() {
+	if (isMusicChannelsActive())
+		scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand34));
+	else
+		loadCommand34();
+	return 0;
 }
-int ASound9::command6() {
-	return ASound::command6();
+
+// ---------------------------------------------------------------------------
+// Music command 35
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand35() {
+	ASound::command1();
+	resetCallbackTimer(0x50);
+	_channels[0]->load(loadData(0x2F7E));
+	_channels[1]->load(loadData(0x2FC7));
+	_channels[2]->load(loadData(0x300E));
+	_channels[3]->load(loadData(0x30D2));
+	_channels[8]->load(loadData(0x3148));
+	_channels[5]->load(loadData(0x31D2));
+	_channels[6]->load(loadData(0x326A));
 }
-int ASound9::command7() {
-	return ASound::command7();
+
+int ASound9::command35() {
+	if (isMusicChannelsActive())
+		scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand35));
+	else
+		loadCommand35();
+	return 0;
 }
-int ASound9::command8() {
-	return ASound::command8();
+
+// ---------------------------------------------------------------------------
+// Music command 36
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand36() {
+	ASound::command1();
+	resetCallbackTimer(0x28);
+	_channels[0]->load(loadData(0x33D4));
+	_channels[1]->load(loadData(0x345F));
+	_channels[2]->load(loadData(0x34D5));
+	_channels[3]->load(loadData(0x3505));
+	_channels[4]->load(loadData(0x37D1));
+	_channels[5]->load(loadData(0x3895));
+	_channels[8]->load(loadData(0x383D));
 }
 
-int ASound9::command24() {
-	playSound(0x203E);
-	playSound(0x2071);
+int ASound9::command36() {
+	if (isMusicChannelsActive())
+		scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand36));
+	else
+		loadCommand36();
 	return 0;
 }
 
-int ASound9::command25() {
-	playSound(0x209F);
-	playSound(0x20CB);
-	return 0;
+// ---------------------------------------------------------------------------
+// Music command 37
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand37() {
+	ASound::command1();
+	resetCallbackTimer(0x50);
+	_channels[0]->load(loadData(0x38D8));
+	_channels[1]->load(loadData(0x393D));
+	_channels[2]->load(loadData(0x39A1));
+	_channels[3]->load(loadData(0x3A05));
+	_channels[4]->load(loadData(0x3A89));
+	_channels[8]->load(loadData(0x3A97));
+	_channels[5]->load(loadData(0x3AF3));
 }
 
-int ASound9::command26() {
-	playSound(0x20F9);
+int ASound9::command37() {
+	if (isMusicChannelsActive())
+		scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand37));
+	else
+		loadCommand37();
 	return 0;
 }
 
-int ASound9::command27() {
-	playSound(0x2105);
+// ---------------------------------------------------------------------------
+// Music command 38
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand38() {
+	ASound::command1();
+	resetCallbackTimer(0x28);
+	_channels[0]->load(loadData(0x3C5C));
+	_channels[1]->load(loadData(0x3CE9));
+	_channels[2]->load(loadData(0x3D69));
+	_channels[3]->load(loadData(0x3505));
+	_channels[4]->load(loadData(0x3D95));
+	_channels[8]->load(loadData(0x3DF9));
+	_channels[6]->load(loadData(0x3E45));
+}
+
+int ASound9::command38() {
+	if (isMusicChannelsActive())
+		scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand38));
+	else
+		loadCommand38();
 	return 0;
 }
 
-int ASound9::command32() {
+// ---------------------------------------------------------------------------
+// Music command 39
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand39() {
 	ASound::command1();
-	findFreeChannel(loadData(0x2B16));
-	findFreeChannel(loadData(0x2B6C));
-	findFreeChannel(loadData(0x2BB6));
-	findFreeChannel(loadData(0x2E88));
-	findFreeChannel(loadData(0x2E98));
-	findFreeChannel(loadData(0x2EA3));
-	findFreeChannel(loadData(0x2EAE));
-	findFreeChannel(loadData(0x2EB7));
+	resetCallbackTimer(0x28);
+	_channels[0]->load(loadData(0x3E6E));
+	_channels[1]->load(loadData(0x3EE3));
+	_channels[2]->load(loadData(0x3F55));
+	_channels[3]->load(loadData(0x4049));
+	_channels[4]->load(loadData(0x44E3));
+	_channels[5]->load(loadData(0x459B));
+	_channels[8]->load(loadData(0x4571));
+}
+
+int ASound9::command39() {
+	if (isMusicChannelsActive())
+		scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand39));
+	else
+		loadCommand39();
 	return 0;
 }
 
-int ASound9::command33() {
+// ---------------------------------------------------------------------------
+// Music command 40
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand40() {
+	ASound::command1();
+	resetCallbackTimer(0x38);
+	_channels[0]->load(loadData(0x2664));
+	_channels[1]->load(loadData(0x285C));
+	_channels[2]->load(loadData(0x2A61));
+	_channels[8]->load(loadData(0x4692));
+	_channels[4]->load(loadData(0x479F));
+	_channels[5]->load(loadData(0x4841));
+	_channels[6]->load(loadData(0x4875));
+}
+
+int ASound9::command40() {
+	if (isMusicChannelsActive())
+		scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand40));
+	else
+		loadCommand40();
 	return 0;
 }
 
-int ASound9::command34() {
+// ---------------------------------------------------------------------------
+// Music command 41
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand41() {
 	ASound::command1();
-	_channels[0]->load(loadData(0x31D0));
-	_channels[1]->load(loadData(0x3221));
-	_channels[2]->load(loadData(0x3282));
-	_channels[3]->load(loadData(0x32CB));
-	_channels[4]->load(loadData(0x331A));
-	_channels[5]->load(loadData(0x3369));
-	_channels[6]->load(loadData(0x33B0));
+	resetCallbackTimer(0x54);
+	_channels[0]->load(loadData(0x157A));
+	_channels[1]->load(loadData(0x1629));
+	_channels[2]->load(loadData(0x1A68));
+	_channels[8]->load(loadData(0x1BA2));
+	_channels[4]->load(loadData(0x1C58));
+	_channels[5]->load(loadData(0x1D38));
+	_channels[6]->load(loadData(0x1FC2));
+}
+
+int ASound9::command41() {
+	if (isMusicChannelsActive())
+		scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand41));
+	else
+		loadCommand41();
 	return 0;
 }
 
-int ASound9::command35() {
+// ---------------------------------------------------------------------------
+// Music command 42
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand42() {
 	ASound::command1();
-	_channels[0]->load(loadData(0x295E));
-	_channels[1]->load(loadData(0x299E));
-	_channels[2]->load(loadData(0x29C3));
-	_channels[3]->load(loadData(0x29E8));
-	_channels[4]->load(loadData(0x2A46));
-	_channels[5]->load(loadData(0x2AA5));
-	_channels[6]->load(loadData(0x2AE0));
+	resetCallbackTimerEx(0xA8, 0x50);
+	_channels[0]->load(loadData(0x15B3));
+	_channels[1]->load(loadData(0x163A));
+	_channels[2]->load(loadData(0x1AE1));
+	_channels[3]->load(loadData(0x1613));
+	_channels[8]->load(loadData(0x1BE7));
+	_channels[4]->load(loadData(0x1C8F));
+	_channels[5]->load(loadData(0x1E1B));
+	_channels[6]->load(loadData(0x205F));
+}
+
+int ASound9::command42() {
+	if (isMusicChannelsActive())
+		scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand42));
+	else
+		loadCommand42();
 	return 0;
 }
 
-int ASound9::command36() {
+// ---------------------------------------------------------------------------
+// Music command 43 — direct load (no deferred path)
+// ---------------------------------------------------------------------------
+
+int ASound9::command43() {
 	ASound::command1();
-	_channels[0]->load(loadData(0x30AA));
-	_channels[1]->load(loadData(0x30DD));
-	_channels[2]->load(loadData(0x3109));
-	_channels[3]->load(loadData(0x313D));
-	_channels[4]->load(loadData(0x3175));
-	_channels[5]->load(loadData(0x319B));
+	resetCallbackTimer(0x60);
+	_channels[0]->load(loadData(0x4A00));
+	_channels[1]->load(loadData(0x4A5F));
+	_channels[2]->load(loadData(0x4AAB));
+	_channels[3]->load(loadData(0x4D7D));
+	_channels[4]->load(loadData(0x4D93));
+	_channels[5]->load(loadData(0x4D9E));
+	_channels[6]->load(loadData(0x4DA9));
+	_channels[7]->load(loadData(0x4DB2));
 	return 0;
 }
 
-int ASound9::command37() {
+// ---------------------------------------------------------------------------
+// SFX commands 45, 46
+// ---------------------------------------------------------------------------
+
+int ASound9::command45() {
+	playSound(0x48AA);
+	playSound(0x490A);
+	return 0;
+}
+
+int ASound9::command46() {
+	playSound(0x4955);
+	playSound(0x49AA);
+	return 0;
+}
+
+// ---------------------------------------------------------------------------
+// SFX commands 48, 49, 50
+// ---------------------------------------------------------------------------
+
+int ASound9::command48() { playSound(0x54C3); return 0; }
+int ASound9::command49() { playSound(0x54CD); return 0; }
+int ASound9::command50() { playSound(0x54D7); return 0; }
+
+// ---------------------------------------------------------------------------
+// Music command 51 — deferred loader (timer set before command1)
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand51() {
+	resetCallbackTimer(0x60);
 	ASound::command1();
-	_channels[0]->load(loadData(0x2156));
-	_channels[1]->load(loadData(0x21A6));
-	_channels[2]->load(loadData(0x228E));
-	_channels[3]->load(loadData(0x22F7));
-	_channels[4]->load(loadData(0x2351));
-	_channels[5]->load(loadData(0x25A8));
-	_channels[6]->load(loadData(0x28BF));
+	_channels[0]->load(loadData(0x4DBE));
+	_channels[1]->load(loadData(0x4E56));
+	_channels[2]->load(loadData(0x4EDC));
+	_channels[3]->load(loadData(0x4F32));
+	_channels[4]->load(loadData(0x4F62));
+	_channels[5]->load(loadData(0x4F75));
+	_channels[6]->load(loadData(0x4F84));
+	_channels[7]->load(loadData(0x4F95));
+}
+
+int ASound9::command51() {
+	if (isMusicChannelsActive())
+		scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand51));
+	else
+		loadCommand51();
 	return 0;
 }
 
-int ASound9::command38() {
-	byte *pData = loadData(0x11BC);
+// ---------------------------------------------------------------------------
+// Music command 52 — isSoundActive guard + deferred loader
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand52() {
+	resetCallbackTimer(0x54);
+	ASound::command1();
+	_channels[0]->load(loadData(0x4FAA));
+	_channels[1]->load(loadData(0x5211));
+	_channels[2]->load(loadData(0x524A));
+	_channels[3]->load(loadData(0x526E));
+	_channels[4]->load(loadData(0x5469));
+	_channels[5]->load(loadData(0x5475));
+	_channels[6]->load(loadData(0x548E));
+}
+
+int ASound9::command52() {
+	byte *pData = loadData(0x4FAA);
 	if (!isSoundActive(pData)) {
-		ASound::command1();
-		_channels[0]->load(pData);
-		_channels[1]->load(loadData(0x1477));
-		_channels[2]->load(loadData(0x158D));
-		_channels[3]->load(loadData(0x1777));
-		_channels[4]->load(loadData(0x1977));
-		_channels[5]->load(loadData(0x1BC5));
-		_channels[6]->load(loadData(0x1CFF));
-		_channels[7]->load(loadData(0x1EAF));
+		if (isMusicChannelsActive())
+			scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand52));
+		else
+			loadCommand52();
 	}
 	return 0;
 }
 
-int ASound9::command39() {
-	byte *pData = loadData(0x0C36);
+// ---------------------------------------------------------------------------
+// Command 53 — delayed fade: arms a one-shot timer to call command1
+// ---------------------------------------------------------------------------
+
+void ASound9::command53_loader() {
+	ASound::command1();
+}
+
+int ASound9::command53() {
+	resetCallbackTimer(0x708);
+	scheduleCallback(MAKE_CALLBACK(ASound9, command53_loader));
+	return 0;
+}
+
+// ---------------------------------------------------------------------------
+// Music command 54 — isSoundActive guard + deferred loader
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand54() {
+	resetCallbackTimer(0x60);
+	ASound::command1();
+	_channels[0]->load(loadData(0x5548));
+	_channels[1]->load(loadData(0x555E));
+	_channels[2]->load(loadData(0x556F));
+	_channels[3]->load(loadData(0x558F));
+}
+
+int ASound9::command54() {
+	byte *pData = loadData(0x5548);
 	if (!isSoundActive(pData)) {
-		ASound::command0();
-		_channels[0]->load(pData);
-		_channels[1]->load(loadData(0x0D7D));
-		_channels[2]->load(loadData(0x0E50));
-		_channels[3]->load(loadData(0x0F1C));
-		_channels[4]->load(loadData(0x0FCE));
-		_channels[5]->load(loadData(0x10BA));
+		if (isMusicChannelsActive())
+			scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand54));
+		else
+			loadCommand54();
 	}
 	return 0;
 }
 
-int ASound9::command64() {
-	playSound(0x2EC6);
+// ---------------------------------------------------------------------------
+// Music command 55 — isSoundActive guard + deferred loader
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand55() {
+	resetCallbackTimer(0x60);
+	ASound::command1();
+	_channels[0]->load(loadData(0x5712));
+	_channels[1]->load(loadData(0x57EA));
+	_channels[2]->load(loadData(0x5A1E));
+	_channels[3]->load(loadData(0x5ACA));
+	_channels[4]->load(loadData(0x5AEE));
+	_channels[5]->load(loadData(0x5B4E));
+	_channels[6]->load(loadData(0x5BBB));
+	_channels[7]->load(loadData(0x5BC8));
+	_channels[8]->load(loadData(0x5BE0));
+}
+
+int ASound9::command55() {
+	byte *pData = loadData(0x5712);
+	if (!isSoundActive(pData)) {
+		if (isMusicChannelsActive())
+			scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand55));
+		else
+			loadCommand55();
+	}
 	return 0;
 }
 
-int ASound9::command65() {
-	playSound(0x2EDA);
+// ---------------------------------------------------------------------------
+// Music command 57 — isSoundActive guard + deferred loader
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand57() {
+	resetCallbackTimer(0x30);
+	ASound::command1();
+	_channels[0]->load(loadData(0x5CD6));
+	_channels[1]->load(loadData(0x5D5E));
+	_channels[2]->load(loadData(0x5DD1));
+	_channels[3]->load(loadData(0x5E0F));
+	_channels[4]->load(loadData(0x5E54));
+	_channels[5]->load(loadData(0x5F36));
+	_channels[6]->load(loadData(0x5F97));
+	_channels[7]->load(loadData(0x5FEC));
+	_channels[8]->load(loadData(0x5ECD));
+}
+
+int ASound9::command57() {
+	byte *pData = loadData(0x5CD6);
+	if (!isSoundActive(pData)) {
+		if (isMusicChannelsActive())
+			scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand57));
+		else
+			loadCommand57();
+	}
 	return 0;
 }
 
-int ASound9::command66() {
-	_channels[0]->load(loadData(0x2EE4));
-	_channels[1]->load(loadData(0x2F0E));
-	_channels[2]->load(loadData(0x2F3E));
-	_channels[3]->load(loadData(0x2F6E));
-	_channels[4]->load(loadData(0x2EE4));
-	_channels[5]->load(loadData(0x2F0E));
-	_channels[6]->load(loadData(0x2F3E));
-	_channels[7]->load(loadData(0x2F6E));
-	return 0;
+// ---------------------------------------------------------------------------
+// Music command 58 — music-channel isSoundActive guard + deferred loader
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand58() {
+	resetCallbackTimer(0x90);
+	ASound::command1();
+	_channels[0]->load(loadData(0x5BEE));
+	_channels[1]->load(loadData(0x5C23));
+	_channels[2]->load(loadData(0x5C3E));
+	_channels[3]->load(loadData(0x5C7E));
+	_channels[4]->load(loadData(0x5C9D));
+	_channels[5]->load(loadData(0x5CA8));
+	_channels[6]->load(loadData(0x5CC8));
 }
 
-int ASound9::command67() {
-	_channels[6]->load(loadData(0x2F9E));
-	_channels[7]->load(loadData(0x2FBD));
-	_channels[8]->load(loadData(0x2FCC));
+int ASound9::command58() {
+	byte *pData = loadData(0x5BEE);
+	if (!isSoundActive(pData)) {
+		if (isMusicChannelsActive())
+			scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand58));
+		else
+			loadCommand58();
+	}
 	return 0;
 }
 
-int ASound9::command68() {
-	playSound(0x2FEB);
+// ---------------------------------------------------------------------------
+// Music command 59 — direct load (no deferred, no timer)
+// ---------------------------------------------------------------------------
+
+int ASound9::command59() {
+	ASound::command1();
+	_channels[0]->load(loadData(0x55AE));
+	_channels[1]->load(loadData(0x5637));
+	_channels[2]->load(loadData(0x5621));
+	_channels[3]->load(loadData(0x56DF));
 	return 0;
 }
 
-int ASound9::command69() {
-	playSound(0x2FF5);
-	playSound(0x301B);
-	playSound(0x3041);
+// ---------------------------------------------------------------------------
+// Command 61 — stop SFX channels then play three SFX voices
+// ---------------------------------------------------------------------------
+
+int ASound9::command61() {
+	ASound::command5();
+	playSound(0x54E1);
+	playSound(0x5507);
+	playSound(0x552D);
 	return 0;
 }
 
-int ASound9::command70() {
-	playSound(0x305B);
-	playSound(0x3064);
+// ---------------------------------------------------------------------------
+// Music command 62 — deferred loader (channels 0-2, 5-6 only)
+// ---------------------------------------------------------------------------
+
+void ASound9::loadCommand62() {
+	ASound::command1();
+	resetCallbackTimer(0x40);
+	_channels[0]->load(loadData(0x6006));
+	_channels[1]->load(loadData(0x6204));
+	_channels[2]->load(loadData(0x6409));
+	_channels[5]->load(loadData(0x660C));
+	_channels[6]->load(loadData(0x667F));
+}
+
+int ASound9::command62() {
+	if (isMusicChannelsActive())
+		scheduleCallback(MAKE_CALLBACK(ASound9, loadCommand62));
+	else
+		loadCommand62();
 	return 0;
 }
 
-int ASound9::command71() {
-	playSound(0x306D);
-	playSound(0x308A);
+// ---------------------------------------------------------------------------
+// SFX command 63
+// ---------------------------------------------------------------------------
+
+int ASound9::command63() {
+	playSound(0x66EC);
 	return 0;
 }
 
diff --git a/engines/mads/madsv2/dragonsphere/sound_dragonsphere.h b/engines/mads/madsv2/dragonsphere/sound_dragonsphere.h
index 0476d3632be..57fba442974 100644
--- a/engines/mads/madsv2/dragonsphere/sound_dragonsphere.h
+++ b/engines/mads/madsv2/dragonsphere/sound_dragonsphere.h
@@ -42,37 +42,53 @@ public:
 };
 
 /**
- * ASound1  (asound.ph1, _dataOffset = 0x21e0)
+ * ASound1  (asound.dr1, _dataOffset = 0x2520, _dataSize = 0x49e0)
  *
- * Dispatch table layout:
- *   off_11C32: commands  0–8   (max=8,    base=0)
- *   off_11C44: command   16    (max=0x10, base=0x10, 1 entry)
- *   off_11C46: commands 24–27  (max=0x1B, base=0x18, 4 entries)
- *   off_11C4E: commands 32–39  (max=0x27, base=0x20, 8 entries)
+ * Dispatch table layout (five tables collapsed to flat [102]):
+ *   off_11A14:   commands  0– 8  (base=0,    max=8)
+ *   off_11A26:   commands 16–19  (base=0x10, max=0x13; slot 19 = no-op)
+ *   off_11A2E:   commands 24–32  (base=0x18, max=0x20; slot 32 = no-op)
+ *   funcs_12251: commands 32–49  (base=0x20, max=0x31; slot 49 = no-op)
+ *   off_11A64:   commands 64–101 (base=0x40, max=0x65)
+ *     Slot 92 and slot 98 are no-ops (command98); slots 102–103 are
+ *     nullsub_1/nullsub_4, both beyond the [102] array.
  *
- * A fifth table (unk_13C3E, commands 64–76) exists but is encoded as raw
- * sound data bytes used as near-pointers — not reconstructible without the
- * binary.  Those commands are silently ignored.
+ * word_12370 (_musicIndex in base): tracks the last music-piece launched
+ * via command18 for re-entry.  Values <=0x12 use off_11A26; >0x12 use
+ * funcs_12251 with index = musicIndex - 0x20.
  *
- * command16 (sub_11F70): random background-music selector.  Checks whether
- * channel 0 is already playing one of the five known music pieces; if not,
- * randomly picks from four music loaders and plays it, storing the choice
- * in _musicIndex (mirrors word_11F5E in the original).
+ * Mutable sound-data bytes (modified before channel loads):
+ *   _soundData[0x28C9] — pitch-bend variant byte (command43 / command48)
+ *   _soundData[0x4680] — transposition byte for command31's block
  */
 class ASound1 : public ASound {
 private:
 	typedef int (ASound1::*CommandPtr)();
-	static const CommandPtr _commandList[40];
-
-	// Mirrors word_11F5E: tracks which music piece was last selected.
-	int _musicIndex = 0;
-
-	// Background-music loaders (targets of the CS:0x1F60 indirect table).
-	int commandMusic0();   // sub_11D84  – starts at 0x1ECA
-	int commandMusic1();   // sub_11EE6  – starts at 0x3418
-	int commandMusic2();   // sub_11F0E  – starts at 0x3688
-	int commandMusic3();   // sub_11F36  – starts at 0x3D52
-
+	static const CommandPtr _commandList[102];
+
+	// --------------- deferred-load callbacks (void, no return) ------------
+	// Stored via reinterpret_cast<CallbackFunction> and fired by tickCallback.
+	void loadCommand16();
+	void loadCommand32();
+	void loadCommand33();
+	void loadCommand34();
+	void loadCommand35();
+	void loadCommand36();
+	void loadCommand37();
+	void loadCommand38();
+	void loadCommand39();
+	void loadCommand40();
+	void loadCommand41();
+	void loadCommand42();
+	void loadCommand44();
+	void loadCommand45();
+	void loadCommand46();
+	void loadCommand47();
+
+	// Shared pitch-bend loader called directly from command43 and command48.
+	void loadMusicPitchBend();
+
+	// --------------- command handlers (int, return 0) ----------------------
 	int command0();
 	int command1();
 	int command2();
@@ -84,11 +100,17 @@ private:
 	int command8();
 
 	int command16();
+	int command17();
+	int command18();
 
 	int command24();
 	int command25();
 	int command26();
 	int command27();
+	int command28();
+	int command29();
+	int command30();
+	int command31();
 
 	int command32();
 	int command33();
@@ -98,6 +120,52 @@ private:
 	int command37();
 	int command38();
 	int command39();
+	int command40();
+	int command41();
+	int command42();
+	int command43();
+	int command44();
+	int command45();
+	int command46();
+	int command47();
+	int command48();
+
+	int command64();
+	int command65();
+	int command66();
+	int command67();
+	int command68();
+	int command69();
+	int command70();
+	int command71();
+	int command72();
+	int command73();
+	int command74();
+	int command75();
+	int command76();
+	int command77();
+	int command78();
+	int command79();
+	int command80();
+	int command81();
+	int command82();
+	int command83();
+	int command84();
+	int command85();
+	int command86();
+	int command87();
+	int command88();
+	int command89();
+	int command90();
+	int command91();
+	int command93();
+	int command94();
+	int command95();
+	int command96();
+	int command97();
+	int command99();
+	int command100();
+	int command101();
 
 public:
 	ASound1(Audio::Mixer *mixer, OPL::OPL *opl);
@@ -106,51 +174,45 @@ public:
 };
 
 /**
- * ASound2  (asound.ph2, _dataOffset = 0x2040)
+ * ASound2  (asound.dr2, _dataOffset = 0x1FA0, _dataSize = 0x2950)
  *
- * Dispatch table layout:
- *   asound_commands1: commands  0–8   (max=8,    base=0)
- *   asound_commands2: command   16    (max=0x10, base=0x10, 1 entry)
- *   asound_commands3: commands 24–27  (max=0x1B, base=0x18, 4 entries)
- *   asound_commands4: commands 32–35  (max=0x23, base=0x20, 4 entries)
- *   asound_commands5: commands 64–72  (max=0x48, base=0x40, 9 entries)
+ * Dispatch table layout (five tables collapsed to flat [76]):
+ *   off_11A14:   commands  0–8   (base=0,    max=8)
+ *   off_11A26:   commands 16–19  (base=0x10, max=0x13; slot 19 = no-op)
+ *   off_11A2E:   commands 24–31  (base=0x18, max=0x1F; slot at cmd32 unreachable)
+ *   funcs_11C87: commands 32–36  (base=0x20, max=0x24; slot 36 = no-op)
+ *   off_11A4A:   commands 64–75  (base=0x40, max=0x4B; slots 73–75 = no-ops)
+ *
+ * command16 sets _musicIndex = 0x10 for command18 re-entry.
+ * commands 32–35: _musicIndex saved by dispatcher for command18 re-entry.
  */
 class ASound2 : public ASound {
 private:
 	typedef int (ASound2::*CommandPtr)();
-	static const CommandPtr _commandList[73];
+	static const CommandPtr _commandList[76];
 
-	int command0();
-	int command1();
-	int command2();
-	int command3();
-	int command4();
-	int command5();
-	int command6();
-	int command7();
+	// Deferred loader callbacks (void, Pattern B)
+	void loadCommand16();
+	void loadCommand32();
+	void loadCommand33();
+	void loadCommand34();
+	void loadCommand35();
+
+	int command0(); int command1(); int command2(); int command3();
+	int command4(); int command5(); int command6(); int command7();
 	int command8();
 
 	int command16();
+	int command17();
+	int command18();
 
-	int command24();
-	int command25();
-	int command26();
-	int command27();
+	int command24(); int command25(); int command26(); int command27();
+	int command28(); int command29(); int command30(); int command31();
 
-	int command32();
-	int command33();
-	int command34();
-	int command35();
+	int command32(); int command33(); int command34(); int command35();
 
-	int command64();
-	int command65();
-	int command66();
-	int command67();
-	int command68();
-	int command69();
-	int command70();
-	int command71();
-	int command72();
+	int command64(); int command65(); int command66(); int command67();
+	int command68(); int command69_70(); int command71(); int command72();
 
 public:
 	ASound2(Audio::Mixer *mixer, OPL::OPL *opl);
@@ -362,49 +424,49 @@ public:
 class ASound9 : public ASound {
 private:
 	typedef int (ASound9:: *CommandPtr)();
-	int command0();
-	int command1();
-	int command2();
-	int command3();
-	int command4();
-	int command5();
-	int command6();
-	int command7();
-	int command8();
-
-	int command16() {
-		return command24();
-	}
-
-	int command24();
-	int command25();
-	int command26();
-	int command27();
 
-	int command32();
-	int command33();
-	int command34();
-	int command35();
-	int command36();
-	int command37();
-	int command38();
-	int command39();
+	// Deferred loader helpers (void callbacks, Pattern B)
+	void loadCommand32();
+	void loadCommand33_47();
+	void loadCommand34();
+	void loadCommand35();
+	void loadCommand36();
+	void loadCommand37();
+	void loadCommand38();
+	void loadCommand39();
+	void loadCommand40();
+	void loadCommand41();
+	void loadCommand42();
+	void loadCommand51();
+	void loadCommand52();
+	void command53_loader();
+	void loadCommand54();
+	void loadCommand55();
+	void loadCommand57();
+	void loadCommand58();
+	void loadCommand62();
+
+	// Commands 0-8: delegate to base
+	int command0(); int command1(); int command2(); int command3();
+	int command4(); int command5(); int command6(); int command7();
+	int command8();
 
-	int command64();
-	int command65();
-	int command66();
-	int command67();
-	int command68();
-	int command69();
-	int command70();
-	int command71();
+	// Music commands 32-63
+	int command32(); int command33_47(); int command34(); int command35();
+	int command36(); int command37(); int command38(); int command39();
+	int command40(); int command41(); int command42(); int command43();
+	int command45(); int command46();
+	int command48(); int command49(); int command50();
+	int command51(); int command52(); int command53();
+	int command54(); int command55();
+	int command57(); int command58(); int command59();
+	int command61(); int command62(); int command63();
 
-	static const CommandPtr _commandList[72];
+	static const CommandPtr _commandList[65];
 
 public:
 	ASound9(Audio::Mixer *mixer, OPL::OPL *opl);
-	~ASound9() override {
-	}
+	~ASound9() override {}
 	int command(int commandId, int param) override;
 };
 


Commit: 60143443a6daa427a2f05ab8b842fde6d96c0393
    https://github.com/scummvm/scummvm/commit/60143443a6daa427a2f05ab8b842fde6d96c0393
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2026-05-25T09:42:29+10:00

Commit Message:
MADS: DRAGONSPHERE: Add missing entries to the words.h enum

Changed paths:
    engines/mads/madsv2/dragonsphere/mads/words.h


diff --git a/engines/mads/madsv2/dragonsphere/mads/words.h b/engines/mads/madsv2/dragonsphere/mads/words.h
index 41c8190dfee..04a219259ee 100644
--- a/engines/mads/madsv2/dragonsphere/mads/words.h
+++ b/engines/mads/madsv2/dragonsphere/mads/words.h
@@ -29,99 +29,107 @@ namespace MADSV2 {
 namespace Dragonsphere {
 
 enum {
-	words_look                 =   3,
-	words_take                 =   4,
-	words_push                 =   5,
-	words_open                 =   6,
-	words_put                  =   7,
-	words_talk_to              =   8,
-	words_give                 =   9,
-	words_pull                 =  10,
-	words_close                =  11,
-	words_throw                =  12,
-	words_walk_to              =  13,
-	words_floor                =  16,
-	words_walk_across          =  17,
-	words_rug                  =  18,
-	words_carpet               =  19,
-	words_wall                 =  20,
-	words_bed                  =  21,
-	words_pillow               =  22,
-	words_chest                =  23,
-	words_window               =  24,
-	words_nightstand           =  25,
-	words_tapestry             =  26,
-	words_dressing_screen      =  27,
-	words_walk_behind          =  28,
-	words_royal_crest          =  29,
-	words_look_at              =  30,
-	words_bedroom_wall         =  31,
-	words_castle_walls         =  32,
-	words_book                 =  33,
-	words_fireplace            =  34,
-	words_door_to_queens_room  =  36,
-	words_fireplace_screen     =  35,
-	words_exit                 =  36,
-	words_walk_through         =  37,
-	words_hall_to_south        =  38,
-	words_walk_into            =  39,
-	words_wall_plaque          =  40,
-	words_decoration           =  41,
-	words_swords               =  42,
-	words_bust                 =  44,
-	words_arch                 =  45,
-	words_signet_ring          =  46,
-	words_invoke               =  47,
-	words_polish               =  48,
-	words_bird_figurine        =  50,
-	words_rub                  =  51,
-	words_birdcall             =  52,
-	words_make_noise           =  54,
-	words_shieldstone          =  55,
-	words_sword                =  56,
-	words_attack               =  57,
-	words_carve_up             =  58,
-	words_goblet               =  59,
-	words_fill                 =  60,
-	words_drink_from           =  61,
-	words_bone                 =  62,
-	words_gnaw                 =  63,
-	words_fruit                =  64,
-	words_eat                  =  65,
-	words_doll                 =  66,
-	words_play_with            =  67,
-	words_heal                 =  68,
-	words_heal_self            =  70,
-	words_polystone            =  71,
-	words_mimic                =  72,
-	words_red_powerstone       =  73,
-	words_yellow_powerstone    =  74,
-	words_blue_powerstone      =  75,
-	words_key_crown            =  76,
-	words_wear                 =  77,
-	words_dates                =  78,
-	words_statue               =  79,
-	words_bottle_of_flies      =  80,
-	words_listen_to            =  81,
-	words_soul_egg             =  82,
-	words_break                =  83,
-	words_magic_belt           =  84,
-	words_adjust               =  85,
-	words_amulet               =  86,
-	words_thrust               =  87,
-	words_mud                  =  88,
-	words_feel                 =  89,
-	words_taste                =  90,
-	words_feathers             =  91,
-	words_torch                =  93,
-	words_wave                 =  94,
-	words_flask                =  95,
-	words_flask_full_of_acid   =  96,
-	words_pour                 =  98,
-	words_pour_contents_of     =  99,
+	words_look                 =  3,
+	words_take                 =  4,
+	words_push                 =  5,
+	words_open                 =  6,
+	words_put                  =  7,
+	words_talk_to              =  8,
+	words_give                 =  9,
+	words_pull                 = 10,
+	words_close                = 11,
+	words_throw                = 12,
+	words_walk_to              = 13,
+	words_nothing              =  14,
+	words_floor                = 16,
+	words_walk_across          = 17,
+	words_rug                  = 18,
+	words_carpet               = 19,
+	words_wall                 = 20,
+	words_bed                  = 21,
+	words_pillow               = 22,
+	words_chest                = 23,
+	words_window               = 24,
+	words_nightstand           = 25,
+	words_tapestry             = 26,
+	words_dressing_screen      = 27,
+	words_walk_behind          = 28,
+	words_royal_crest          = 29,
+	words_look_at              = 30,
+	words_bedroom_wall         = 31,
+	words_castle_walls         = 32,
+	words_book                 = 33,
+	words_fireplace            = 34,
+	words_fireplace_screen     = 35,
+	words_door_to_queens_room  = 36,
+	words_exit                 = 36,
+	words_walk_through         = 37,
+	words_hall_to_south        = 38,
+	words_walk_into            = 39,
+	words_wall_plaque          = 40,
+	words_decoration           = 41,
+	words_swords               = 42,
+	words_wall_sconce          = 43,
+	words_bust                 = 44,
+	words_arch                 = 45,
+	words_signet_ring          = 46,
+	words_invoke               = 47,
+	words_polish               = 48,
+	words_gangbang             = 49,
+	words_bird_figurine        = 50,
+	words_rub                  = 51,
+	words_birdcall             = 52,
+	words_use                  = 53,
+	words_make_noise           = 54,
+	words_shieldstone          = 55,
+	words_sword                = 56,
+	words_attack               = 57,
+	words_carve_up             = 58,
+	words_goblet               = 59,
+	words_fill                 = 60,
+	words_drink_from           = 61,
+	words_bone                 = 62,
+	words_gnaw                 = 63,
+	words_fruit                = 64,
+	words_eat                  = 65,
+	words_doll                 = 66,
+	words_play_with            = 67,
+	words_heal                 = 68,
+	words_heal_thyself         = 69,
+	words_heal_self            = 70,
+	words_polystone            = 71,
+	words_mimic                = 72,
+	words_red_powerstone       = 73,
+	words_yellow_powerstone    = 74,
+	words_blue_powerstone      = 75,
+	words_key_crown            = 76,
+	words_wear                 = 77,
+	words_dates                = 78,
+	words_statue               = 79,
+	words_bottle_of_flies      = 80,
+	words_listen_to            = 81,
+	words_soul_egg             = 82,
+	words_break                = 83,
+	words_magic_belt           = 84,
+	words_adjust               = 85,
+	words_amulet               = 86,
+	words_thrust               = 87,
+	words_mud                  = 88,
+	words_feel                 = 89,
+	words_taste                = 90,
+	words_feathers             = 91,
+	words_tickle               = 92,
+	words_torch                = 93,
+	words_wave                 = 94,
+	words_flask                = 95,
+	words_flask_full_of_acid   = 96,
+	words_pour_contents        = 97,
+	words_pour                 = 98,
+	words_pour_contents_of     = 99,
 	words_drink                = 100,
 	words_rope                 = 101,
 	words_tie                  = 102,
+	words_power_vacuum_stone   = 103,
 	words_take_magic_from      = 104,
 	words_dead_rat             = 105,
 	words_pet                  = 106,
@@ -133,6 +141,7 @@ enum {
 	words_black_sphere         = 112,
 	words_soptus_soporific     = 113,
 	words_shifter_ring         = 114,
+	words_shift_self           = 115,
 	words_shift_into_bear      = 116,
 	words_shift_into_seal      = 117,
 	words_shift_into_snake     = 118,
@@ -149,33 +158,55 @@ enum {
 	words_rare_coin            = 129,
 	words_admire               = 130,
 	words_crystal_flower       = 131,
+	words_diamond_dust         = 132,
+	words_ruby_ring            = 133,
 	words_gold_nugget          = 134,
+	words_magic_music_box      = 135,
 	words_emerald              = 136,
+	words_piece_of_paper       = 137,
 	words_speak_words_on       = 138,
 	words_vortex_stone         = 139,
+	words_bust_on_wall         = 142,
+	words_dragon               = 144,
 	words_touch                = 146,
 	words_throne_room          = 147,
+	words_return_to            = 148,
+	words_cave                 = 149,
 	words_passageway_to_west   = 150,
 	words_passageway_to_east   = 151,
 	words_cave_floor           = 152,
+	words_stone_column         = 153,
+	words_abyss                = 154,
 	words_look_into            = 155,
 	words_castle               = 156,
+	words_castle_gate          = 157,
 	words_ground               = 158,
+	words_barrel               = 159,
+	words_barrels              = 160,
+	words_haystack             = 161,
+	words_root_through         = 162,
+	words_battlements          = 163,
+	words_gate_to_throne_room  = 164,
+	words_castle_wall          = 165,
 	words_door                 = 166,
 	words_wall_switch          = 167,
 	words_stairs               = 168,
-	words_cave                 = 149,
-	words_abyss                = 154,
 	words_walk_down            = 169,
 	words_edge_of_abyss        = 170,
+	words_courtyard            = 171,
 	words_rock                 = 172,
 	words_cave_ceiling         = 173,
 	words_cave_wall            = 174,
 	words_brazier              = 175,
 	words_door_to_throne_room  = 176,
+	words_go_through           = 177,
 	words_dining_table         = 178,
 	words_activate             = 179,
+	words_battlement           = 180,
+	words_door_to_gaurdroom    = 181,
 	words_dungeon_floor        = 182,
+	words_dungeon_walls        = 183,
+	words_dungeon_ceiling      = 184,
 	words_bedding              = 185,
 	words_floor_grate          = 186,
 	words_manacles             = 187,
@@ -185,18 +216,26 @@ enum {
 	words_guard_station        = 191,
 	words_door_to_dungeon_cell = 192,
 	words_doorway_to_cell      = 193,
+	words_dungeon_door         = 194,
 	words_dungeon_wall         = 195,
 	words_ceiling              = 196,
 	words_door_to_hallway      = 197,
 	words_table                = 198,
 	words_bookshelf            = 199,
+	words_trophy               = 200,
+	words_reading_bench        = 201,
 	words_chair                = 202,
+	words_loveseat             = 203,
 	words_basket               = 204,
+	words_stool                = 205,
 	words_guard_stool          = 206,
 	words_rocks                = 207,
 	words_dividing_wall        = 208,
+	words_archway              = 209,
+	words_market_grounds       = 210,
 	words_hedge                = 211,
 	words_sky                  = 212,
+	words_plains               = 213,
 	words_fields               = 214,
 	words_gate_to_courtyard    = 215,
 	words_road_to_east         = 216,
@@ -204,9 +243,13 @@ enum {
 	words_clouds               = 218,
 	words_merchant_s_stall     = 219,
 	words_well                 = 220,
+	words_down_well            = 221,
+	words_go                   = 222,
+	words_go_down              = 223,
 	words_crank                = 224,
 	words_bucket               = 225,
 	words_jump_down            = 226,
+	words_walls                = 227,
 	words_doorway_to_south     = 228,
 	words_pedestal             = 229,
 	words_door_to_north        = 230,
@@ -219,7 +262,15 @@ enum {
 	words_candlestick          = 237,
 	words_desk                 = 238,
 	words_turn                 = 239,
+	words_pole                 = 240,
+	words_the_scene            = 241,
+	words_leave                = 242,
+	words_end_table            = 243,
+	words_battle_axes          = 244,
 	words_door_to_king_s_room  = 245,
+	words_coat_of_arms         = 246,
+	words_large_window         = 247,
+	words_small_window         = 248,
 	words_door_to_meeting_room = 249,
 	words_door_to_ballroom     = 250,
 	words_flowers              = 251,
@@ -228,21 +279,29 @@ enum {
 	words_door_to_courtyard    = 254,
 	words_platform             = 255,
 	words_step                 = 256,
+	words_red_carpet           = 257,
 	words_king_s_throne        = 258,
+	words_sit_in               = 259,
 	words_queen_s_throne       = 260,
+	words_trapdoor             = 261,
 	words_grate                = 262,
 	words_river                = 263,
 	words_diaries              = 264,
 	words_swim_down            = 265,
 	words_scullery_maid        = 266,
+	words_doorway_to_dungeon   = 267,
 	words_ward                 = 268,
 	words_darkness_beast       = 269,
 	words_beast                = 270,
 	words_put_magic_into       = 271,
 	words_guard                = 272,
 	words_crown                = 273,
+	words_books                = 274,
+	words_secret_door          = 275,
+	words_wall_panel           = 276,
 	words_doorway              = 277,
 	words_faerie               = 278,
+	words_soptus_ecliptus2     = 279,
 	words_guard_captain        = 280,
 	words_merchant             = 281,
 	words_shapechanger         = 282,
@@ -255,54 +314,83 @@ enum {
 	words_soporific            = 289,
 	words_parchment            = 290,
 	words_king                 = 291,
+	words_macmorn              = 292,
 	words_mountainside         = 293,
 	words_path_to_south        = 294,
 	words_rough_stone          = 295,
 	words_climb_up             = 296,
 	words_large_rock           = 297,
 	words_small_rock           = 298,
-	words_puddle               = 315,
-	words_boulders             = 320,
 	words_path_to_west         = 299,
 	words_cave_entrance        = 300,
 	words_pallet               = 301,
 	words_blanket              = 302,
 	words_firepit              = 303,
 	words_flat_stone           = 304,
+	words_move                 = 305,
 	words_spirit_bundle        = 306,
+	words_trail_leading_up     = 307,
 	words_follow               = 308,
+	words_trail_leading_down   = 309,
 	words_nest                 = 310,
+	words_reach_in             = 311,
+	words_trail_leading_west   = 312,
 	words_path_to_east         = 313,
 	words_waterfall            = 314,
+	words_puddle               = 315,
 	words_edge_of_cliff        = 316,
 	words_ledge                = 317,
 	words_climb_down           = 318,
 	words_landing              = 319,
-	words_sconce               = 329,
+	words_boulders             = 320,
 	words_rock_tumble          = 321,
 	words_rock_tree            = 322,
 	words_pillar               = 323,
 	words_jump_to              = 324,
+	words_cliff                = 325,
+	words_pillars              = 326,
+	words_special_rock         = 327,
+	words_gaze_upon            = 328,
+	words_sconce               = 329,
+	words_ladder               = 330,
 	words_stairway             = 331,
 	words_mechanism            = 332,
 	words_spearheads           = 333,
 	words_trap_door            = 334,
+	words_swim                 = 335,
+	words_down_river           = 336,
 	words_swim_up              = 337,
+	words_up_river             = 338,
 	words_shore                = 339,
 	words_swim_to              = 340,
 	words_swim_towards         = 341,
+	words_King_Callash         = 342,
+	words_King_s_throne        = 343,
+	words_King                 = 344,
 	words_grotto               = 345,
 	words_climb_through        = 346,
 	words_Queen_Mother         = 347,
+	words_Macmorn              = 348,
+	words_small_ledge          = 349,
 	words_MacMorn              = 350,
+	words_to_110               = 351,
+	words_to_kitty_heaven      = 352,
+	words_kitty_heaven         = 353,
+	words_room_110             = 354,
 	words_Llanie               = 355,
+	words_cw                   = 356,
 	words_hermit               = 357,
+	words_trou                 = 358,
 	words_shak                 = 359,
+	words_room_501             = 360,
 	words_moon                 = 361,
-	words_eye                  = 368,
+	words_ufo                  = 362,
 	words_sit_on               = 363,
 	words_stranger             = 364,
 	words_tower_door           = 365,
+	words_door_to_east         = 366,
+	words_door_to_west         = 367,
+	words_eye                  = 368,
 	words_doorway_to_east      = 369,
 	words_doorway_to_west      = 370,
 	words_skull                = 371,
@@ -315,6 +403,7 @@ enum {
 	words_petcock              = 378,
 	words_nozzle               = 379,
 	words_tubing               = 380,
+	words_beaker               = 381,
 	words_flame                = 382,
 	words_metal_plate          = 383,
 	words_shaft_of_light       = 384,
@@ -323,15 +412,18 @@ enum {
 	words_cage                 = 387,
 	words_freezer              = 388,
 	words_contents_of_freezer  = 389,
+	words_cage_with_rats       = 390,
 	words_door_to_south        = 391,
 	words_trail_of_green_slime = 392,
 	words_neck_lock            = 393,
 	words_bench                = 394,
 	words_skeleton             = 395,
+	words_leg_clamps           = 396,
 	words_leg_lock             = 397,
 	words_waist_lock           = 398,
 	words_Ner_Tom              = 399,
 	words_belt                 = 400,
+	words_jump_into            = 401,
 	words_closet               = 402,
 	words_door_to_eye_chamber  = 403,
 	words_dresser              = 404,
@@ -348,6 +440,7 @@ enum {
 	words_large_spider_web     = 415,
 	words_infernal_machine     = 416,
 	words_water_source         = 417,
+	words_flow_of_water        = 418,
 	words_retort               = 419,
 	words_doorway_to_corridor  = 420,
 	words_telescope            = 421,
@@ -357,9 +450,15 @@ enum {
 	words_dragon_sculpture     = 425,
 	words_rat                  = 426,
 	words_rat_cage             = 427,
+	words_down_button          = 428,
+	words_door_to_machine_room = 429,
+	words_strange_portal       = 430,
 	words_door_frame           = 431,
+	words_elevator_hole        = 432,
+	words_up_button            = 433,
 	words_doorway_to_dark_room = 434,
 	words_big_skull            = 435,
+	words_elevator_platform    = 436,
 	words_button               = 437,
 	words_top_button           = 438,
 	words_bottom_button        = 439,
@@ -367,18 +466,35 @@ enum {
 	words_eye_chamber_doorway  = 441,
 	words_machine_room_doorway = 442,
 	words_glowing_floor        = 443,
+	words_Brynn_Fann           = 444,
+	words_Gran_Callahach       = 445,
+	words_Slathan_Ni_Patan     = 446,
+	words_Hightower            = 447,
 	words_Soptus_Ecliptus      = 448,
 	words_tower                = 449,
+	words_mountain_path        = 450,
 	words_path_behind_tower    = 451,
+	words_vines                = 452,
 	words_Slathan_ni_Patan     = 453,
+	words_sick                 = 454,
 	words_path_around_tower    = 455,
+	words_path_to_Hightower    = 456,
 	words_spirit_plane         = 457,
 	words_spirit_tree          = 458,
+	words_walk                 = 459,
 	words_remains              = 460,
+	words_doorway_to_elevator  = 461,
 	words_dragon_door          = 462,
 	words_dragon_bones         = 463,
 	words_iron_floor           = 464,
 	words_hole                 = 465,
+	words_stone                = 466,
+	words_homemade_bundle      = 467,
+	words_left                 = 468,
+	words_right                = 469,
+	words_up                   = 470,
+	words_down                 = 471,
+	words_west                 = 472,
 	words_desert_to_west       = 473,
 	words_cross                = 474,
 	words_desert_to_east       = 475,
@@ -393,9 +509,15 @@ enum {
 	words_tangle               = 484,
 	words_sand                 = 485,
 	words_magic_grapes         = 486,
+	words_roc_s_nest           = 487,
 	words_grape_vine           = 488,
+	words_strange_square       = 489,
+	words_water_sphere         = 490,
 	words_east_end_of_island   = 491,
+	words_island               = 492,
 	words_secret_message       = 493,
+	words_find                 = 494,
+	words_sand_near_stones     = 495,
 	words_desert_sky           = 496,
 	words_pool                 = 497,
 	words_palm_tree            = 498,
@@ -407,6 +529,7 @@ enum {
 	words_floating_disk        = 504,
 	words_gnarled_root         = 505,
 	words_snake_pit            = 506,
+	words_marker               = 507,
 	words_shaman               = 508,
 	words_guardhouse           = 509,
 	words_bone_tree            = 510,
@@ -418,10 +541,19 @@ enum {
 	words_Roc                  = 516,
 	words_Roc_s_nest           = 517,
 	words_select               = 518,
+	words_purple_gem           = 519,
 	words_purple_stone         = 520,
 	words_green_stone          = 521,
 	words_path                 = 522,
 	words_guards               = 523,
+	words_lamp                 = 524,
+	words_scimitar             = 525,
+	words_sitting_pillow       = 526,
+	words_water_gourd          = 527,
+	words_jar                  = 528,
+	words_tent_pole            = 529,
+	words_paraphernalia        = 531,
+	words_slathan_ni_patan2    = 532,
 	words_eye_rock             = 533,
 	words_body_tree            = 534,
 	words_clearing             = 535,
@@ -432,7 +564,9 @@ enum {
 	words_dead_tree            = 540,
 	words_pit                  = 541,
 	words_shifter_village      = 542,
+	words_slathan_sky2         = 543,
 	words_shifter              = 544,
+	words_walk_around          = 545,
 	words_wrecked_bridge       = 546,
 	words_shack                = 547,
 	words_wrecked_shack        = 548,
@@ -447,6 +581,7 @@ enum {
 	words_tree_stump           = 557,
 	words_sanctuary_woods      = 558,
 	words_toads                = 559,
+	words_doofus               = 560,
 	words_shifting_monster     = 561,
 	words_sprite               = 562,
 	words_maze                 = 563,
@@ -456,8 +591,9 @@ enum {
 	words_guardian             = 567,
 	words_Butterfly_King       = 568,
 	words_robe                 = 569,
+	words_cedar_chest          = 570,
 	words_Dragonsphere         = 571,
-	words_path_to_Hightower    = 572
+	words_Caliph               = 573,
 };
 
 } // namespace Dragonsphere


Commit: 2650d9aa0f2f3deff794dba3f3f605e21a31097f
    https://github.com/scummvm/scummvm/commit/2650d9aa0f2f3deff794dba3f3f605e21a31097f
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2026-05-25T09:42:29+10:00

Commit Message:
MADS: DRAGONSPHERE: Cleanups and fixes for room 104

Changed paths:
    engines/mads/madsv2/dragonsphere/mads/conv.h
    engines/mads/madsv2/dragonsphere/mads/words.h
    engines/mads/madsv2/dragonsphere/rooms/room103.cpp
    engines/mads/madsv2/dragonsphere/rooms/room104.cpp


diff --git a/engines/mads/madsv2/dragonsphere/mads/conv.h b/engines/mads/madsv2/dragonsphere/mads/conv.h
index 018ed111345..eaad7c58914 100644
--- a/engines/mads/madsv2/dragonsphere/mads/conv.h
+++ b/engines/mads/madsv2/dragonsphere/mads/conv.h
@@ -32,32 +32,42 @@ namespace Dragonsphere {
 // Each conversation gets its own enum block named after its .CON file.
 
 enum {
-	conv002_counter_only = 0,
-	conv002_banter_random = 1,
-	conv002_flirt_rand = 2
+	conv001_exit_b_b         = 10,
+	conv001_bear_b_b         = 13,
+	conv001_unbear_b_b       = 16,
+	conv001_sixteen_b_b      = 19,
+	conv001_pre_25_b_b       = 30,
+	conv001_twentyfive       = 31,
+	conv001_end_b_b          = 38
 };
 
 enum {
-	conv003_replies_b_b = 2,
-	conv003_exit_b_b = 8,
-	conv003_replies_defeat = 5
+	conv002_counter_only     =  0,
+	conv002_banter_random    =  1,
+	conv002_flirt_rand       =  2
 };
 
 enum {
-	conv004_seen_only = 0,
-	conv004_resolved_only = 1,
-	conv004_am_only = 2,
-	conv004_wait_only = 3,
-	conv004_thanks_only = 4,
-	conv004_nay_only = 5,
-	conv004_queen_only = 6,
-	conv004_fathers_only = 7,
-	conv004_last_only = 8,
-	conv004_exit_b_b = 10
+	conv003_replies_b_b      =  2,
+	conv003_exit_b_b         =  8,
+	conv003_replies_defeat   =  5
 };
 
 enum {
-	conv005_exit_b_b = 16
+	conv004_seen_only        =  0,
+	conv004_resolved_only    =  1,
+	conv004_am_only          =  2,
+	conv004_wait_only        =  3,
+	conv004_thanks_only      =  4,
+	conv004_nay_only         =  5,
+	conv004_queen_only       =  6,
+	conv004_fathers_only     =  7,
+	conv004_last_only        =  8,
+	conv004_exit_b_b         = 10
+};
+
+enum {
+	conv005_exit_b_b         = 16
 };
 
 enum {
diff --git a/engines/mads/madsv2/dragonsphere/mads/words.h b/engines/mads/madsv2/dragonsphere/mads/words.h
index 04a219259ee..a28f8c38afa 100644
--- a/engines/mads/madsv2/dragonsphere/mads/words.h
+++ b/engines/mads/madsv2/dragonsphere/mads/words.h
@@ -29,103 +29,103 @@ namespace MADSV2 {
 namespace Dragonsphere {
 
 enum {
-	words_look                 =  3,
-	words_take                 =  4,
-	words_push                 =  5,
-	words_open                 =  6,
-	words_put                  =  7,
-	words_talk_to              =  8,
-	words_give                 =  9,
-	words_pull                 = 10,
-	words_close                = 11,
-	words_throw                = 12,
-	words_walk_to              = 13,
+	words_look                 =   3,
+	words_take                 =   4,
+	words_push                 =   5,
+	words_open                 =   6,
+	words_put                  =   7,
+	words_talk_to              =   8,
+	words_give                 =   9,
+	words_pull                 =  10,
+	words_close                =  11,
+	words_throw                =  12,
+	words_walk_to              =  13,
 	words_nothing              =  14,
-	words_floor                = 16,
-	words_walk_across          = 17,
-	words_rug                  = 18,
-	words_carpet               = 19,
-	words_wall                 = 20,
-	words_bed                  = 21,
-	words_pillow               = 22,
-	words_chest                = 23,
-	words_window               = 24,
-	words_nightstand           = 25,
-	words_tapestry             = 26,
-	words_dressing_screen      = 27,
-	words_walk_behind          = 28,
-	words_royal_crest          = 29,
-	words_look_at              = 30,
-	words_bedroom_wall         = 31,
-	words_castle_walls         = 32,
-	words_book                 = 33,
-	words_fireplace            = 34,
-	words_fireplace_screen     = 35,
-	words_door_to_queens_room  = 36,
-	words_exit                 = 36,
-	words_walk_through         = 37,
-	words_hall_to_south        = 38,
-	words_walk_into            = 39,
-	words_wall_plaque          = 40,
-	words_decoration           = 41,
-	words_swords               = 42,
-	words_wall_sconce          = 43,
-	words_bust                 = 44,
-	words_arch                 = 45,
-	words_signet_ring          = 46,
-	words_invoke               = 47,
-	words_polish               = 48,
-	words_gangbang             = 49,
-	words_bird_figurine        = 50,
-	words_rub                  = 51,
-	words_birdcall             = 52,
-	words_use                  = 53,
-	words_make_noise           = 54,
-	words_shieldstone          = 55,
-	words_sword                = 56,
-	words_attack               = 57,
-	words_carve_up             = 58,
-	words_goblet               = 59,
-	words_fill                 = 60,
-	words_drink_from           = 61,
-	words_bone                 = 62,
-	words_gnaw                 = 63,
-	words_fruit                = 64,
-	words_eat                  = 65,
-	words_doll                 = 66,
-	words_play_with            = 67,
-	words_heal                 = 68,
-	words_heal_thyself         = 69,
-	words_heal_self            = 70,
-	words_polystone            = 71,
-	words_mimic                = 72,
-	words_red_powerstone       = 73,
-	words_yellow_powerstone    = 74,
-	words_blue_powerstone      = 75,
-	words_key_crown            = 76,
-	words_wear                 = 77,
-	words_dates                = 78,
-	words_statue               = 79,
-	words_bottle_of_flies      = 80,
-	words_listen_to            = 81,
-	words_soul_egg             = 82,
-	words_break                = 83,
-	words_magic_belt           = 84,
-	words_adjust               = 85,
-	words_amulet               = 86,
-	words_thrust               = 87,
-	words_mud                  = 88,
-	words_feel                 = 89,
-	words_taste                = 90,
-	words_feathers             = 91,
-	words_tickle               = 92,
-	words_torch                = 93,
-	words_wave                 = 94,
-	words_flask                = 95,
-	words_flask_full_of_acid   = 96,
-	words_pour_contents        = 97,
-	words_pour                 = 98,
-	words_pour_contents_of     = 99,
+	words_floor                =  16,
+	words_walk_across          =  17,
+	words_rug                  =  18,
+	words_carpet               =  19,
+	words_wall                 =  20,
+	words_bed                  =  21,
+	words_pillow               =  22,
+	words_chest                =  23,
+	words_window               =  24,
+	words_nightstand           =  25,
+	words_tapestry             =  26,
+	words_dressing_screen      =  27,
+	words_walk_behind          =  28,
+	words_royal_crest          =  29,
+	words_look_at              =  30,
+	words_bedroom_wall         =  31,
+	words_castle_walls         =  32,
+	words_book                 =  33,
+	words_fireplace            =  34,
+	words_fireplace_screen     =  35,
+	words_door_to_queens_room  =  36,
+	words_exit                 =  36,
+	words_walk_through         =  37,
+	words_hall_to_south        =  38,
+	words_walk_into            =  39,
+	words_wall_plaque          =  40,
+	words_decoration           =  41,
+	words_swords               =  42,
+	words_wall_sconce          =  43,
+	words_bust                 =  44,
+	words_arch                 =  45,
+	words_signet_ring          =  46,
+	words_invoke               =  47,
+	words_polish               =  48,
+	words_gangbang             =  49,
+	words_bird_figurine        =  50,
+	words_rub                  =  51,
+	words_birdcall             =  52,
+	words_use                  =  53,
+	words_make_noise           =  54,
+	words_shieldstone          =  55,
+	words_sword                =  56,
+	words_attack               =  57,
+	words_carve_up             =  58,
+	words_goblet               =  59,
+	words_fill                 =  60,
+	words_drink_from           =  61,
+	words_bone                 =  62,
+	words_gnaw                 =  63,
+	words_fruit                =  64,
+	words_eat                  =  65,
+	words_doll                 =  66,
+	words_play_with            =  67,
+	words_heal                 =  68,
+	words_heal_thyself         =  69,
+	words_heal_self            =  70,
+	words_polystone            =  71,
+	words_mimic                =  72,
+	words_red_powerstone       =  73,
+	words_yellow_powerstone    =  74,
+	words_blue_powerstone      =  75,
+	words_key_crown            =  76,
+	words_wear                 =  77,
+	words_dates                =  78,
+	words_statue               =  79,
+	words_bottle_of_flies      =  80,
+	words_listen_to            =  81,
+	words_soul_egg             =  82,
+	words_break                =  83,
+	words_magic_belt           =  84,
+	words_adjust               =  85,
+	words_amulet               =  86,
+	words_thrust               =  87,
+	words_mud                  =  88,
+	words_feel                 =  89,
+	words_taste                =  90,
+	words_feathers             =  91,
+	words_tickle               =  92,
+	words_torch                =  93,
+	words_wave                 =  94,
+	words_flask                =  95,
+	words_flask_full_of_acid   =  96,
+	words_pour_contents        =  97,
+	words_pour                 =  98,
+	words_pour_contents_of     =  99,
 	words_drink                = 100,
 	words_rope                 = 101,
 	words_tie                  = 102,
diff --git a/engines/mads/madsv2/dragonsphere/rooms/room103.cpp b/engines/mads/madsv2/dragonsphere/rooms/room103.cpp
index 3b028077b36..c3fe41fa572 100644
--- a/engines/mads/madsv2/dragonsphere/rooms/room103.cpp
+++ b/engines/mads/madsv2/dragonsphere/rooms/room103.cpp
@@ -276,7 +276,7 @@ static void room_103_parser() {
 		return;
 	}
 
-	if (player_parse(37, 36, 0) || player_parse(6, 36, 0) || player_parse(10, 36, 0)) {
+	if (player_said_2(walk_through, door_to_queens_room) || player_said_2(open, door_to_queens_room) || player_said_2(pull, door_to_queens_room)) {
 		switch (kernel.trigger) {
 		case 0:
 			player.commands_allowed = false;
@@ -337,20 +337,20 @@ static void room_103_parser() {
 		return;
 	}
 
-	if (player_parse(37, 249, 0) || player_parse(6, 249, 0) || player_parse(10, 249, 0)) {
+	if (player_said_2(walk_through, door_to_meeting_room) || player_said_2(open, door_to_meeting_room) || player_said_2(pull, door_to_meeting_room)) {
 		new_room = 104;
 		player.command_ready = false;
 		return;
 	}
 
-	if (player_parse(37, 250, 0) || player_parse(6, 250, 0) || player_parse(10, 250, 0)) {
+	if (player_said_2(walk_through, door_to_ballroom) || player_said_2(open, door_to_ballroom) || player_said_2(pull, door_to_ballroom)) {
 		new_room = 105;
 		player.command_ready = false;
 		return;
 	}
 
-	if (player_parse(3, 0) || player_parse(30, 0)) {
-		if (player_parse(26, 0)) {
+	if (player_said_1(look) || player_said_1(look_at)) {
+		if (player_said_1(tapestry)) {
 			if (inter_point_x <= END_HALL_TAPESTRY_X && inter_point_y <= END_HALL_TAPESTRY_Y)
 				text_show(10302);
 			else
@@ -358,46 +358,46 @@ static void room_103_parser() {
 			player.command_ready = false;
 			return;
 		}
-		if (player_parse(246, 0)) { text_show(10305); player.command_ready = false; return; }
-		if (player_parse(36, 0))  { text_show(10307); player.command_ready = false; return; }
-		if (player_parse(245, 0)) { text_show(10308); player.command_ready = false; return; }
-		if (player_parse(570, 0)) { text_show(10309); player.command_ready = false; return; }
-		if (player_parse(198, 0)) { text_show(10311); player.command_ready = false; return; }
-		if (player_parse(248, 0)) { text_show(10312); player.command_ready = false; return; }
-		if (player_parse(247, 0)) { text_show(10314); player.command_ready = false; return; }
-		if (player_parse(244, 0)) { text_show(10315); player.command_ready = false; return; }
-		if (player_parse(44, 0))  { text_show(10317); player.command_ready = false; return; }
-		if (player_parse(41, 0))  { text_show(10320); player.command_ready = false; return; }
-		if (player_parse(40, 0))  { text_show(10322); player.command_ready = false; return; }
-		if (player_parse(250, 0)) { text_show(10323); player.command_ready = false; return; }
-		if (player_parse(249, 0)) { text_show(10324); player.command_ready = false; return; }
+		if (player_said_1(coat_of_arms)) { text_show(10305); player.command_ready = false; return; }
+		if (player_said_1(door_to_queens_room))  { text_show(10307); player.command_ready = false; return; }
+		if (player_said_1(door_to_king_s_room)) { text_show(10308); player.command_ready = false; return; }
+		if (player_said_1(cedar_chest)) { text_show(10309); player.command_ready = false; return; }
+		if (player_said_1(table)) { text_show(10311); player.command_ready = false; return; }
+		if (player_said_1(small_window)) { text_show(10312); player.command_ready = false; return; }
+		if (player_said_1(large_window)) { text_show(10314); player.command_ready = false; return; }
+		if (player_said_1(battle_axes)) { text_show(10315); player.command_ready = false; return; }
+		if (player_said_1(bust))  { text_show(10317); player.command_ready = false; return; }
+		if (player_said_1(decoration))  { text_show(10320); player.command_ready = false; return; }
+		if (player_said_1(wall_plaque))  { text_show(10322); player.command_ready = false; return; }
+		if (player_said_1(door_to_ballroom)) { text_show(10323); player.command_ready = false; return; }
+		if (player_said_1(door_to_meeting_room)) { text_show(10324); player.command_ready = false; return; }
 	}
 
-	if ((player_parse(4, 0) || player_parse(10, 0)) && player_parse(244, 0)) {
+	if ((player_said_1(take) || player_said_1(pull)) && player_said_1(battle_axes)) {
 		text_show(10316);
 		player.command_ready = false;
 		return;
 	}
 
-	if ((player_parse(5, 0) || player_parse(10, 0)) && player_parse(26, 0)) {
+	if ((player_said_1(push) || player_said_1(pull)) && player_said_1(tapestry)) {
 		text_show(10304);
 		player.command_ready = false;
 		return;
 	}
 
-	if (player_parse(10, 246, 0)) {
+	if (player_said_2(pull, coat_of_arms)) {
 		text_show(10306);
 		player.command_ready = false;
 		return;
 	}
 
-	if (player_parse(6, 570, 0)) {
+	if (player_said_2(open, cedar_chest)) {
 		text_show(10310);
 		player.command_ready = false;
 		return;
 	}
 
-	if (player_parse(6, 248, 0) || player_parse(6, 247, 0)) {
+	if (player_said_2(open, small_window) || player_said_2(open, large_window)) {
 		text_show(10313);
 		player.command_ready = false;
 		return;
diff --git a/engines/mads/madsv2/dragonsphere/rooms/room104.cpp b/engines/mads/madsv2/dragonsphere/rooms/room104.cpp
index 7216fe1ff65..e83a6026f70 100644
--- a/engines/mads/madsv2/dragonsphere/rooms/room104.cpp
+++ b/engines/mads/madsv2/dragonsphere/rooms/room104.cpp
@@ -190,15 +190,8 @@ static Scratch scratch;
 
 #define LENGTH_OF_LIFE            1300 
 
-// Vocabulary word IDs (raw integers from disassembly; VOCABH.DB/VOCAB.DB)
-//   words_MacMorn=350, words_wall_panel=276, words_secret_door=275
-//   words_table=198, words_books=274, words_tapestry=26 (✓ verified)
-//   words_Queen_Mother=347, words_king=291, words_doorway=277
-//   words_walk_through=37 (✓), words_walk_to=13 (✓)
 
-// ---------------------------------------------------------------------------
-
-void room_104_init() {
+static void room_104_init() {
 	conv_get(CONV_FINALE);
 
 	// Palette-only loads for Pid persona (prevent palette fragmentation)
@@ -264,7 +257,7 @@ void room_104_init() {
 
 		// Stamp the correct tapestry sprite based on current state
 		if (global[tapestry_status] == TAPESTRY_CLOSED ||
-		    global[tapestry_status] == TAPESTRY_CLOSED2) {
+			global[tapestry_status] == TAPESTRY_CLOSED2) {
 			kernel_flip_hotspot(276, false);  // words_wall_panel
 			kernel_flip_hotspot(275, false);  // words_secret_door
 			seq[fx_tapestry_closed] = kernel_seq_stamp(ss[fx_tapestry_closed], false, KERNEL_FIRST);
@@ -278,17 +271,17 @@ void room_104_init() {
 
 		// Adjust secret door / wall panel hotspots based on books state
 		if (global[books_status] == BOOKS_PRESENT ||
-		    global[books_status] == BOOKS_PRESENT2) {
+			global[books_status] == BOOKS_PRESENT2) {
 			if (global[tapestry_status] == TAPESTRY_OPENED ||
-			    global[tapestry_status] == TAPESTRY_OPENED2) {
+				global[tapestry_status] == TAPESTRY_OPENED2) {
 				kernel_flip_hotspot(275, false);  // words_secret_door
 			}
 		} else if (global[books_status] == BOOKS_PULLED ||
-		           global[books_status] == BOOKS_PULLED2) {
+			       global[books_status] == BOOKS_PULLED2) {
 			seq[fx_wall_open_close] = kernel_seq_stamp(ss[fx_wall_open_close], false, KERNEL_LAST);
 			kernel_seq_depth(seq[fx_wall_open_close], 8);
 			if (global[tapestry_status] == TAPESTRY_OPENED ||
-			    global[tapestry_status] == TAPESTRY_OPENED2) {
+				global[tapestry_status] == TAPESTRY_OPENED2) {
 				kernel_flip_hotspot(276, false);   // words_wall_panel
 				kernel_flip_hotspot(275, true);    // words_secret_door (true in release binary)
 			}
@@ -314,9 +307,9 @@ void room_104_init() {
 		seq[fx_open_doorway] = kernel_seq_stamp(ss[fx_open_doorway], false, KERNEL_FIRST);
 		kernel_seq_depth(seq[fx_open_doorway], 8);
 		local->doorway_id = kernel_add_dynamic(277, 37, SYNTAX_SINGULAR,
-		                        seq[fx_open_doorway], 0, 0, 0, 0);
+			                    seq[fx_open_doorway], 0, 0, 0, 0);
 		kernel_dynamic_walk(local->doorway_id, WALK_TO_DOORWAY_X, WALK_TO_DOORWAY_Y,
-		                    FACING_NORTHEAST);
+			                FACING_NORTHEAST);
 
 		global[books_status] = BOOKS_NOT_PRESENT;
 		kernel_flip_hotspot_loc(26, false, TAP_HS_CLOSED_X, TAP_HS_CLOSED_Y);  // words_tapestry
@@ -380,7 +373,7 @@ void room_104_init() {
 
 		{
 			int16 id = kernel_add_dynamic(347, 13, SYNTAX_FEM_NOT_PROPER,
-			                              KERNEL_NONE, 0, 0, 0, 0);  // words_Queen_Mother
+				                          KERNEL_NONE, 0, 0, 0, 0);  // words_Queen_Mother
 			kernel_dynamic_hot[id].prep = PREP_ON;
 			kernel_dynamic_anim(id, aa[2], 0);
 		}
@@ -389,7 +382,7 @@ void room_104_init() {
 
 		{
 			int16 id = kernel_add_dynamic(291, 13, SYNTAX_MASC_NOT_PROPER,
-			                              KERNEL_NONE, 0, 0, 0, 0);  // words_king
+				                          KERNEL_NONE, 0, 0, 0, 0);  // words_king
 			kernel_dynamic_hot[id].prep = PREP_ON;
 			kernel_dynamic_anim(id, aa[0], 0);
 		}
@@ -399,8 +392,8 @@ void room_104_init() {
 		seq[fx_door] = kernel_seq_stamp(ss[fx_door], false, KERNEL_LAST);
 		kernel_seq_depth(seq[fx_door], 14);
 		player_first_walk(START_X_ROOM_103, START_Y_ROOM_103, FACING_SOUTH,
-		                  WALK_TO_X_FROM_103, WALK_TO_Y_FROM_103, FACING_SOUTH,
-		                  false);
+			              WALK_TO_X_FROM_103, WALK_TO_Y_FROM_103, FACING_SOUTH,
+			              false);
 		player_walk_trigger(ROOM_104_DOOR_CLOSES);
 
 	} else {
@@ -436,7 +429,7 @@ void room_104_init() {
 
 			{
 				int16 id = kernel_add_dynamic(347, 13, SYNTAX_FEM_NOT_PROPER,
-				                              KERNEL_NONE, 0, 0, 0, 0);  // words_Queen_Mother
+					                          KERNEL_NONE, 0, 0, 0, 0);  // words_Queen_Mother
 				kernel_dynamic_hot[id].prep = PREP_ON;
 				kernel_dynamic_anim(id, aa[2], 0);
 			}
@@ -445,7 +438,7 @@ void room_104_init() {
 
 			{
 				int16 id = kernel_add_dynamic(291, 13, SYNTAX_MASC_NOT_PROPER,
-				                              KERNEL_NONE, 0, 0, 0, 0);  // words_king
+					                          KERNEL_NONE, 0, 0, 0, 0);  // words_king
 				kernel_dynamic_hot[id].prep = PREP_ON;
 				kernel_dynamic_anim(id, aa[0], 0);
 			}
@@ -490,13 +483,13 @@ static void room_104_process_conversation_finale() {
 
 	// Player-choice (b_b) node dispatch
 	switch (player_verb) {
-	case 38:  // conv001_end_b_b
+	case conv001_end_b_b:
 		global[end_of_game] = true;
 		new_room = 106;
 		break;
 
-	case 30:  // conv001_pre_25_b_b
-		*conv_my_next_start = 31;  // conv001_twentyfive
+	case conv001_pre_25_b_b:
+		*conv_my_next_start = conv001_twentyfive;
 		conv_abort();
 		if (global[llanie_status] == IS_SAVED) {
 			aa[3]                  = kernel_run_animation(kernel_name('l', 1), 0);
@@ -505,28 +498,28 @@ static void room_104_process_conversation_finale() {
 		}
 		break;
 
-	case 10:  // conv001_exit_b_b
+	case conv001_exit_b_b:
 		if (!kernel.trigger)
 			local->mac_action = MAC_TABLE;
 		me_trig_flag  = true;
 		you_trig_flag = true;
 		break;
 
-	case 13:  // conv001_bear_b_b
+	case conv001_bear_b_b:
 		local->pid_action = PID_BEAR;
 		conv_hold();
 		me_trig_flag  = true;
 		you_trig_flag = true;
 		break;
 
-	case 16:  // conv001_unbear_b_b
+	case conv001_unbear_b_b:
 		if (!kernel.trigger)
 			local->mac_action = MAC_UNBEAR;
 		me_trig_flag  = true;
 		you_trig_flag = true;
 		break;
 
-	case 19:  // conv001_sixteen_b_b
+	case conv001_sixteen_b_b:
 		local->death_timer    = 0;
 		local->clock          = 0;
 		local->activate_timer = true;
@@ -537,11 +530,18 @@ static void room_104_process_conversation_finale() {
 
 	// Trigger dispatch
 	switch (kernel.trigger) {
-	case ROOM_104_YOU_TALK:  // 81
+	case ROOM_104_YOU_TALK:
 		// Per-node character action: which character speaks this NPC line?
 		switch (player_verb) {
 		// Mac talks (if idle, set to talking)
-		case 0: case 2: case 4: case 5: case 7: case 8: case 11: case 17:
+		case 0:
+		case 2:
+		case 4:
+		case 5:
+		case 7:
+		case 8:
+		case 11:
+		case 17:
 			if (local->mac_action == MAC_SHUT_UP)
 				local->mac_action = MAC_TALK;
 			local->pid_action = PID_SHUT_UP;
@@ -608,11 +608,11 @@ static void room_104_process_conversation_finale() {
 	local->pid_talk_count      = 0;
 }
 
-void room_104_pre_parser() {
+static void room_104_pre_parser() {
 	// verb(pull/open/close/push) AND tapestry: redirect walk to tapestry area when
 	// the interaction point is in the right side of the room (inter_point_x > 280)
-	if ((player_parse(10, 0) || player_parse(6, 0) || player_parse(11, 0) || player_parse(5, 0)) &&
-	     player_parse(26, 0)) {  // tapestry
+	if ((player_said_1(pull) || player_said_1(open) || player_said_1(close) || player_said_1(push)) &&
+			player_said_1(tapestry)) {
 		if (inter_point_x > 280) {
 			player_walk(WALK_TO_DOORWAY_X, WALK_TO_DOORWAY_Y, FACING_NORTHEAST);
 		}
@@ -620,16 +620,16 @@ void room_104_pre_parser() {
 
 	// For Pid persona, cancel any walk when the player tries actions that require
 	// cutscene handling rather than walking
-	if (player_parse(37, 176, 0) ||    // walk_through + door_to_throne_room
-	    player_parse(37, 197, 0) ||    // walk_through + door_to_hallway
-	    player_parse(37, 277, 0) ||    // walk_through + doorway
-	    ((player_parse(10, 0) || player_parse(6, 0) || player_parse(5, 0)) && player_parse(26, 0)) ||   // pull/open/push + tapestry
-	    ((player_parse(10, 0) || player_parse(11, 0) || player_parse(5, 0)) && player_parse(26, 0)) ||  // pull/close/push + tapestry
-	    ((player_parse(5, 0) || player_parse(10, 0)) && player_parse(18, 0)) ||  // push/pull + rug
-	    player_parse(35, 0) ||         // fireplace_screen
-	    player_parse(200, 0) ||        // trophy
-	    player_parse(203, 0) ||        // loveseat
-	    player_parse(6, 201, 0)) {     // open + reading_bench
+	if (player_said_2(walk_through, door_to_throne_room) ||
+			player_said_2(walk_through, door_to_hallway) ||
+			player_said_2(walk_through, doorway) ||
+			((player_said_1(pull) || player_said_1(open) || player_said_1(push)) && player_said_1(tapestry)) ||
+			((player_said_1(pull) || player_said_1(close) || player_said_1(push)) && player_said_1(tapestry)) ||
+			((player_said_1(push) || player_said_1(pull)) && player_said_1(rug)) ||
+			player_said_1(fireplace_screen) ||
+			player_said_1(trophy) ||
+			player_said_1(loveseat) ||
+			player_said_2(open, reading_bench)) {
 		if (global[player_persona] == PLAYER_IS_PID) {
 			player_cancel_walk();
 		}
@@ -649,7 +649,6 @@ static void handle_animation_king() {
 		king_reset_frame = -1;
 
 		switch (local->king_frame) {
-
 		case 22:
 			if (local->king_action == KING_INVISIBLE) {
 				king_reset_frame = 21;
@@ -724,7 +723,6 @@ static void handle_animation_king() {
 	}
 }
 
-
 static void handle_animation_mac() {
 	int mac_reset_frame;
 
@@ -733,7 +731,6 @@ static void handle_animation_mac() {
 		mac_reset_frame = -1;
 
 		switch (local->mac_frame) {
-
 		case 25:  /* almost end of Macmorn drawing sword */
 			player.commands_allowed = true;
 			break;
@@ -785,7 +782,8 @@ static void handle_animation_mac() {
 			kernel_synch(KERNEL_ANIM, aa[4], KERNEL_ANIM, aa[1]);
 			break;
 
-		case 112: /* end of throw table */
+		case 112:
+			/* end of throw table */
 			mac_reset_frame = 8;    /* draw sword */
 			local->mac_action = MAC_SHUT_UP;
 			break;
@@ -857,18 +855,21 @@ static void handle_animation_mac() {
 			}
 			break;
 
-		case 76:   /* end of MacMorn laughing over Pid's death */
+		case 76:
+			/* end of MacMorn laughing over Pid's death */
 			conv_reset(CONV_FINALE);
 			text_show(10467);
 			global[no_load_walker] = false;
 			new_room = 119;
 			break;
 
-		case 82:  /* part way into un-bearing pid */
+		case 82:
+			/* part way into un-bearing pid */
 			local->pid_action = PID_UNBEAR;
 			break;
 
-		case 97:  /* somewhere into flipping table */
+		case 97:
+			/* somewhere into flipping table */
 			local->activate_timer = true;
 			kernel_seq_delete(seq[fx_table]);
 			seq[fx_table] = kernel_seq_forward(ss[fx_table], false, 10, 0, 0, 1);
@@ -886,7 +887,6 @@ static void handle_animation_mac() {
 	}
 }
 
-
 static void handle_animation_mac_2() {
 	int mac_reset_frame;
 
@@ -1038,7 +1038,6 @@ static void handle_animation_twinkles() {
 		twinkles_reset_frame = -1;
 
 		switch (local->twinkles_frame) {
-
 		case 1:   /* keep her invisible behind wall */
 			if (local->twinkles_action == TWINKLES_INVISIBLE) {
 				twinkles_reset_frame = 0;
@@ -1068,9 +1067,7 @@ static void handle_animation_twinkles() {
 		case 37:  /* end of coming into room and freeze */
 		case 38:  /* end of freeze 2                    */
 		case 47:  /* end of talk                        */
-
 			switch (local->twinkles_action) {
-
 			case TWINKLES_TALK:
 				twinkles_reset_frame = 38;  /* talk */
 				local->twinkles_action = TWINKLES_SHUT_UP;
@@ -1105,7 +1102,6 @@ static void handle_animation_twinkles() {
 	}
 }
 
-
 static void handle_animation_death() {
 	int death_reset_frame;
 
@@ -1114,7 +1110,6 @@ static void handle_animation_death() {
 		death_reset_frame = -1;
 
 		switch (local->death_frame) {
-
 		case 11:
 			global_speech_go(7); /* hurl */
 			break;
@@ -1291,7 +1286,7 @@ static void handle_animation_pid() {
 	}
 }
 
-void room_104_daemon(void) {
+static void room_104_daemon() {
 	int reset_frame;
 	int temp;  /* for synching purposes */
 	long dif;  /* for timer stuff       */
@@ -1373,7 +1368,6 @@ void room_104_daemon(void) {
 	}
 
 	if (kernel.trigger == ROOM_104_RUN_P2) {
-
 		kernel_abort_animation(aa[4]);
 
 		aa[4] = kernel_run_animation(kernel_name('p', 2), 0);
@@ -1451,7 +1445,7 @@ void room_104_daemon(void) {
 	}
 }
 
-void room_104_parser() {
+static void room_104_parser() {
 	int16 temp;   // used for kernel_synch old-sequence argument
 
 	// Active conversation: dispatch to the finale processor
@@ -1464,27 +1458,28 @@ void room_104_parser() {
 	// Room description on look_around
 	if (player.look_around) {
 		if (global[player_persona] == PLAYER_IS_KING) {
-			text_show(10401);  // text_104_01
+			text_show(10401);
 		} else {
-			text_show(10437);  // text_104_37
+			text_show(10437);
 		}
 		player.command_ready = false;
 		return;
 	}
 
 	// walk_through / open / pull + door_to_throne_room (176)
-	if (player_parse(37, 176, 0) || player_parse(6, 176, 0) || player_parse(10, 176, 0)) {
+	if (player_said_2(walk_through, door_to_throne_room) || player_said_2(open, door_to_throne_room) ||
+			player_said_2(pull, door_to_throne_room)) {
 		if (global[player_persona] == PLAYER_IS_KING) {
 			new_room = 106;
 		} else {
-			text_show(10434);  // text_104_34
+			text_show(10434);
 		}
 		player.command_ready = false;
 		return;
 	}
 
 	// walk_through / open / pull + door_to_hallway (197)
-	if (player_parse(37, 197, 0) || player_parse(6, 197, 0) || player_parse(10, 197, 0)) {
+	if (player_said_2(walk_through, door_to_hallway) || player_said_2(open, door_to_hallway) || player_said_2(pull, door_to_hallway)) {
 		if (global[player_persona] == PLAYER_IS_KING) {
 			switch (kernel.trigger) {
 			case 0:
@@ -1536,16 +1531,16 @@ void room_104_parser() {
 				break;
 			}
 		} else {
-			text_show(10434);  // text_104_34
+			text_show(10434);
 		}
 		player.command_ready = false;
 		return;
 	}
 
 	// pull / take / open + books (274) — open the bookcase
-	if (player_parse(10, 274, 0) || player_parse(4, 274, 0) || player_parse(6, 274, 0)) {
+	if (player_said_2(pull, books) || player_said_2(take, books) || player_said_2(open, books)) {
 		if (global[books_status] == BOOKS_PRESENT ||
-		    global[books_status] == BOOKS_PRESENT2 || kernel.trigger) {
+			global[books_status] == BOOKS_PRESENT2 || kernel.trigger) {
 			switch (kernel.trigger) {
 			case 0:
 				player.commands_allowed = false;
@@ -1567,7 +1562,7 @@ void room_104_parser() {
 				kernel_synch(KERNEL_SERIES, seq[fx_wall_open_close], KERNEL_SERIES, temp);
 				kernel_seq_depth(seq[fx_wall_open_close], 8);
 				if (global[tapestry_status] == TAPESTRY_OPENED ||
-				    global[tapestry_status] == TAPESTRY_OPENED2) {
+					global[tapestry_status] == TAPESTRY_OPENED2) {
 					kernel_flip_hotspot(276, false);  // words_wall_panel
 					kernel_flip_hotspot(275, true);   // words_secret_door
 				}
@@ -1582,12 +1577,12 @@ void room_104_parser() {
 					global[player_score] += 2;
 					global[books_status] = BOOKS_PULLED;
 					if (global[tapestry_status] == TAPESTRY_OPENED ||
-					    global[tapestry_status] == TAPESTRY_OPENED2) {
-						text_show(10428);  // text_104_28
+						global[tapestry_status] == TAPESTRY_OPENED2) {
+						text_show(10428);
 					} else {
 						sound_play(N_BooksRumble);
 						sound_play(N_WallGrinds);
-						text_show(10427);  // text_104_27
+						text_show(10427);
 					}
 				} else {
 					sound_play(94);        // N_BooksRumble
@@ -1603,7 +1598,7 @@ void room_104_parser() {
 	}
 
 	// push + books (274) — close the bookcase
-	if (player_parse(5, 274, 0)) {
+	if (player_said_2(push, books)) {
 		if (global[books_status] == BOOKS_PULLED || global[books_status] == BOOKS_PULLED2) {
 			switch (kernel.trigger) {
 			case 0:
@@ -1625,7 +1620,7 @@ void room_104_parser() {
 				break;
 			case 2:
 				if (global[tapestry_status] == TAPESTRY_OPENED ||
-				    global[tapestry_status] == TAPESTRY_OPENED2) {
+					global[tapestry_status] == TAPESTRY_OPENED2) {
 					kernel_flip_hotspot(276, true);   // words_wall_panel
 					kernel_flip_hotspot(275, false);  // words_secret_door
 				}
@@ -1637,7 +1632,7 @@ void room_104_parser() {
 				break;
 			case 4:
 				if (global[books_status] == BOOKS_PULLED) {
-					text_show(10429);  // text_104_29
+					text_show(10429);
 				}
 				global[books_status] = BOOKS_PRESENT2;
 				player.commands_allowed = true;
@@ -1649,11 +1644,11 @@ void room_104_parser() {
 	}
 
 	// pull / open / push + tapestry (26) — open the tapestry
-	if (player_parse(10, 26, 0) || player_parse(6, 26, 0) || player_parse(5, 26, 0)) {
+	if (player_said_2(pull, tapestry) || player_said_2(open, tapestry) || player_said_2(push, tapestry)) {
 		if (global[player_persona] == PLAYER_IS_KING) {
 			if (inter_point_x >= CORRECT_TAPESTRY_X) {
 				if (global[tapestry_status] == TAPESTRY_CLOSED ||
-				    global[tapestry_status] == TAPESTRY_CLOSED2) {
+					global[tapestry_status] == TAPESTRY_CLOSED2) {
 					switch (kernel.trigger) {
 					case 0:
 						kernel_seq_delete(seq[fx_tapestry_closed]);
@@ -1670,7 +1665,7 @@ void room_104_parser() {
 						kernel_flip_hotspot_loc(26, true,  TAP_HS_OPEN_X,   TAP_HS_OPEN_Y);
 						kernel_flip_hotspot_loc(26, false, TAP_HS_CLOSED_X, TAP_HS_CLOSED_Y);
 						if (global[books_status] == BOOKS_PULLED ||
-						    global[books_status] == BOOKS_PULLED2) {
+							global[books_status] == BOOKS_PULLED2) {
 							kernel_flip_hotspot(276, false);  // wall_panel
 							kernel_flip_hotspot(275, true);   // secret_door
 						} else {
@@ -1684,12 +1679,12 @@ void room_104_parser() {
 							global[tapestry_status] = TAPESTRY_OPENED;
 							global[player_score] += 2;
 							if (global[books_status] == BOOKS_NOT_PRESENT ||
-							    global[books_status] == BOOKS_PRESENT ||
-							    global[books_status] == BOOKS_PRESENT2) {
-								text_show(10424);  // text_104_24
+								global[books_status] == BOOKS_PRESENT ||
+								global[books_status] == BOOKS_PRESENT2) {
+								text_show(10424);
 							} else if (global[books_status] == BOOKS_PULLED ||
-							           global[books_status] == BOOKS_PULLED2) {
-								text_show(10425);  // text_104_25
+								       global[books_status] == BOOKS_PULLED2) {
+								text_show(10425);
 							}
 						} else {
 							global[tapestry_status] = TAPESTRY_OPENED2;
@@ -1702,23 +1697,23 @@ void room_104_parser() {
 				}
 				// tapestry already open: fall through to close-tapestry check below
 			} else {
-				text_show(10404);  // text_104_04
+				text_show(10404);
 				player.command_ready = false;
 				return;
 			}
 		} else {
-			text_show(10445);  // text_104_45
+			text_show(10445);
 			player.command_ready = false;
 			return;
 		}
 	}
 
 	// pull / close / push + tapestry (26) — close the tapestry
-	if (player_parse(10, 26, 0) || player_parse(11, 26, 0) || player_parse(5, 26, 0)) {
+	if (player_said_2(pull, tapestry) || player_said_2(close, tapestry) || player_said_2(push, tapestry)) {
 		if (global[player_persona] == PLAYER_IS_KING) {
 			if (inter_point_x >= CORRECT_TAPESTRY_X) {
 				if (global[tapestry_status] == TAPESTRY_OPENED ||
-				    global[tapestry_status] == TAPESTRY_OPENED2) {
+					global[tapestry_status] == TAPESTRY_OPENED2) {
 					switch (kernel.trigger) {
 					case 0:
 						player.commands_allowed  = false;
@@ -1748,7 +1743,7 @@ void room_104_parser() {
 				}
 			}
 		} else {
-			text_show(10445);  // text_104_45
+			text_show(10445);
 			player.command_ready = false;
 			return;
 		}
@@ -1757,15 +1752,13 @@ void room_104_parser() {
 	// ---------------------------------------------------------------------------
 	// look / look_at block
 	// ---------------------------------------------------------------------------
-	if (player_parse(3, 0) || player_parse(17, 0)) {  // look(3) or look_at(17) — TODO: verify look_at ID
-
-		if (player_parse(3, 274, 0) || player_parse(17, 274, 0) ||   // books
-		    player_parse(3, 199, 0) || player_parse(17, 199, 0)) {    // bookshelf
+	if (player_said_1(look) || player_said_1(look_at)) {
+		if (player_said_1(books) || player_said_1(bookshelf)) {
 			if (global[player_persona] == PLAYER_IS_KING) {
 				if (global[books_status] == BOOKS_NOT_PRESENT) {
 					kernel_flip_hotspot(274, true);
 					global[books_status] = BOOKS_PRESENT;
-					text_show(10418);  // text_104_18
+					text_show(10418);
 					player.command_ready = false;
 					return;
 				} else if (global[books_status] == BOOKS_PRESENT) {
@@ -1773,62 +1766,62 @@ void room_104_parser() {
 					player.command_ready = false;
 					return;
 				} else if (global[books_status] == BOOKS_PULLED ||
-				           global[books_status] == BOOKS_PULLED2) {
-					text_show(10419);  // text_104_19
+					       global[books_status] == BOOKS_PULLED2) {
+					text_show(10419);
 					player.command_ready = false;
 					return;
 				} else if (global[books_status] == BOOKS_PRESENT2) {
-					text_show(10420);  // text_104_20
+					text_show(10420);
 					player.command_ready = false;
 					return;
 				}
 			} else {
-				text_show(10439);  // text_104_39
+				text_show(10439);
 				player.command_ready = false;
 				return;
 			}
 		}
 
-		if (player_parse(3, 34, 0) || player_parse(17, 34, 0)) {   // fireplace (34)
+		if (player_said_1(fireplace)) {
 			if (global[player_persona] == PLAYER_IS_KING) {
-				text_show(10402);  // text_104_02
+				text_show(10402);
 			} else {
-				text_show(10438);  // text_104_38
+				text_show(10438);
 			}
 			player.command_ready = false;
 			return;
 		}
 
-		if (player_parse(3, 26, 0) || player_parse(17, 26, 0)) {   // tapestry (26)
+		if (player_said_1(tapestry)) {
 			if (global[player_persona] == PLAYER_IS_KING) {
 				if (inter_point_x >= ARCHER_TAPESTRY_BEGIN_X && inter_point_x <= ARCHER_TAPESTRY_END_X) {
-					text_show(10403);  // text_104_03
+					text_show(10403);
 					player.command_ready = false;
 					return;
 				} else if (inter_point_x >= CASTLE_TAPESTRY_BEGIN_X && inter_point_x <= CASTLE_TAPESTRY_END_X) {
-					text_show(10422);  // text_104_22
+					text_show(10422);
 					player.command_ready = false;
 					return;
 				} else {
 					if (global[tapestry_status] == TAPESTRY_OPENED ||
-					    global[tapestry_status] == TAPESTRY_OPENED2) {
-						text_show(10460);  // text_104_60
+						global[tapestry_status] == TAPESTRY_OPENED2) {
+						text_show(10460);
 					} else {
-						text_show(10423);  // text_104_23
+						text_show(10423);
 					}
 					player.command_ready = false;
 					return;
 				}
 			} else {
-				text_show(10439);  // text_104_39
+				text_show(10439);
 				player.command_ready = false;
 				return;
 			}
 		}
 
-		if (player_parse(3, 18, 0) || player_parse(17, 18, 0)) {   // rug (18)
+		if (player_said_1(rug)) {
 			if (global[player_persona] == PLAYER_IS_KING) {
-				text_show(10405);  // text_104_05
+				text_show(10405);
 			} else {
 				text_show(10439);
 			}
@@ -1836,9 +1829,9 @@ void room_104_parser() {
 			return;
 		}
 
-		if (player_parse(3, 35, 0) || player_parse(17, 35, 0)) {   // fireplace_screen (35)
+		if (player_said_1(fireplace_screen)) {
 			if (global[player_persona] == PLAYER_IS_KING) {
-				text_show(10407);  // text_104_07
+				text_show(10407);
 			} else {
 				text_show(10439);
 			}
@@ -1846,50 +1839,49 @@ void room_104_parser() {
 			return;
 		}
 
-		if (player_parse(3, 176, 0) || player_parse(17, 176, 0)) {  // door_to_throne_room (176)
+		if (player_said_1(door_to_throne_room)) {
 			if (global[player_persona] == PLAYER_IS_KING) {
-				text_show(10409);  // text_104_09
+				text_show(10409);
 			} else {
-				text_show(10434);  // text_104_34
+				text_show(10434);
 			}
 			player.command_ready = false;
 			return;
 		}
 
-		if (player_parse(3, 204, 0) || player_parse(17, 204, 0)) {  // sconce — TODO: verify ID=204
+		if (player_said_1(sconce)) {
 			if (global[player_persona] == PLAYER_IS_KING) {
-				text_show(10410);  // text_104_10
+				text_show(10410);
 			} else {
-				text_show(10440);  // text_104_40
+				text_show(10440);
 			}
 			player.command_ready = false;
 			return;
 		}
 
-		// wood_basket — TODO: unknown vocab ID; needs disassembly verification
-		// if (player_parse(3, ???, 0) || player_parse(17, ???, 0)) {
-		//     if (global[player_persona] == PLAYER_IS_KING) {
-		//         text_show(10411);  // text_104_11
-		//     } else {
-		//         text_show(10439);
-		//     }
-		//     player.command_ready = false;
-		//     return;
-		// }
+		if (player_said_1(basket)) {
+			if (global[player_persona] == PLAYER_IS_KING) {
+			    text_show(10411);
+			} else {
+			    text_show(10439);
+			}
+			player.command_ready = false;
+			return;
+		}
 
-		if (player_parse(3, 200, 0) || player_parse(17, 200, 0)) {  // trophy (200)
+		if (player_said_1(trophy)) {
 			if (global[player_persona] == PLAYER_IS_KING) {
-				text_show(10412);  // text_104_12
+				text_show(10412);
 			} else {
-				text_show(10441);  // text_104_41
+				text_show(10441);
 			}
 			player.command_ready = false;
 			return;
 		}
 
-		if (player_parse(3, 201, 0) || player_parse(17, 201, 0)) {  // reading_bench (201)
+		if (player_said_1(reading_bench)) {
 			if (global[player_persona] == PLAYER_IS_KING) {
-				text_show(10414);  // text_104_14
+				text_show(10414);
 			} else {
 				text_show(10439);
 			}
@@ -1897,9 +1889,9 @@ void room_104_parser() {
 			return;
 		}
 
-		if (player_parse(3, 203, 0) || player_parse(17, 203, 0)) {  // loveseat (203)
+		if (player_said_1(loveseat)) {
 			if (global[player_persona] == PLAYER_IS_KING) {
-				text_show(10416);  // text_104_16
+				text_show(10416);
 			} else {
 				text_show(10439);
 			}
@@ -1907,9 +1899,9 @@ void room_104_parser() {
 			return;
 		}
 
-		if (player_parse(3, 197, 0) || player_parse(17, 197, 0)) {  // door_to_hallway (197)
+		if (player_said_1(door_to_hallway)) {
 			if (global[player_persona] == PLAYER_IS_KING) {
-				text_show(10421);  // text_104_21
+				text_show(10421);
 			} else {
 				text_show(10434);
 			}
@@ -1917,139 +1909,132 @@ void room_104_parser() {
 			return;
 		}
 
-		if (player_parse(3, 275, 0) || player_parse(17, 275, 0)) {  // secret_door (275)
-			text_show(10430);  // text_104_30
+		if (player_said_1(secret_door)) {
+			text_show(10430);
 			player.command_ready = false;
 			return;
 		}
 
-		if (player_parse(3, 277, 0) || player_parse(17, 277, 0) ||
-		    player_parse(37, 277, 0) || player_parse(6, 277, 0)) {  // doorway variants
+		if (player_said_1(doorway)) {
 			if (global[player_persona] == PLAYER_IS_PID) {
-				text_show(10432);  // text_104_32
+				text_show(10432);
 				player.command_ready = false;
 				return;
 			}
 		}
 
-		if (player_parse(3, 276, 0)) {  // look + wall_panel (276) — look_at not in original
+		if (player_said_1(wall_panel)) {
 			if (global[books_status] == BOOKS_NOT_PRESENT ||
-			    global[books_status] == BOOKS_PRESENT) {
-				text_show(10435);  // text_104_35
+				global[books_status] == BOOKS_PRESENT) {
+				text_show(10435);
 			} else {
-				text_show(10436);  // text_104_36
+				text_show(10436);
 			}
 			player.command_ready = false;
 			return;
 		}
 
-		if (player_parse(3, 291, 0) || player_parse(17, 291, 0)) {  // king (291)
+		if (player_said_1(king)) {
 			if (global[player_persona] == PLAYER_IS_PID) {
-				text_show(10443);  // text_104_43
+				text_show(10443);
 				player.command_ready = false;
 				return;
 			}
 		}
 
-		if (player_parse(3, 414, 0) || player_parse(17, 414, 0)) {  // music_box (414)
+		if (player_said_1(music_box)) {
 			if (global[no_load_walker]) {
-				// TODO: object_examine(magic_music_box, 843, 0);
-				//   magic_music_box = inventory object ID — needs verification
-				//   843 = text_008_43 message ID
+				object_examine(magic_music_box, 843, 0);
 				player.command_ready = false;
 				return;
 			}
 		}
 
-		if (player_parse(3, 350, 0) || player_parse(17, 350, 0)) {  // MacMorn (350) — PID only here
+		if (player_said_1(MacMorn)) {
 			if (global[player_persona] == PLAYER_IS_PID) {
-				text_show(10444);  // text_104_44
+				text_show(10444);
 				player.command_ready = false;
 				return;
 			}
-			// King looking at MacMorn: falls through to second MacMorn check at end of look block
 		}
 
-		if (player_parse(3, 198, 0) || player_parse(17, 198, 0)) {  // table (198)
+		if (player_said_1(table)) {
 			if (global[player_persona] == PLAYER_IS_PID) {
-				text_show(10455);  // text_104_55
+				text_show(10455);
 			} else {
 				if (inter_point_x < 174) {
-					text_show(10451);  // text_104_51
+					text_show(10451);
 				} else {
-					text_show(10448);  // text_104_48
+					text_show(10448);
 				}
 			}
 			player.command_ready = false;
 			return;
 		}
 
-		if (player_parse(3, 237, 0) || player_parse(17, 237, 0)) {  // decoration (237)
+		if (player_said_1(decoration)) {
 			if (global[player_persona] == PLAYER_IS_PID) {
 				text_show(10439);
 			} else {
-				text_show(10449);  // text_104_49
+				text_show(10449);
 			}
 			player.command_ready = false;
 			return;
 		}
 
-		if (player_parse(3, 56, 0) || player_parse(17, 56, 0)) {  // sword (56)
+		if (player_said_1(sword)) {
 			if (global[player_persona] == PLAYER_IS_PID) {
 				text_show(10439);
 			} else {
-				text_show(10450);  // text_104_50
+				text_show(10450);
 			}
 			player.command_ready = false;
 			return;
 		}
 
-		// floor — TODO: unknown vocab ID; needs disassembly verification
-		// if (player_parse(3, ???, 0) || player_parse(17, ???, 0)) {
-		//     if (global[player_persona] == PLAYER_IS_PID) {
-		//         text_show(10439);
-		//         player.command_ready = false;
-		//         return;
-		//     }
-		// }
-
-		// wall — TODO: unknown vocab ID; needs disassembly verification
-		// if (player_parse(3, ???, 0) || player_parse(17, ???, 0)) {
-		//     if (global[player_persona] == PLAYER_IS_PID) {
-		//         text_show(10439);
-		//         player.command_ready = false;
-		//         return;
-		//     }
-		// }
-
-		// candlestick — TODO: unknown vocab ID; needs disassembly verification
-		// if (player_parse(3, ???, 0) || player_parse(17, ???, 0)) {
-		//     if (global[player_persona] == PLAYER_IS_PID) {
-		//         text_show(10439);
-		//     } else {
-		//         text_show(10461);  // text_104_61
-		//     }
-		//     player.command_ready = false;
-		//     return;
-		// }
-
-		if (player_parse(3, 347, 0) || player_parse(17, 347, 0)) {  // Queen_Mother (347)
-			text_show(10456);  // text_104_56
+		if (player_said_1(floor)) {
+			if (global[player_persona] == PLAYER_IS_PID) {
+				text_show(10439);
+				player.command_ready = false;
+				return;
+			}
+		}
+
+		if (player_said_1(wall)) {
+			if (global[player_persona] == PLAYER_IS_PID) {
+			    text_show(10439);
+			    player.command_ready = false;
+			    return;
+			}
+		}
+
+		if (player_said_1(candlestick)) {
+			if (global[player_persona] == PLAYER_IS_PID) {
+			    text_show(10439);
+			} else {
+			    text_show(10461);
+			}
 			player.command_ready = false;
 			return;
 		}
 
-		if (player_parse(3, 350, 0) || player_parse(17, 350, 0)) {  // MacMorn (350) — King reach
-			text_show(10444);  // text_104_44
+		if (player_said_1(Queen_Mother)) {
+			text_show(10456);
+			player.command_ready = false;
+			return;
+		}
+
+		if (player_said_1(MacMorn)) {
+			text_show(10444);
 			player.command_ready = false;
 			return;
 		}
 	}  // end look/look_at block
 
 	// push / pull + rug (18)
-	if (player_parse(5, 18, 0) || player_parse(10, 18, 0)) {
+	if (player_said_2(push, rug) || player_said_2(pull, rug)) {
 		if (global[player_persona] == PLAYER_IS_KING) {
-			text_show(10406);  // text_104_06
+			text_show(10406);
 		} else {
 			text_show(10445);
 		}
@@ -2058,9 +2043,9 @@ void room_104_parser() {
 	}
 
 	// push / pull + fireplace_screen (35)
-	if (player_parse(5, 35, 0) || player_parse(10, 35, 0)) {
+	if (player_said_2(push, fireplace_screen) || player_said_2(pull, fireplace_screen)) {
 		if (global[player_persona] == PLAYER_IS_KING) {
-			text_show(10408);  // text_104_08
+			text_show(10408);
 		} else {
 			text_show(10445);
 		}
@@ -2069,9 +2054,9 @@ void room_104_parser() {
 	}
 
 	// push / pull + trophy (200)
-	if (player_parse(5, 200, 0) || player_parse(10, 200, 0)) {
+	if (player_said_2(push, trophy) || player_said_2(pull, trophy)) {
 		if (global[player_persona] == PLAYER_IS_KING) {
-			text_show(10413);  // text_104_13
+			text_show(10413);
 		} else {
 			text_show(10445);
 		}
@@ -2080,9 +2065,9 @@ void room_104_parser() {
 	}
 
 	// open + reading_bench (201)
-	if (player_parse(6, 201, 0)) {
+	if (player_said_2(open, reading_bench)) {
 		if (global[player_persona] == PLAYER_IS_KING) {
-			text_show(10415);  // text_104_15
+			text_show(10415);
 		} else {
 			text_show(10445);
 		}
@@ -2091,9 +2076,9 @@ void room_104_parser() {
 	}
 
 	// push / pull + loveseat (203)
-	if (player_parse(5, 203, 0) || player_parse(10, 203, 0)) {
+	if (player_said_2(push, loveseat) || player_said_2(pull, loveseat)) {
 		if (global[player_persona] == PLAYER_IS_KING) {
-			text_show(10417);  // text_104_17
+			text_show(10417);
 		} else {
 			text_show(10445);
 		}
@@ -2102,39 +2087,38 @@ void room_104_parser() {
 	}
 
 	// open / push / pull + secret_door (275) or wall_panel (276)
-	if (player_parse(6, 275, 0) || player_parse(6, 276, 0) ||
-	    player_parse(5, 275, 0) || player_parse(5, 276, 0) ||
-	    player_parse(10, 275, 0) || player_parse(10, 276, 0)) {
-		text_show(10431);  // text_104_31
+	if (player_said_2(open, secret_door) || player_said_2(open, wall_panel) ||
+		player_said_2(push, secret_door) || player_said_2(push, wall_panel) ||
+		player_said_2(pull, secret_door) || player_said_2(pull, wall_panel)) {
+		text_show(10431);
 		player.command_ready = false;
 		return;
 	}
 
-	// invoke (47) + signet_ring — TODO: verify signet_ring vocab ID
-	// if (player_parse(47, ???, 0)) {
-	//     if (global[player_persona] == PLAYER_IS_PID) {
-	//         text_show(10433);  // text_104_33
-	//         player.command_ready = false;
-	//         return;
-	//     }
-	// }
+	if (player_said_2(invoke, signet_ring)) {
+		if (global[player_persona] == PLAYER_IS_PID) {
+		    text_show(10433);
+		    player.command_ready = false;
+		    return;
+		}
+	}
 
 	// put (7) + tentacle_parts (125) + wall_panel (276)
-	if (player_parse(7, 125, 276, 0)) {
+	if (player_said_3(put, tentacle_parts, wall_panel)) {
 		if (global[player_persona] == PLAYER_IS_KING) {
-			text_show(10446);  // text_104_46
+			text_show(10446);
 			player.command_ready = false;
 			return;
 		}
 	}
 
 	// shift_into_bear (116) — bear transformation in finale
-	if (player_parse(116, 0)) {
+	if (player_said_1(shift_into_bear)) {
 		if (local->anim_0_running) {
 			if (local->has_been_bear) {
-				text_show(10457);  // text_104_57
+				text_show(10457);
 			} else if (local->amulet_works) {
-				text_show(10459);  // text_104_59
+				text_show(10459);
 			} else {
 				local->has_been_bear = true;
 				global[player_score] += 2;
@@ -2154,8 +2138,8 @@ void room_104_parser() {
 	}
 
 	// sword(56)+attack(57)/carve_up(58)/thrust(87) on MacMorn(350), or take(4)+sword(56)
-	if (player_parse(56, 57, 350, 0) || player_parse(56, 58, 350, 0) ||
-	    player_parse(56, 87, 350, 0) || player_parse(4, 56, 0)) {
+	if (player_said_3(sword, attack, MacMorn) || player_said_3(sword, carve_up, MacMorn) ||
+		player_said_3(sword, thrust, MacMorn) || player_said_2(take, sword)) {
 		if (local->anim_0_running) {
 			local->activate_timer = false;
 			local->pid_action     = PID_DRAW_SWORD;
@@ -2182,7 +2166,7 @@ void room_104_parser() {
 	}
 
 	// invoke (47) + amulet (46)
-	if (player_parse(47, 46, 0)) {
+	if (player_said_2(invoke, amulet)) {
 		if (local->anim_0_running) {
 			if (local->amulet_works) {
 				local->activate_timer   = false;
@@ -2198,14 +2182,12 @@ void room_104_parser() {
 				kernel_synch(KERNEL_SERIES, seq[fx_e5], KERNEL_NOW, 0);
 
 				ss[fx_e3] = kernel_load_series(kernel_name('e', 3),
-				                               PAL_MAP_ALL_TO_CLOSEST | PAL_MAP_ANY_TO_CLOSEST);
+					                           PAL_MAP_ALL_TO_CLOSEST | PAL_MAP_ANY_TO_CLOSEST);
 				aa[1]                 = kernel_run_animation(kernel_name('m', 2), 0);
 				local->anim_1_running = false;
 				local->anim_5_running = true;
 			} else {
-				// TODO: object_examine(amulet_obj_id, 945, 0);
-				//   amulet_obj_id = inventory object ID for amulet — needs verification
-				//   945 = text_009_45 message ID
+				object_examine(amulet, 945, 0);
 			}
 			player.command_ready = false;
 			return;
@@ -2213,61 +2195,59 @@ void room_104_parser() {
 	}
 
 	// sword+attack/carve_up/thrust on Queen_Mother (347)
-	if (player_parse(56, 57, 347, 0) || player_parse(56, 58, 347, 0) ||
-	    player_parse(56, 87, 347, 0)) {
-		text_show(10458);  // text_104_58
+	if (player_said_3(sword, attack, Queen_Mother) || player_said_3(sword, carve_up, Queen_Mother) ||
+		player_said_3(sword, thrust, Queen_Mother)) {
+		text_show(10458);
 		player.command_ready = false;
 		return;
 	}
 
 	// talk_to (8) + MacMorn (350)
-	if (player_parse(8, 350, 0)) {
-		text_show(10464);  // text_104_64
+	if (player_said_2(talk_to, MacMorn)) {
+		text_show(10464);
 		player.command_ready = false;
 		return;
 	}
 
 	// talk_to (8) + Queen_Mother (347)
-	if (player_parse(8, 347, 0)) {
-		text_show(10463);  // text_104_63
+	if (player_said_2(talk_to, Queen_Mother)) {
+		text_show(10463);
 		player.command_ready = false;
 		return;
 	}
 
 	// talk_to (8) + king (291)
-	if (player_parse(8, 291, 0)) {
-		text_show(10465);  // text_104_65
+	if (player_said_2(talk_to, king)) {
+		text_show(10465);
 		player.command_ready = false;
 		return;
 	}
 
-	// pour_contents_of + MacMorn — TODO: verify pour_contents_of vocab ID
-	// if (player_parse(???, 350, 0)) {
-	//     text_show(10462);  // text_104_62
-	//     player.command_ready = false;
-	//     return;
-	// }
+	// pour_contents_of (99) + MacMorn (350)
+	if (player_said_2(pour_contents_of, MacMorn)) {
+		text_show(10462);
+		player.command_ready = false;
+		return;
+	}
 
-	// walk_across (41) / walk_to (13) when Queen is in room
 	if (local->anim_2_running) {
-		if (player_parse(41, 0) || player_parse(13, 0)) {
-			text_show(10445);  // text_104_45
+		if (player_said_1(walk_across) || player_said_1(walk_to)) {
+			text_show(10445);
 			player.command_ready = false;
 			return;
 		}
 	}
 
-	// take (4) + candlestick — TODO: verify candlestick vocab ID
-	// if (player_parse(4, ???, 0)) {
-	//     text_show(10468);  // text_104_68
-	//     player.command_ready = false;
-	//     return;
-	// }
+	if (player_said_2(take, candlestick)) {
+		text_show(10468);
+		player.command_ready = false;
+		return;
+	}
 
 	// open (6) + music_box (414) when in finale scene
-	if (player_parse(6, 414, 0)) {
+	if (player_said_2(open, music_box)) {
 		if (global[no_load_walker]) {
-			text_show(10470);  // text_104_70
+			text_show(10470);
 			player.command_ready = false;
 			return;
 		}




More information about the Scummvm-git-logs mailing list