Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Combat Runtime

Architecture

Combat is implemented by seven scene programs, COMBAT1.BIN through COMBAT7.BIN. The executable supplies generic animation, selectable-action, thread, random-number, sound, and faith-loss operations; each BIN program combines those primitives into an encounter. This is a script-driven system, not a conventional enemy object with a health field in the ten-byte display record.

Every combat program loads COMBTAGS.ART, COMBAT.ART, and one or more enemy-specific ART resources:

ProgramManual identityBytesCommandsAnimation sequencesStepsActionsEnemy ART bases
COMBAT1.BINMacho3,300625331824BIG, BIG2, BIG3, BIG4
COMBAT2.BINArmored7,230973305944HELMET
COMBAT3.BINMantis4,381782382604MANTIS, MANTIS2, MANTIS3
COMBAT4.BINSnake7,4341,073295814SNAKE, SNAKE2
COMBAT5.BINSpider6,501919345234CRAB
COMBAT6.BINLeech2,314351151633GUARD, GUARD2
COMBAT7.BINZapper4,289720352934ZAP, ZAP2, SPRK
Total35,4495,4432142,59627

The programs branch on script variables and progression flags, including the Sword and Shield power flags 0x30 and 0x31. They use opcode 0x81 to reduce the player’s faith. Encounter outcomes, enemy phases, and exits are therefore expressed as script control flow and persistent state changes. No separate enemy-health structure has yet been found.

Animation definitions

Opcode 0x06 begins an animation definition. Its word operand becomes the sequence interval. The immediately following opcode-0x07 commands are the steps retained in the BIN stream; executing 0x07 merely advances over its nine-byte payload.

Each step has this layout:

Payload offsetSizeMeaning
+01One-based ART frame.
+11Loaded ART slot.
+22Signed X coordinate.
+42Signed Y coordinate.
+628.8 scale; 0x0100 is native size.
+81Render flags.

The runtime animation table starts at DS:6EBA, has a 12-byte stride, and is counted by the word at DS:B114. The recovered fields are:

Record offsetSizeMeaning
+02BIN offset of the first step.
+22BIN offset of the current step.
+42Sequence interval/countdown input.
+62Linked or parent animation index.
+81Animation mode/state.
+91Associated render/display slot.
+102Internal timing state; exact split remains unnamed.

Opcode 0x08 reads animation, mode and starts the slot with parent/link value -1. Opcode 0x5F reads animation, linked animation, mode and supplies the explicit link. Opcode 0x09 stops a slot and releases its render slot. Opcode 0x3F suspends the current BIN thread unless the animation state is 0, 5, or 6. Opcode 0x80 branches when a chosen animation’s state byte is nonzero; opcode 0x8A branches for exactly the same three finished states accepted by 0x3F.

The executable routines at 0x3B9B, 0x3D08, 0x3DA8, 0x3F59, and 0x3FDF resolve linked transforms, render one slot, update all slots, start a slot, and stop a slot respectively. The updater advances through the BIN steps in ten-byte command units and implements animation modes 1 through 10. Disassembly of 0x3F59 shows that only modes 2, 4, 6, and 10 initialize at the final step; mode 8 starts at the first step. At the boundary, 0x3DA8 backs a mode-7 overrun up by one ten-byte record, retaining the final frame and changing to mode 8. A mode-8 underrun advances by one record, retaining the first frame and changing to mode 7. The complete observed behavior is:

ModeStartDirectionBoundary result
1FirstForwardStop and release after the final step.
2LastBackwardStop and release before the first step.
3FirstForwardWrap to first.
4LastBackwardWrap to last.
5FirstNoneRetain first in terminal state 5.
6LastNoneRetain last in terminal state 6.
7FirstForwardRetain last and change to mode 8.
8FirstBackwardRetain first and change to mode 7.
9FirstForwardRetain last and change to terminal state 6.
10LastBackwardRetain first and change to terminal state 5.

Selectable action targets

Opcode 0x3A appends a ten-byte action target to the table at DS:480E; the word at DS:6EA4 is its count:

Record offsetSizeMeaning
+02Absolute target offset in the current BIN program.
+22Screen X coordinate.
+42Screen Y coordinate.
+62Offset of a selector string in the current BIN.
+81Active flag.
+91Reserved/padding.

Opcode 0x3B enables one record and 0x3C disables it. Opcode 0x41 enables action selection globally; 0x42 disables it and clears pending selection state. The overlay routine at 0x6A23 scans active targets, compares their coordinates with the pointer, decodes the selector string, and draws the corresponding label. The keyboard path at 0x8558 searches the same table. A selected record is dispatched through the BIN-thread start routine at 0x7A5C.

The overlay scan first checks enabled scene-thread selectors and then the ordinary action-target table. Function 0x3315 computes max(abs(dx),abs(dy)) + floor(min(abs(dx),abs(dy))/2). Candidates above 0x46 are rejected, and strict less-than replacement preserves the first candidate on a tie. The chosen label is submitted in transient render slot 0x97; that slot is released when no target qualifies. For .u, .d, .l, and .r, the executable selects one-based STUFF.ART frames 8 through 11.

The numeric path at 0x6B94 through 0x6CCE interprets a selector .XY as loaded ART slot X and one-based frame Y. It submits that resource frame using the target’s X/Y anchor and the frame’s signed descriptor origin. This is why the combat names are not drawn with the font and why .23 elsewhere can use the same generic overlay path.

The keyboard matcher at 0x8558 normally compares the character after the dot. For .1N it instead compares N, exposing combat selector keys 1 through 4. The translation routine at 0x866B, with its switch table at 0x86E8, maps A and S to 1, D to 2, R to 3, G to v, and U to x. C first searches for an ordinary .c selector and falls back to 4 only when none is active. Other letters pass through. Both keyboard and overlay scans preserve scene-thread selector priority over ordinary action records.

COMBTAGS.ART contains four label frames. Rendering all four with 019_ZAP.PAL identifies the selector mapping independently of the scripts:

SelectorFrame labelDefinitions in seven combat programs
.11ATTACK7
.12DEFEND6
.13RETREAT7
.14COMBAT7

The world-map chapter documents the independent hall-kind and transition evidence for these manual identities. COMBAT6.BIN is the only program without a DEFEND target. For example, COMBAT7.BIN defines:

SourceTargetXYAction
0x0C060x0EC815161ATTACK
0x0C110x0EAB136153DEFEND
0x0C1C0x105315167RETREAT
0x0C270x0FA715762COMBAT

These coordinates are screen hotspots rather than enemy bounding boxes. The same generic table can represent selectable actions elsewhere, so its runtime name is deliberately broader than “combat buttons.”

BIN threads and synchronization

The command-stream scheduler uses 16-byte slots beginning at DS:8D44; the current slot index is at DS:7DB4. The fields proven so far include the BIN cursor at +0, a delay/timer at +0x0C, an active byte at +0x0E, and a status byte at +0x0F.

Opcode 0x3E activates a selected slot at an absolute BIN target and runs it immediately. Opcode 0x61 clears a slot’s active byte. This is the mechanism used after an action target has supplied a branch destination. It is distinct from the separate 16-byte record family created by opcode 0x02, whose type 0x02 entries also participate in the scene display/update list.

The shared start routine at 0x7A5C does more than replace the cursor. It sets the active byte, writes the target, primes the delay from the negated current controller delta, clears the status byte, and immediately invokes update_scene_threads for that slot. In host terms this is a fresh, ready invocation which runs before its caller continues. Action selection uses the same routine for slot zero. Retaining the idle loop’s existing negative delay instead leaves a combat choice dormant for hundreds or thousands of logical timer units.

Opcode 0x59 waits for a digital effect to finish. With usable playback it yields and retries while the driver reports an active effect. Without usable playback it subtracts 100 from the calling scheduler slot’s delay, advances past the command, and lets the ordinary per-thread timer provide the pause. Opcode 0x82 computes a runtime pseudorandom value modulo its first operand and stores the remainder in the script variable selected by its second operand. Together with animation waits, these operations let the combat scripts sequence visual attacks, sounds, and randomized branches without embedding those policies in an enemy structure.

Action and outcome control flow

The four actions are entry points into the same encounter program, not numeric combat operations. ATTACK disables the current choices and runs an enemy-phase-specific animation path. DEFEND changes the available timing window; when Shield flag 0x31 is set, the scripts leave the attack option available longer. COMBAT uses opcode 0x82 and branches on Sword flag 0x30 and Shield flag 0x31 to choose successful, harmless, and faith-damaging sequences. The exact visual phase that makes each enemy vulnerable remains encoded in scene-local animation and counter variables.

Opcode 0x81 contains the base loss used on Normal difficulty. The seven programs contain these loss sites:

ProgramBase faith-loss immediatesVictory map mutationRetreat target and exit entry
COMBAT1533, 2,011kind 0xB0x0C38 -> 0x0C9D
COMBAT2107, 102, 502kind 0xB0x1BAC -> 0x1BF6
COMBAT31,037, 531, 2,011, 1,703kind 0xB0x10C3 -> 0x10D6
COMBAT4596, 1,005kind 0xB0x1C9D -> 0x1CC3
COMBAT5213, 2,009kind 0xB0x18F7 -> 0x1902
COMBAT6nonekind 0xA; copy parameter B to A0x087B -> 0x087E
COMBAT7233, 207kind 0xB; restore faith0x1053 -> 0x105E

Each Retreat target is itself an unconditional jump to the common exit entry. It therefore skips the victory-only map mutation. The table records all static loss sites rather than claiming that every site executes in one fight; branches choose among them. Easy halves each immediate, Difficult multiplies it by four, and installation no-combat mode suppresses it.

COMBAT7 contains the manual’s exceptional Zapper reward directly. Its victory subroutine alternates faith between 1 and 10,000 five times, producing a visible meter flash and ending at full faith. This happens before kind 0xB is written to the defeated encounter’s map cell.

Shared encounter epilogue

Six programs set state flag 0x38 after defining their action table and clear it in the common exit. The Game Options input routine tests the same flag and disables the Automatic Combat menu target while it is set. Flag 0x38 is therefore the combat-active lock, while flag 0x37 stores the Automatic Combat setting itself. COMBAT6 is exceptional: it has no DEFEND target, never changes flag 0x38, contains no faith-loss opcode, and produces a different map transition. Its internally named GUARD encounter is the Leech Cyber covering a Scripture station: victory reveals the station and transfers its saved verse selector into the active field.

After victory or retreat, the programs clear current-cell parameter A and select a hall scene. Variable 67 chooses the literal GHALB or GHALS variants for two special cases. Otherwise opcode 0x7A patches the first byte of the inline CHAL string from variable 16, the current map-level letter, yielding the appropriate AHAL through GHAL resource name. Opcode 0x7E starts a palette blackout immediately before these scene changes.

All seven combat programs also expose a separate POWER scene-change entry. POWER.BIN is the in-combat study/power interface, not a game-over scene. On a successful selection it copies the current combat number, adds ASCII '0', patches the digit in the inline name combat1, and changes back to that encounter. The caller path into this special entry has not yet been fully recovered.

Inspection

Inspect animation definitions and action targets with:

tools/inspect_bin.py \
  build/dd1/all/337_COMBAT7.BIN --animations --actions

The summaries follow linear definition order. Calls and branches can skip or repeat definitions, and enable/disable commands determine which action targets are live at a particular moment.

Live COMBAT1 table validation

A visible, silent QEMU capture now validates the three runtime table families. The route was controlled rather than a natural walk to an encounter: a genuine hall quick-save contained snapshot/live scene names MENU/CHAL, and tools/patch_save_scene.py changed only both 20-byte scene-name fields to COMBAT1. The modified state round-tripped through the FAT image byte for byte. Loading it with F9 produced the Macho combat screen, and the A key started a visible green attack effect.

The first dump caught scene initialization between visible redraws, with counts 0 and 1, so it is not used for table comparison. A second one-MiB physical dump after the attack input had SHA-256 becc98dd2eba0bad502f1bf6b7aef4ef2638fa48d0e0e62688ad879085bc1654. QEMU reported DS=14E1; tools/inspect_runtime_tables.py therefore read the same data offsets documented above at physical base 0x14E10.

The stable capture contains four action records and 33 animation records. All four action target/X/Y/selector-offset tuples match the static COMBAT1 definitions. ATTACK, DEFEND, and RETREAT are active; the automatic COMBAT target is present but inactive. Every animation record’s first-step offset and interval matches its corresponding BIN definition, 33 of 33. Current-step, link, state, render-slot, and timing values are retained as live values rather than forced to equal their initial definitions.

The first ten 16-byte scheduler records were also saved. Current slot is zero; slots 0, 5, 7, and 8 have active byte 1. Slot 0 has cursor 0x0C35 and signed delay -824 at the sampled instant. The remaining opaque bytes are emitted in hex so later field naming will not lose evidence. The exact comparison command is:

tools/inspect_runtime_tables.py \
  build/formal-captures/combat-after-a-memory.bin \
  --data-segment 0x14e1 \
  --bin build/dd1/all/343_COMBAT1.BIN

This validates the table addresses, strides, counts, and statically comparable fields. It does not prove a natural map-to-encounter transition, nor does the controlled state preserve a normal combat outcome: the patched checkpoint had zero snapshot faith, and the run subsequently reached the “Don’t Give Up!” screen. Those provenance limits are intentional and recorded rather than being generalized to ordinary play.

Remaining questions

  • Name the final two bytes of each animation slot and every mode’s exact transition rule.
  • Separate the opcode-0x02 interactive/display record family from the true BIN scheduler slots field by field.
  • Correlate every randomized branch with the exact enemy phase and rendered successful, harmless, or damaging sequence.