deepened understanding
This commit is contained in:
parent
a70ec15899
commit
73931629ae
32 changed files with 5011 additions and 259 deletions
334
plan-mid.md
334
plan-mid.md
|
|
@ -15,284 +15,129 @@ Detailed completed analysis belongs in the files under `docs/`, not in this plan
|
|||
|
||||
## Progress Snapshot
|
||||
|
||||
- Overall useful decompilation progress: about 50%
|
||||
- Reasonable uncertainty band: about 45% to 52%
|
||||
- Top 100 far-call target coverage: about 80%
|
||||
- Segment spread with meaningful analysis: about 26% to 32%
|
||||
- Tooling maturity for continued work: about 77%
|
||||
Latest verified batch: [docs/combat-dat.md](docs/combat-dat.md) now closes the shipped combat-tactic data file as a documentation target instead of leaving it as a scratch-note reference. Current best read is that all local Remorse/Regret variants share one identical `14`-record `COMBAT.DAT`, the live NE database now already has the right tactic/process field anchors (`combatDatTacticPtr`, `combatDatTacticCurOffset`, `combatDatBlockNo`, `tacticNo`) plus setup helpers, and the shipped opcode subset is now decoded into a full human-readable tactic catalog using direct binary parsing plus the ScummVM Crusader attack-process interpreter as a reference model.
|
||||
|
||||
### Why The Estimate Moves Slightly
|
||||
- Overall useful decompilation progress: about 58%
|
||||
- Reasonable uncertainty band: about 55% to 63%
|
||||
- Top 100 far-call target coverage: about 86%
|
||||
- Segment spread with meaningful analysis: about 34% to 40%
|
||||
- Tooling maturity for continued work: about 83%
|
||||
|
||||
- Recent work materially improved semantic confidence inside the startup/display, cache/allocator, callback-object, and USECODE/VM lanes.
|
||||
- The startup/display lane is now materially complete as an active major section: the shared `g_active_dispatch_entry_farptr[+0x40]` hold token is separated from the seg108-local `0x4f38` bit-`0x40` lane, the seg126 control stream is confirmed as file-backed, the paired `0x8c5c/0x8c60` renderer objects are narrowed to two script-selected preset text lanes, and the neighboring seg127 fade controller now has an exact local contract at `0x630a..0x6316`.
|
||||
- The current VM/loader batch also justified a small bump: `000d:ebe3` is now a named ordered opcode sequencer with a tighter entry/exit contract, the masked-create hub at `000d:463a` is now a verified owner-table gate rather than an inferred wrapper sink, and the seg070 twin loops under `entity_vm_runtime_owner_resource_create` now read as paired file-family loaders writing into separate temporary buffers rather than one ambiguous callback shard.
|
||||
- The latest live-NE caller-family batch justified another small confidence bump: the remaining direct `0005:295f -> 10a0:275f` callers now close to `Item_ReceiveHit` and `SuperSprite_HitAndFinish` non-NPC damage lanes, which removes the last direct-caller ambiguity in that selector-consumer island even though the upstream class-family selector is still unresolved.
|
||||
- The latest USECODE pass justified another small VM-lane bump: the gameplay-side wrapper ladder now extends through slots `0x10..0x14` with verified mixed payload shapes (`none` vs extra signed word), the new slot-only Ghidra names keep that taxonomy visible without overpromoting event labels, and the `000d:22bc` stage is now comment-backed as a sequencer-internal link-matrix/pushback consumer over decoded workspace bytes rather than a direct descriptor-row reader.
|
||||
- The immortality follow-up justified another small tooling-and-confidence bump: the extractor now emits a dedicated target-body scan, the strongest current USECODE candidates show no inline `0x410` / `0x00000410` literal, and the remaining frontier is narrowed to data-driven decoding of `EVENT` slot `0x0a` plus `NPCTRIG` slots `0x0a` / `0x20` rather than the older wider trigger family set.
|
||||
- The latest owner-loaded range pass justified another small confidence bump too: the owner-resource child selector now matches extracted `class_id + 2` exactly, the class header/subentry math at `000d:5066/51fd/53b4` is closed against the extractor's raw headers and event rows, and the surviving immortality uncertainty has moved from `can the loader fit NPCTRIG arithmetic at all?` to the narrower `which class family is actually selected upstream?` question.
|
||||
- The PSX sprite-extraction side is also less speculative now: a dump-grounded pass proved the known-colored wall-console bundle `bundle_000A1B04` already exists verbatim in live VRAM at texture page `(1,1)`, and the corrected working color formula is the top-left live CLUT candidate from the atlas, namely the contiguous `256`-entry slice at GPU row `0xF0`, `x=0`; the same rule now produces plausible output across a wider `92`-bundle `mode 1` batch instead of only the single cabinet proof case.
|
||||
- The PSX executable-side catalog lane is tighter too: `SLUS_002.68` now has comment-backed proof that `wdl_resource_bundle_load_by_index` selects seven hardcoded `\LSETn\L` prefixes across thresholds `10/20/30/40/50/60`, the extracted disc currently ships `62` level bundles (`L0..L58`, `L62..L64`) with a real gap at `L59..L61`, the executable exposes only `15` plain-text `Mission Briefing ^Mission N` strings, and the mission-complete passcode path now has a closed `4`-character consonant/digit alphabet at `80063ef0` plus direct ammo/item/weapon name tables. The remaining PSX passcode gap is now narrower: public cheat-password candidates `XXXX` and `L0SR`/`L0SER` are not stored as plain ASCII in `SLUS_002.68`, so the compare path likely uses numeric or transformed validation instead of a flat string table.
|
||||
- The F7-overlay lane is tighter again after the latest live/exported cross-check. New note `docs/f7-overlays.md` now separates the three cheat-gated overlays by their actual geometry source: plain `F7` is the coarse origin-aligned `0x200`-unit world lattice; `Ctrl+F7` is the egg-hatcher trigger-footprint overlay driven by `EggHatcher_1090_0921`; and `Alt+F7` is narrower than the earlier viewer approximation because the runtime only feeds `Snap_AddSnapEgg` from shape `0x04fe`, with `Snap_GetSnapEggRange` deriving each snap rectangle from that item's `QHi`, `mapNum`, and `npcNum` bytes. The practical viewer implication is also closed more cleanly now: do not center-snap the plain grid, and do not treat `Alt+F7` as generic egg-family coverage.
|
||||
- The new PSX pre-alpha comparison lane is also anchored now: `/psx/prealpha/SLUS_002.68` still carries direct `Crusader: No Remorse` branding, the same retail-style `wdl_resource_bundle_load_by_index` `\LSET1\L .. \LSET7\L` threshold ladder, and the same `15` mission-briefing/passcode shell, but the unpacked `Crusader 2 Pre-Pre Alpha` disc currently ships only `3` level bundles, `1` XA, and no `.STR` movies. The most interesting current mismatches are architectural leftovers that no longer match the disc literally, especially the missing-file `\AUDIO\TALK1.XA;1` path and the surviving `LoadExec` helper for `MENU.EXE` / `ENGINE.EXE` / `PSX.EXE`.
|
||||
- That closes one live top-priority section and justifies a small headline increase even though the remaining work is still breadth-heavy.
|
||||
### Why The Estimate Moved
|
||||
|
||||
- The NE `CRUSADER.EXE` database now has materially more named functions, better caller-role coverage, and broader comment-backed provenance than when this tracker was first drafted.
|
||||
- The startup/display lane is no longer a top active section. Its outer ownership and control flow are stable enough that it should stay closed unless new caller evidence changes the model.
|
||||
- The cheat/debug lane is also much tighter: the `jassica16` latch, the broader `-laurie` gate, the `~` runtime toggle, the F7-family overlays, the F10/Ctrl behavior, and the `0x410` CD-transfer-display branch are now separated well enough that this lane is mostly documentation and cleanup, not architecture recovery.
|
||||
- The USECODE/VM lane has moved from broad structure guesses to a partial runtime model: core loader/runtime helpers are named, owner-loaded slot arithmetic is verified against extracted corpora, several masked-create helpers have real contracts, and the major remaining uncertainty is now the upstream selector/caller path rather than the storage format itself.
|
||||
- The map-renderer crosswalk lane also removed a lot of lingering shape ambiguity by closing more controller/helper families directly from extracted corpora plus scene evidence.
|
||||
- The combat-tactic data lane is also now materially tighter: `COMBAT.DAT` is no longer just a named-tactic hint source, but a documented bytecode archive with stable per-record names, verified block structure, a decoded shipped opcode subset, and a practical family-level behavior map for the `Dumb`, `Pivot`, `Advance`, `Careful`, marker-shuttle, and step-out-shoot tactics.
|
||||
|
||||
## Current Verified State
|
||||
|
||||
### Primary Tracking Assets
|
||||
|
||||
- `crusader_segment_coverage_ledger.csv` now exists for all 145 NE segments and should remain the primary coverage tracker.
|
||||
- `crusader_decompilation_notes.md` is now only an index; detailed evidence lives in `docs/`.
|
||||
- `CRUSADER.EXE` is now the default live Ghidra target for ongoing work; verified `CRUSADER-RAW.EXE` results remain a cross-reference evidence base, especially for seg001/seg021 and earlier cheat/VM batches.
|
||||
- The raw full-EXE porting workflow remains stable as a supporting evidence path for the verified seg001 and seg021 mappings.
|
||||
- `crusader_segment_coverage_ledger.csv` remains the main executable-wide coverage tracker and should be updated after each verified batch.
|
||||
- `crusader_decompilation_notes.md` is an index, not the place for long-form analysis.
|
||||
- `CRUSADER.EXE` remains the default live Ghidra target.
|
||||
- Verified `CRUSADER-RAW.EXE` work remains a supporting evidence base for ports, naming provenance, and caller/context cross-checks.
|
||||
|
||||
### Strong Or Stable Areas
|
||||
|
||||
- seg001 gameplay/input/projectile work is deep enough to support verified raw-name ports.
|
||||
- raw 0007 rendering/camera/tile-visibility work is structurally strong.
|
||||
- 0008 dispatch-entry helpers and 000c state-machine helpers have broad partial coverage.
|
||||
- 000a/000d tracked-handle, cache, allocator, dispatch-entry, and startup/display support lanes now have a coherent partial map.
|
||||
- 000e parser and animation subsystems have a real partial map.
|
||||
- The auxiliary local disassembly corpus at `K:/ghidra/crusader-disasm` is now inventoried and integrated as a separate evidence source for shape metadata, static map/object dumps, opcode names, and older Remorse/Regret intrinsic-function vocabularies; its safe-reuse rules and porting implications are captured in `docs/crusader-disasm-reference.md`.
|
||||
- The PSX side now has a first explicit pre-alpha comparison note too. `docs/psx/prealpha.md` records that `/psx/prealpha/SLUS_002.68` is still much closer to a reduced No Remorse PSX branch than to a visibly rebranded sequel executable: the live database now has `wdl_resource_bundle_load_by_index` renamed at `80038084`, and comment-backed notes on the stale `TALK1.XA` selector helper and the split-`LoadExec` `MENU.EXE` / `ENGINE.EXE` / `PSX.EXE` path that no longer matches the current unpacked disc tree.
|
||||
- The workspace now also has a first dedicated offline map-rendering/tooling lane: `tools/render_crusader_map.py` can load a chosen `FIXED.DAT`, expand `GLOB.FLX`, decode required `SHAPES.FLX` frames, apply `GAMEPAL.PAL`, and emit a first-pass PNG from either static set, while `docs/map-rendering.md` captures the current format contracts, the `--fixed-dat` override, and the intentionally limited compositor model.
|
||||
- The map/editor-visibility lane is now tighter too. New note `docs/editor-object-visibility.md` records live `CRUSADER.EXE` proof that the downstream item draw helper `1198:02e4` (`Item_PaintSprite`) explicitly returns early on `ShapeData.flags2 & 1` (`SI_EDITOR`), but the follow-up render-path pass also found the controlling upstream skip at `1180:0951..095c` in the world-item builder. Current best read is therefore `editor-tagged shapes are filtered before draw-node allocation in the normal world-item renderer, with a second downstream paint-time guard still present`, which also explains why a first patch that only flipped `1198:033b` produced no visible change in-game. No recovered retail `-debug`, cheat/debug hotkey, Laurie/usecode-debugger path, or `0x410` lane currently re-enables those objects. The closest confirmed toggle remains ScummVM's own `_showEditorItems` debugger command, which is engine-added rather than retail.
|
||||
- The localized-build comparison lane now covers the Japanese Windows-native executable too. New note `docs/jp-remorse-windows9x-investigation.md` records that `/ja/CRUSADER.EXE` is a PE-style Win32 image with native window creation, DirectDraw/DirectSound init, registry-backed config under `Software\Electronic Arts\Crusader: No Remorse\J1.21`, IME/DBCS-facing imports, and a `GetVersion`-driven Win9x compatibility branch that retries `TlsAlloc()` until the slot is above `2` when the classic Win9x version bit is set. Current best read is `real Windows 9x-native port with likely Win95 intent`, with runtime prerequisites still left to test.
|
||||
- The removed-item lane is tighter now too. `docs/removed_items.md` now records a live `CRUSADER.EXE` close on the inventory/display name path: retail `1118:056A` is `DTable_GetNameForShapeNo`, `1118:05D5` is its `INVALID` fallback returning `1478:238C`, and `Weasel_OnPaint` uses that same lookup family. The backing `1478:22BC` `char *[41]` array preserves exact explosive names inline, including `CONCUSSION GRENADE`, `NERVE GAS GRENADE`, `EMP GRENADE`, `SPIDER BOMB`, `LAND MINE`, `BLAST PAC`, and `FUSION PAC`, while repeating `INVALID` at slots `0/14/26/32`. The reusable Remorse dump at `out/dtable_get_name_dump.json` plus companion CSVs now closes the direct-table question too: `0548` is not a named dtable entry, so its in-game `Invalid` label is best explained as a plain fallback for an unmapped shape. The same tooling now also closes the Regret comparison side: live `REGRET.EXE` uses helper `1130:056a`, its recovered segment-`1480` dtable island expands the slot layout to `52` names with repeated `INVALID` at `0/17/36/44`, preserves Regret-only names such as `BK-16`, `LNR-81`, `XP-5`, `IONIC SHIELD`, `PLASMA SHIELD`, `RADIATION SHIELD`, and `VIR IMAGER`, and resolves bomb rows `0343`, `0350`, `0560`, `039A`, and `039C` while notably omitting `BLAST PAC`. Current best read remains narrower than the first pass: the removed grenade variants are real retail dtable names plus map leftovers, `LANDMINE`/`BLASTPAC`/`FUSPAC` are active Remorse classes rather than new removed items, and `SPIDER BOMB` is currently stronger as a cross-game dtable/comparison signal than as a fully closed Remorse finding.
|
||||
- The Japanese localized-build lane now also covers surviving cheat/debug and startup-argument behavior. New note `docs/jp-remorse-cheats-and-launch-params.md` records that the JP Win32 build still has a live `-laurie` special-case, a live `JASSICA16` cheat-state matcher, a still-executable immortality toggle path, and a working Win32 parser for `-debug`, `-u`, `-warp`, `-skill`, `-mapoff`, `-egg`, and `-demo`. The same pass also adds one important caveat relative to the older DOS-side docs: the JP Win32 parser is only directly closed for mission-only `-warp <mission>` so far, not for positional `-warp <mission> <x> <y> <z>`.
|
||||
- The startup map-selection lane is now tighter across both retail games too: No Remorse still hardcodes `Teleporter_CreateProcessDirect(1, 0x1e, 1)` inside `Game_Start`, while No Regret keeps the same literal selector in two live places, the early `Game_Start` site at `1008:1448` and the later authoritative new-game hop in `Game_RunNewGameFlow` at `1030:05c5`. The separate `-warp mission` path still uses an executable-embedded word table plus `-mapoff`, and the repo docs now include the dedicated REGRET-side note `docs/regret-game-start.md`. Current best read remains `startup map choice in code, map contents in external FIXED.DAT resources`, not `mission-start map configured in CRUSADER.CFG`.
|
||||
- That same warp-table lane is now exact across both retail DOS executables too. Byte checks against `CRUSADER.EXE` and `REGRET.EXE` now show matching 17-word `-warp mission` base-map tables (`0,1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,40`) at `1478:0488` and `1480:075c`, each followed by a `0,0` terminator. The public map renderer now also has a dedicated mission-table extractor and generated JSON cache, so scene metadata no longer has to treat mission/base-map usage as an unknown ownership question.
|
||||
- That same startup lane is now tighter at the argument level too. Current best parser/control-flow read in `REGRET.EXE` is `-warp <mission> [x y z]`, with X/Y/Z carried as positional argv tokens after the mission number rather than as separate recovered switches. The corresponding runtime branch in `Game_RunNewGameFlow` is also clearer: nonnegative `-egg` overrides beat the coordinate path, while the real eggless-map workaround is `-warp <mission> <x> <y> <z>` plus `-mapoff` with `-egg` omitted so the game falls into direct `NPC_Teleport` instead of the teleporter-egg lookup.
|
||||
- The matching No Remorse cross-check is now closed too. Live `CRUSADER.EXE` `HandleCommandlineArgs` at `1048:0adc` uses the same positional `-warp <mission> [x y z]` parser shape, and `Game_Start` at `1020:029e` / `1020:02d0` uses the same runtime precedence: direct coordinates only win when the egg override is still negative, otherwise the code falls back to `Teleporter_CreateProcessDirect`. The parameter-only eggless-map workaround is therefore shared across both retail games, not Regret-specific.
|
||||
- The public map-renderer link lane is tighter again. Cross-game `0x01DB` support now covers both the earlier frame-`1` teleporter-light helpers and the remaining Regret map `3` frame-`0` telepad helper placements that carry destination ids `27/28` in `quality`. The same pass also adds the checked same-map `ELEVATOR` lane: frame-`0` `shape:542` sources now link to local teleport-destination eggs by verified `QLo` rules (`1..0x0f -> same egg id`, `0x10 -> egg 4`). A new Regret follow-up closes the remaining map-`3` egg-`102` gap too: Regret `shape:400` (`0x0190`) is a second `ELEVATOR` family, `ELEVATOR::gotHit` uses a generic same-map lane for `QLo >= 100 && < 0x00c8`, and map `3` contains concrete source `item:664:fixed:400:0:44030:9662:0` with `quality 614` (`QLo 102`) linking to destination egg `102`. A second Regret-only editor-object follow-up now also promotes `WATCHNS`, `WATCHEW`, `CRYOBOX`, `CRAZYEW`, `CRAZYNS`, `VIDEOBOX`, and the `SECRET_DOOR_POST` / `PRESSURE_BARRIER_*` target shapes into the viewer, including cautious local arrows for `WATCH* -> 0x0510` and `CRYOBOX -> 0x05DF/0x05E0`.
|
||||
- The map-13 wall-jump follow-up is tighter too. The suspicious nearby start tile `fixed:4767` is now closed as `FFFLOOR` (`shape 0x0135` / `shape:309`) rather than as an editor/helper collision override: decoded reference data still marks it as ordinary `terrain` (`4 x 4 x 0`, `solid`, `fixed`, `land`), but the extracted EUSECODE corpus shows `FFFLOOR::gotHit/equip/unequip` as an environmental hazard/sensor lane that toggles nearby same-shape floors and pushes standing actors through `NPC.slot_2d` into the normal hit/damage path. The nearest same-level trigger companion in the retail cache is family-4 egg `fixed:4770`, which currently resolves to `CHANGER`, so the local map-13 setup now reads better as a small scripted floor/trigger cluster than as proof that the rare jump-through wall has an authored per-instance non-solid flag.
|
||||
- That same map-13 trigger companion is tighter now too. The nearby family-4 egg `fixed:4770` is no longer just a subtype label: retail Remorse clearly uses `QLo 4 -> CHANGER`, and the extracted `CHANGER::hatch` body plus the local decoded scene now line up as a keyed roof-destruction trigger (`mapNum` egg id `37`, nearby roof tiles with `QLo 37`). Current safest local read is `FFFLOOR hazard tile plus CHANGER roof-removal cluster` on the same upper platform, still not a direct wall-solidity override.
|
||||
- The editor-helper overlay lane is tighter too. A broader exported-scene sweep now shows that `BRO_BOOT` (`0x04FE`) really does form a repeatable local helper lane into nearby same-`QLo` `SPANEL` items, with concrete Remorse examples on maps `9`, `10`, `11`, `21`, `23`, `160`, and `246`, so the renderer now promotes `BRO_BOOT -> SPANEL` alongside the existing cmd-link, alarm, steam, door, and flame helper arrows. A later decompressed `.cache` pass tightens two Regret trigger families as well: `NPC_ONLY` (`0x0366`) and `CRUMORPH` (`0x0318`) now promote cautious local `-> 0x04B1` same-`QLo` arrows where authored matches exist, while `NPC_ONLY -> actor` and `DEATHBOX -> 0x04B1` still stay out of the overlay. The latest tooltip pass also upgrades `0x04B1` from a mostly structural decode to concrete operation notes: helper dispatch via nearby `0x0476`, direct target mutation, timed pulses through `TRIGGER.slot_22` / `DOOR.slot_21`, verified link rewrites, and a create-and-drop lane.
|
||||
- The same Regret controller lane is now better separated into `static helper links` versus `runtime actor-key links`. Current best read is that the withheld actor-target arrows (`CRUMORPH`, `NPC_ONLY`, and the wider sibling family) depend on mutable actor field `0x63`, not on a stable DTABLE export: sampled Regret DTABLE rows still show `0x00` at record byte `0x63`, while recovered `TRIGGER.slot_29` / `slot_2B` can rewrite field `0x63` on nearby matched NPCs after startup. The newly identified sibling families using that same hidden actor-key mechanism are `WATCHNS` / `WATCHEW`, `THRMBCKN` / `THRMBCKE`, and `SURCAMNS` / `SURCAMEW`. Current viewer/tooling stance remains `metadata + cautious helper arrows only` until a runtime or spawn-time export can close actor field `0x63` directly.
|
||||
- The skill-controller lane is tighter too. Shape `0x0120` is now closed as `FASTSKIL`, distinct from `SKILLBOX`: `enterFastArea` waits briefly, only runs while map-array is clear, uses frame `0/1` as difficulty thresholds for `TRIGGER.slot_20` lane `0` versus `1`, and uses frame `2` as an explicit `QLo/+1/+2` difficulty router. The renderer now exposes that decode in tooltips and adds conservative local `FASTSKIL -> 0x04B1` helper arrows, with frame-`2` variants for the recovered `QLo + 1` and `QLo + 2` lanes.
|
||||
- The switch/pad clarification lane is tighter too. Shape `0x0080` now closes as `BOX_EW`, and sampled exported scenes are strong enough to promote a conservative `BOX_EW frame 0 -> nearby same-QLo 0x04B1` helper arrow rule. Shape `0x04CD` now closes as `TRIGPAD`, but its broader occupancy/elevator behavior and the negative scene sweep keep it metadata-only instead of promoting a generic cmd-link overlay. Shape `0x033A` now reads best as a tiny `NUMBERS` readout/display helper family clustered with nearby `0x0501/0x0502/0x0503/0x0505/0x0507` pieces, so it also stays label-only.
|
||||
- The readable-usecode viewer lane is tighter too. New note `docs/map_renderer/trigger-usecode-links.md` records the evidence-backed class/event mapping now used for pinned controller tooltips: `BOX_EW`, `PANELNS`, `CARD_NS`, and `SPANEL` open `use`; `CRUMORPH`, `SKILLBOX`, `EVENT`, `ALARMHAT`, and `ALRMTRIG` open `equip`; `FASTSKIL` opens `enterFastArea`; `TRIGPAD` and `NPC_ONLY` open `gotHit`; and the `0x04B1` cmd helper jumps directly to `TRIGGER.slot_20`, the shared high-slot fan-out lane recovered from the extracted corpus and existing trigger notes.
|
||||
- The command-line lane is tighter around `-u` now too. In live non-Japanese `CRUSADER.EXE`, the parser case at `1048:0a46` copies the following token into `1478:065a`, and the renamed `startup_apply_u_override_if_present` at `1420:0cdf` later consumes that buffer to resolve/load an alternate usecode/EUSECODE source into `1478:6611/6613`, mark `1478:6615`, and rebuild the cumulative slot-base words at `1478:8c7c..8c82`. Current best read is `real retail startup usecode override`, not `JP-only` and not `dead string-table residue`; the paired consequence is that the older CRUSADER-side `-setver` attribution should now be treated as reopened until its exact retail consumer is isolated directly.
|
||||
- That same `-u` lane is now tighter at the runtime-scope level too. The follow-up note `docs/usecode-startup-override.md` now records that retail `-u` appears to replace the single live usecode root at `1478:6611/6613`, not add a sidecar table: `startup_apply_u_override_if_present` overwrites that root directly, rebuilds the cumulative slot-base words, and later consumers including `Usecode_ItemCallEvent`, `UsecodeProcess_CreateProcess`, `Interpreter_NextUsecodeOp`, and `Item_GetDamaged` all read the same replacement root. Current safest tooling implication is `runtime swap for the existing Crusader usecode VM`, which makes `-u` a potentially important future validation hook for round-tripped/custom usecode archives once the accepted source format is nailed down.
|
||||
- The same `-u` lane is tighter at the token-shape level now too. Live `1420:0cdf` does not use the copied argv token as an arbitrary final filename; it treats `1478:065a` as the `Filespec_GetFullPath` path component while loading the fixed mutable filename template `eusecode.flx` from `1478:07a0` through `1478:06d6/06d8` and forcing the first byte to `'e'` before both the existence probe and the final load call. Current safest read is therefore `path/root override for standard EUSECODE archive naming`, not `free-form filename override`. The stock bootstrap side is also better scoped: `1478:6611/6613` starts zero in the live NE image and the only currently recovered explicit writer there is the `-u` helper, so the normal non-`-u` seed remains only cross-referenced through the verified raw-side VM bootstrap note rather than fully live-NE-closed.
|
||||
- The same `-u` lane is tighter at the practical experiment level now too. The live helper shape plus direct bytes at `1478:079a` make the fixed filename template concrete as `eusecode.flx`, which materially weakens the older `maybe it accepts arbitrary archive filenames` hope. Current safest user-facing read is now `pass a directory/root to -u and place a complete replacement EUSECODE.FLX there`; failed forms like `-u USECODE/FLICTEST.FLX` and `-u FLICTEST.FLX` are best explained as path/root misuse rather than as evidence that the `-u` switch itself is dead.
|
||||
- The same override lane now has a concrete live-NE constructor pair too. `1420:1499` is now renamed `entity_vm_runtime_create` and currently reads as a `0x1319`-byte runtime-object allocator that zeroes a `0x1300`-byte front region behaving like `0x80` stride-`0x26` slot/runtime records before storing an attached helper pointer at `+0x1315/+0x1317`. `1430:0000` is now renamed `entity_vm_runtime_owner_resource_create` and currently reads as the compact `0x14`-byte file-backed helper that opens the resolved `eusecode.flx` path, queries entry count through vtable `+0x04`, allocates a backing buffer at `+0x10/+0x12`, and materializes indexed owner/resource records through vtable `+0x0c`. Current safest implication is that `-u` swaps the live VM runtime object graph, not just a raw archive handle.
|
||||
- The USECODE/VM owner/resource/runtime lane now has a workable partial model, a named sequencer entry, paired external file-family loader evidence, and supporting extraction/reporting tooling.
|
||||
- The USECODE/VM tooling lane now also has a concrete near-term implementation path: a Pentagram-derived proof-of-concept parser can reuse opcode decoding while swapping in the locally verified owner-loaded class and slot arithmetic, with a hybrid Ghidra comment/bookmark import path instead of a premature custom processor module.
|
||||
- The USECODE tooling lane now also has a first full readable corpus export: `tools/export_usecode_pseudocode.py` writes `977` current pseudocode bodies into `USECODE/EUSECODE_extracted/pseudocode`, and the first focused read of that corpus now shows `JELYHACK::use` / `JELYH2::use` as tiny shared `set_info(0x0207) -> process_exclude -> return` stubs rather than hidden active event cores.
|
||||
- The USECODE tooling lane now also has two new follow-up notes grounded in the exported corpus: `docs/usecode-tool-improvement-plan.md` turns the Pentagram/`crusader-disasm` comparison into a concrete parser roadmap, and `docs/usecode-alarmhat-analysis.md` records the current best evidence-backed read of `ALARMHAT::equip` as a frame-driven local alarm-state controller that equips nearby `shape 0x04D0` helper objects in different modes.
|
||||
- That same `0x04D0` lane is tighter again after the latest map-1/map-248 follow-up: decompressed cache evidence plus extracted `MONSTER.slot_0F`, `MONSTER.slot_0A`, and `ITEM.slot_2D` still show that frame-`0` / frame-`1` `0x04D0` pairs are authored on purpose and keyed locally by `Item.getQLo(...)`, but the old `frame 0 always equals the spawned NPC row` reading is no longer strong enough. Confirmed auto-enabled pairs now line up better if frame `0` is treated as the verified controller lane while frame `1` is treated as the practical visible-NPC cue.
|
||||
- The public renderer follow-up now reflects that narrower stance: the `Monster Spawners` panel still lists `0x04D0` records directly, fixed-record spawners expose copyable stable ids, tooltip/list semantics distinguish the verified frame-0 control path from the current frame-1 practical-preview heuristic, and paired previews now render once per pair instead of twice. Current preview rule is `blue active carrier / red dormant controller`, with the tooltip keeping the explicit `☑/☒` state label and the scene overlay dropping the older on-map checkbox badge. Exported-usecode corroboration still reaches beyond `ALARMHAT` too: `ITEM.slot_2D`, `FUSPAC.slot_01`, and `MISS8.slot_20` all show nearby `0x04D0` scans keyed by frame and/or `Item.getQLo(...)`, which strengthens the low-quality-byte-as-local-signal-key model without promoting it into a universal object pointer.
|
||||
- The USECODE tooling lane now also has a broader equipment-event note: `docs/usecode-equipment-system.md` records live binary proof that `Item_Equip` / `Item_Unequip` are real generic usecode event dispatchers gated by owner-row capability masks (`0x400` / `0x800`), and that the exported corpus currently contains `77` `equip` bodies plus `50` `unequip` bodies spread across actor, turret, alarm, conveyor, camera, and hazard classes. Current best read is `surviving Ultima-style event vocabulary generalized into activation/setup/state-change semantics`, not yet `fully proven paper-doll RPG gear subsystem`.
|
||||
- The USECODE tooling lane now also has its first implemented readability follow-through from that improvement list: `tools/poc_crusader_usecode_parser.py` and `tools/export_usecode_pseudocode.py` now regenerate the full `977`-body corpus with one verified wrapper alias seed (`FREE.waitNTimerTicks` for `0A0C:0032`), class-name-aware target rendering (`FREE.slot_21`, `BLASTPAC.slot_20`, `TRIGGER.slot_20`, etc.), first-pass selector decoding that upgrades the simpler alarm/trigger `loopscr` runs into `for ... in nearby_items(shape=..., origin=...)` / `for ... in nearby_items(family=..., origin=...)` loops, and a second readable selector-family fallback that collapses raw `loopscr 0x42` runs into `selector_0x42(arg0=..., arg1=..., arg2=..., origin=...)` annotations or `for ... in selector_0x42(...)` loops where the control flow is simple enough.
|
||||
- That same renderer lane is tighter again after the BLASTPAC follow-up: ScummVM `uc_machine.cpp` keeps the VM-side semantics anchored (`0x51` branch-on-false, `0x73` loopnext pushes a validity flag and frees exhausted search lists, `0x75/0x76` are real foreach iterators), and the map-viewer structurer now also treats jumps to the current region end label as structured exits. After regenerating the cache, `BLASTPAC.slot_01` no longer falls back to `block_0171` / `block_0415` style islands; the `shape 0x053A` scan is a real `for item in nearby_items(...)` loop and the later `target` / crouch lane is one nested `if/else` tree. The focused renderer regressions now cover both the synthetic region-end join case and the real `BLASTPAC.slot_01` body.
|
||||
- The USECODE/VM lane now also has a verified generic masked-context creation hub (`000d:463a`) plus two concrete sequencer-internal consumer blocks (`000d:208b`, `000d:21ed`) built directly on `entity_vm_context_create_from_slot_index`.
|
||||
- The USECODE/VM lane now also has first caller-role evidence outside the older seg021 wrapper island: the new seg004 callers keep masks `0x8000:0x0007` and `0x2000:0x0015` in gameplay-side materialization lanes, while the newly named seg006 helpers now separate one extra-word masked lane with a real local class-state transition fallback (`0x0008:0x0030`) from a guarded `0x0010:0x0008` materializer that simply returns `0` on miss after readiness checks.
|
||||
- The USECODE/VM lane now also has a wider verified higher-slot wrapper ladder: the `0005` island reaches slot ordinals `0x10..0x14`, slot `0x12` is a zero-extra-word lane, slots `0x11/0x13/0x14` carry extra-word payloads, and the current safest read is `slot-stable payload-shape taxonomy` rather than direct event-name promotion.
|
||||
- The same higher-slot batch now has its first outward binary anchors: slot `0x12` wrapper `0005:3171` is directly called at `0005:1776` and `0005:1945`, the slot `0x10` guarded lane at `0005:3115..3129` is still fenced by the `0005:30f2..3113` class-nibble-`4` check, and the dark slot `0x0a` / `0x0b` wrappers are now instruction-verified as exact signed-additive shims over masks `0x00000400` / `0x00000800` even though their outward callers remain unrecovered.
|
||||
- The compiled-side immortality lane is slightly tighter too: `000b:b3b1` / `000b:b62c` are now a cheat-event listener constructor/handler pair for the shared cheat/control bundle rather than a hidden `0x410` producer, and the extractor-side `TELEPAD` slot-`0x20` `raw_code_offset = 0x00000410` hit is closed as an offset collision rather than direct immortality evidence.
|
||||
- The compiled-side immortality lane is tighter again after the follow-up pass: `000c:8a62 -> 000c:8c56` is now a verified generic event-object dispatcher reading the emitted event id from field `+0x6`, seg109 helper `000b:3d2a` is now comment-backed as generic listener-registration infrastructure rather than an emitter, and the strongest remaining player-trigger family is the event-bearing `NPCTRIG` / `EVENT` neighborhood rather than `TRIGPAD`, `SPECIAL`, `REB_PAD`, or `TELEPAD`.
|
||||
- The immortality lane is tighter again after the extractor extension: generated report `USECODE/EUSECODE_extracted/immortality_target_body_scan.md` now proves that `EVENT`, `NPCTRIG`, `COR_BOOT`, `REE_BOOT`, `SFXTRIG`, `SPECIAL`, and `TRIGPAD` bodies contain no inline little-endian `0x0410`, no dword `0x00000410`, and no byte-swapped `0x1004`; the best surviving frontier is now the monolithic `EVENT` slot `0x0a` body plus compact `NPCTRIG` slots `0x0a` / `0x20`.
|
||||
- The immortality lane is tighter again after the structure pass: new report `USECODE/EUSECODE_extracted/immortality_body_structure.md` now shows `EVENT` slot `0x0a` as a broad hub clause stream (`90` internal `0x53 0x5c <u16> EVENT` subheaders, `383` local labels, wide `event/item/source/dest/door/counter/counter2/link/time/post1/post2/floor/flicMan` tail), while `NPCTRIG` stays compact (`5` subheaders for slot `0x0a`, `1` for slot `0x20`, with narrow `referent/event/item/item2` vs `referent/typeNpc/item/item2` tails). Current best surviving emitter frontier is therefore `NPCTRIG` slot `0x0a` with `NPCTRIG` slot `0x20` as its nearest typed/setup companion, while `EVENT` now reads more like the generic hub body behind the same active-event lane.
|
||||
- The immortality lane is tighter again after the clause pass: new report `USECODE/EUSECODE_extracted/immortality_npctrig_clauses.md` now fixes the open-header decode (`NPCTRIG 0x0a` event-code byte `0x11`, `NPCTRIG 0x20` event-code byte `0x01`) and shows slot `0x0a` as a five-step fixed-width clause ladder (`0x2f` subheader stride, backward-walking `0x2f` targets, per-clause `branch_3f_0a` + `push_24_51` + `writeback_57_02` motifs) while slot `0x20` stays typeNpc-heavy (`10` `field_4b_fe_0f` hits, no `push_24_51`, no `writeback_57_02`). The best remaining descriptor-side frontier is therefore no longer the `NPCTRIG` pair symmetrically; it is specifically `NPCTRIG` slot `0x0a` as the live event-bearing ladder, with slot `0x20` as a typed/setup companion body.
|
||||
- The immortality lane is tighter again after the runtime-fit follow-up: the regenerated clause report now records the per-clause motif offsets and the selector-family fit against `000d:21ed -> 000d:22bc`. `000d:5572` proves the extra word carried by `0005:2c35` is additive (`slot_value + offset`), `000d:21ed` now has an exact `A x B` matrix contract (byte A = lead-word row count, byte B = shared target-list width), and `NPCTRIG` slot `0x0a` is the only surviving compact body that exposes a natural five-row additive selector family (`0x0064/0x0093/0x00c2/0x00f1/0x0120`, uniform stride `0x2f`) instead of a one-clause typeNpc gate.
|
||||
- The immortality caller-path follow-up tightened the runtime bridge again: MCP xrefs now show only three entries into `entity_vm_context_create_from_slot_index` (`000d:46ac`, `000d:208b`, `000d:21ed`), while `0005:2c35` itself still has no recovered code or data xrefs. Stack setup at `000d:208b` hardcodes the `000d:5572` additive word to `0`, which does not match the `NPCTRIG` slot `0x0a` clause-start or target families. The remaining live selector frontier is therefore the still-overlapped `000d:21ed` caller frame rather than a normal caller of `0005:2c35`.
|
||||
- The immortality downstream-use follow-up weakens the remaining direct-selector hypothesis again: `000d:46ec` stores the dynamic word from the `000d:21ed` lane into context field `+0x34`, but `000d:21ed -> 000d:22bc` never rereads `+0x34` or `+0x32` after creation. The durable uses are the object save/load path instead: `000d:498f` serializes only the derived low word at `+0x10c`, `000d:4a78` reloads that saved word as the additive argument to `000d:5572`, and `000d:4c2d..4c4d` rebuilds `+0x10c/+0x10e` from the live slot value plus that saved offset. The only recovered post-load consumers are a tiny sentinel predicate (`FUN_0001_a772` checks for exactly `0000:0001`) and a normalization block (`FUN_0002_1860` clamps `0000:xxxx` values below `0x0080` up to `0x0080`). No recovered compare or dispatch branch matches the `NPCTRIG` slot `0x0a` clause-start or target families, so the direct derived-value fit is weaker again.
|
||||
- The persisted-context contract is tighter again after the latest pass: `entity_vm_context_save` (`000d:498f`) serializes `+0x11f`, `+0x121`, `+0x10c`, `+0x34`, and the `0x80`-byte local buffer, while `entity_vm_context_load` (`000d:4a78`) rebuilds the frame pointers, replays `entity_vm_slot_load_value_plus_offset` from saved `(slot, additive_word)`, restores `+0x10c/+0x10e`, and refreshes owner-source pair `+0x117/+0x119`. That is stronger evidence for `post-selector persistence of derived value state` than for any hidden upstream class discriminator.
|
||||
- The immortality upstream-source follow-up removes most of the caller-frame ambiguity. Direct program-memory bytes for `000d:2131..21ed` now show the hidden pre-call layout explicitly: the seeded `+0xd6/+0xd8` stream is consumed as `word slot_index`, `word add_a`, `word add_b`, `byte setup_len`, `byte inline_len`, and `000d:21d0` pushes `add_a + add_b` as the dynamic word later stored at context `+0x34`. The same window now proves the caller-side frame shape too: frame base is `caller + [caller+0xd4]`, `[frame+0x0a/+0x0c]` is the far pointer passed into `entity_vm_context_setup`, and `[frame+0x0e..]` is a separate inline tail blob copied after creation. That rules out runtime owner-table fields or raw caller-object fields as the immediate source of `+0x34` and reframes the open question one level earlier: where that frame-local far pointer is seeded from, and whether the summed stream pair still maps to `NPCTRIG` slot `0x0a` clause-base/delta structure or only to a more generic descriptor-relative offset pair.
|
||||
- The immortality frame-producer follow-up narrows the upstream writer one step further. Raw bytes at `000c:fbf7..fc47` (`caseD_0`) now show the nearest non-overlapped producer reading one signed placement byte from the seeded `+0xd6/+0xd8` stream, popping a far-pointer dword from the caller stream at `[caller+0xcc/+0xce]`, computing `frame_base = caller + [caller+0xd4]`, and storing that dword at `[frame_base + placement + 0x4/+0x6]`. That means the `000d:21ed` source lane is immediately caller-stream-backed rather than owner-row-backed; if its consumed `[frame+0x0a/+0x0c]` pair comes from this family, the relevant placement byte is `0x0006`, and any surviving `NPCTRIG` linkage must already have been predecoded into the generic caller stream before the frame record is materialized.
|
||||
- The next producer-path pass tightens that split again. `000d:46ec -> 000c:f844 -> 000c:f6e8` now shows that a new context's `+0xcc/+0xce` stream is seeded by copying a caller-supplied setup blob into the object-local buffer, while the slot/additive record from `entity_vm_slot_load_value_plus_offset` seeds the separate `+0xd6/+0xd8` lane and the owner-table row `(+0x10/+0x12) + 0x0d*slot + 4` is mirrored separately through `0x39ca`. Linear raw-byte recovery across `000c:f98b..000d:000d` also closes the forward/reverse frame-record family around that lane: `000c:fc4b..fcbb` is the caller-stream -> frame blob producer that best matches inline-tail placement `0x000a`, while `000c:ff1f..ff83` is the frame -> caller-stream dword copier matching the `000c:fbf7..fc47` far-pointer writer at placement `0x0006`. The surviving open question is therefore narrower again: not which generic parent-frame materializer exists, but where the first non-recursive decoder originates the setup far pointer before this `ff1f/ff9f -> fbf7/fc4b -> 000d:21ed` propagation chain repeats it, and whether that origin still maps specifically to `NPCTRIG` slot `0x0a` or to a broader predecoded VM workspace.
|
||||
- The next immortality pass closes the immediate far-pointer source classification too. Hidden raw bytes at `000c:fa2f..fa5b` recover an inner opcode dispatcher on the seeded `+0xd6/+0xd8` lane, and the same case family now exposes non-recursive caller-stream seeders at `000c:fd51`, `000c:fd91`, `000c:fdd1`, and `000c:fe11`. The dword case at `000c:fe11..fe59` reads an inline dword literal from that control stream, subtracts `4` from `[caller+0xcc]`, and writes the literal dword onto the caller stream before the recursive `ff1f/fbf7` replay family touches it. That means the immediate compiled-side source for the `000d:21ed` setup far pointer is now an inline VM control-stream literal, not an owner-row lookup or generic scratch buffer; any surviving `NPCTRIG` tie has to explain how slot `0x0a` is decoded into that literal-bearing stream upstream, while slot `0x20` still reads as the typed/setup companion body.
|
||||
- The next immortality pass separates that literal-bearing stream from the owner-row path cleanly enough to retune the working model. Instruction recovery at `000d:46ec` now shows the owner-table row `(+0x10/+0x12) + 0x0d*slot + 4` feeding only the separate `0x39ca[slot]` mirror, while the live `+0xd6/+0xd8` control stream passed into `entity_vm_context_setup` continues to come from `entity_vm_slot_load_value_plus_offset`. The hidden `000d:21ed` pre-call span is now explicit as `word slot_index`, `word add_a`, `word add_b`, `byte setup_len`, `byte inline_len`, and the `000c:fa2f` case family now separates immediate literal seeders (`000c:fd51` byte, `000c:fd91` sign-extended byte->word, `000c:fdd1` word, `000c:fe11` dword) from the recursive replay stages (`000c:ff1f`, `000c:ff9f`). Current best read is therefore `decoded per-slot VM workspace plus frame replay`, not `direct NPCTRIG clause stream`, even though `NPCTRIG` slot `0x0a` remains the strongest surviving upstream descriptor family and slot `0x20` still reads as the typed/setup companion.
|
||||
- The next immortality pass closes the workspace-materialization side of that boundary too. `entity_vm_slot_load_value` (`000d:51fd`) is now instruction-verified as the first concrete writer of the later `+0xd6/+0xd8` buffer on a cache miss: `000d:5066` loads a slot header plus cached `6`-byte subentry table through the owner-resource wrapper `000d:714c`, and `000d:5305..53d4` then reads the selected subentry's byte range directly into a newly allocated value-object buffer at `+0x0a/+0x0c`, which `000d:51fd` returns as the live far pair. That means the immediate workspace is file-backed owner-loaded slot data copied into memory before `000c:fa2f` interprets it. The remaining open question is no longer who first materializes the buffer at all, but whether the loaded slot family can be tied specifically to `NPCTRIG` slot `0x0a` or only to the broader owner-loaded descriptor workspace, with slot `0x20` still the best typed/setup companion.
|
||||
- The next immortality pass closes the header/range-arithmetic blocker itself. The owner-resource callbacks operate on `class_id + 2`, which matches extracted `object_index` exactly; the first class-header dword is now constrained as the extra-slot count beyond a fixed `0x20` base table; bytes `8..11` remain the first code-byte offset; and `000d:53b4` reads body windows using the same `(word len, dword raw_code_offset, code_base)` arithmetic emitted by the extractor. `NPCTRIG` therefore now has exact owner-loaded body windows in the live runtime format: slot `0x0a` = `0x00da..0x024e` (`373` bytes) and slot `0x20` = `0x024f..0x03a7` (`345` bytes), while `EVENT` slot `0x0a` likewise fits `0x00d4..0x20a9`. The remaining immortality uncertainty is no longer range translation but upstream class selection into that now-verified loader path.
|
||||
- The selector-side follow-up tightens that last uncertainty without closing it. `entity_vm_slot_index_from_entity` (`000d:45c5`) is now instruction-verified as a three-way category mapper only: `(1)` entity-id lane `1..255` with class bit `0x0002` clear -> `entity_id + 0x8c7e`, `(2)` class-nibble `4` lane -> `class_byte_0x7e05 + 0x8c80`, `(3)` fallback type lane -> `type_word_0x7df9 + 0x8c7c`. `entity_vm_runtime_init_from_path_if_configured` seeds those bases cumulatively from `0x6608..0x660e`, and direct caller `0005:295f` independently reuses the same slot index to test owner-row bit `0x0040`. That strengthens the read that the compiled side sees category spans plus generic row-capability masks, not a hard `NPCTRIG` / `EVENT` class-family discriminator, before the owner-loaded slot body is decoded.
|
||||
- The first focused NE `CRUSADER.EXE` hole-filling pass tightens that same wall one step further without breaking it. In the live NE session, `0005:295f` is now confirmed as the only recovered non-hub consumer of `entity_vm_slot_index_from_entity`, and its only currently recovered callers are `0006:43c3`, `0006:c5f0`, and `0007:3584`. That gives the selector lane three concrete gameplay-side caller families to classify next, while `0005:2c35` remains outward-xref-dark and therefore still does not prove a class-family choice by itself.
|
||||
- The next focused NE pass closes the first of those caller families structurally. Repaired wrapper `0006:4379` is now a verified seg031 dispatch-entry subtype gate over objects created by `0006:42d9` with event type `0x236`, source type `8`, subtype/tag at `+0x3c`, payload/source far pointer at `+0x32`, and aux words at `+0x36/+0x38`. Within that family, subtype `0x20c` at `0006:43c3` routes into `0005:295f`, while sibling subtype `0x20b` at `0006:43e5` routes into `0005:2918` using the same aux pair. That localizes the owner-row bit-`0x0040` consumer to one subtype-tagged dispatch-entry family, but still does not identify the upstream owner-loaded class family.
|
||||
- The first doc-to-live-NE integration batch is now applied in the open `CRUSADER.EXE` database too. Comment-backed anchors landed on the live selector/core pair `1420:0dc5` / `1420:0e3a`, the consumer pair `10a0:2718` / `10a0:275f`, and the first closed caller-family runner `10f0:02d9` / `10f0:0379`, with branch comments at `10f0:03c3` and `10f0:03e5` preserving the verified `0x20c -> 10a0:275f` and `0x20b -> 10a0:2718` split. This improves the live NE handoff without justifying a headline progress-estimate change yet.
|
||||
- The next live-NE caller-family pass closes the remaining direct `0005:295f` callers too. Old `0006:c5f0` now lands at `1128:0ff0` inside `Item_ReceiveHit`, where the non-NPC damage path probes `Item_GetDamaged` with hitter sentinel `0x4000`, packed `(damagetype << 8) | damage_lo`, and a local flag-out byte; old `0007:3584` now lands at `1138:1384` inside `SuperSprite_HitAndFinish`, where the non-NPC collision lane probes the same helper with packed `(firetype << 8) | damage` before optionally falling through to local `Item_ReceiveHit` knockback logic. Live comments now anchor both sites, so the selector frontier has moved upstream again to an earlier subtype/class-family producer rather than another direct caller search.
|
||||
- The compiled cheat/control lane is now split more cleanly. `cheat_code_check` (`0007:0d0a`) is still the sole hidden cheat-sequence matcher (5-byte table via `DS:0x2833`, index `DS:0x283d`), and it toggles `DS:0x844` (`cheats_enabled`) plus mirror `DS:0x6045`, then emits event `0x103`. The matcher bytes themselves are now rechecked in the live NE image as scan codes `24 1e 1f 1f 17 2e 1e 02 07` = `j a s s i c a 1 6`, with the trailing digits specifically using top-row scan codes `0x02` / `0x07`. Live data-use recovery also tightens the latch story: `0x6045` is written only by `Key_CheckCheatToggle` (`1130:2b72`) and the event-`0x7e` runtime toggle at `13e8:203d`. The live NE F10 proof is stronger than the earlier folklore-level read: inside `Key_HandleOptionKeys` (`1130:0896`), the F10 cheat branch first checks `DAT_1478_085f`, then `0x6045`, then reaches `1130:0afd` and calls helper `11c8:01a8`; the `11c8:018a` helper call in the same function appears later at `1130:0cad`, in a different branch. The helper identity is now closed from the code too: `KeyboardGetExtendedShiftStates` (`11d0:39e6`) uses BIOS `INT 16h, AH=12h`, whose AH bits are `0=left Ctrl`, `1=left Alt`, `2=right Ctrl`, `3=right Alt`, so `11c8:01a8` testing `0x0100|0x0400` is really `KeyEvent_IsCtrlDown`, and `11c8:018a` testing `0x0200|0x0800` is really `KeyEvent_IsAltDown`. Upstream keyboard-path recovery also closes the practical behavior too: the held-key repeat builder at `11b8:0129..022b` samples BIOS extended-shift state through `11d0:39e6`, stores the current `31a4` modifier snapshot into each repeated `KeyEvent`, and queues that event through `11d0:3533`, so holding `F10` first and then pressing physical `Ctrl` lets later repeated F10 events reach the immortality branch with refreshed modifier bits. The same repeated F10 event synthesis plus missing debounce explains the multi-modal on/off spam. The F10 immortality sub-branch also only runs for a live current NPC (`NPC_IsDead` gate at `10e8:1fed`). `DAT_1478_085f` is now tighter too: it is set during `Game_Start` (`1020:0127`), cleared at the end of `ComputerGump_CreateGump` (`1398:01f5`), and restored by `ComputerGump_CloseAndResumeGameplay` (`1398:0212`) during the paired computer-gump teardown path before falling into generic gump cleanup. Current safest read is a broader gameplay-input / option-key-active state rather than any cheat-state bit. Separately, event `0x410` at `000c:9703` does **not** toggle immortality; it boolean-toggles `DS:0x604f` / `g_cdTransferDisplayActive` and posts the `CD TRANSFER DISPLAY ACTIVE/INACTIVE` notifications under the broader `0x844` gate, which matches both the user's runtime observation and the old `crusader-disasm` note `CTRL-Q = 0x410`. The older `DS:0x6050` lane at `immortality_activate` (`000c:8231`) remains a separate secondary entity/process path. The older seg109 hidden-menu label is now narrowed further: in the live NE database, `000b:9a86`, `000b:9c0d`, `000b:b3b1`, `000b:b62c`, `000b:15ac`, `000b:0b52`, `000b:0b06`, and `000b:2882` now read more defensibly as `usecode_debugger_*` helpers, with menu labels like `Open Unit`, `View File`, `Watch`, `Inspect`, `Find`, and `Break to TDP`. Current best read is a hidden usecode debugger / unit inspector, not a retail scrollable cheat list. This also tightens the `-laurie` split: `-laurie` enables `0x844`-gated event cheats and debugger-side paths, but not the low-level `0x6045` keyboard latch, which matches the observed `F`-overlay-on / `F10`-refill-off behavior. Renamed in this area: `FUN_000c_8231` -> `immortality_activate`, `FUN_000c_834a` -> `immortality_conditional_activate`, `FUN_000c_8486` -> `immortality_activate_and_reset`, `FUN_000c_743f` -> `immortality_entity_process_create`, `FUN_000b_9a86` -> `usecode_debugger_open_for_current_unit`, `FUN_000b_9c0d` -> `usecode_debugger_open_modal`, `FUN_000b_b3b1` -> `usecode_debugger_gump_create`, `FUN_000b_b62c` -> `usecode_debugger_handle_event`, `FUN_000b_15ac` -> `usecode_debugger_load_unit_file`, `FUN_000b_0b52` -> `usecode_debugger_center_on_line`, `FUN_000b_0b06` -> `usecode_debugger_set_line_selection`, `FUN_000b_2882` -> `usecode_debugger_build_menubar`, `FUN_1398_0212` -> `ComputerGump_CloseAndResumeGameplay`.
|
||||
- The same cheat/control lane is now a little cleaner at the user-facing hotkey level too. A focused live NE pass closed three folklore items: `~` is a real runtime cheat-latch toggle at `13e8:203d` under the broader `0x844` gate; the online `Ctrl+C = show current location` claim is wrong for this build and is really `Ctrl+L`, whose popup branch formats `1478:610c` at `13e8:255e`; and the missing third overlay is not bogus after all, because a separate `Ctrl+F7` branch at `13e8:1a20` toggles `1478:0ee0` while the other two F7-family toggles write `1478:2bc9` and `1478:2bca`.
|
||||
- The follow-up pass closes the `~` versus `jassica16` confusion more tightly too. `jassica16` is the earlier raw scan-code matcher that toggles both `1478:0844` and `1478:6045`, sets `1478:8c52`, and can therefore bootstrap the whole cheat state from cold; `~` is only the later translated logical-`0x7e` branch, so Shift is the expected normal gesture on a US layout and that hotkey can only flip `1478:6045` after `1478:0844` is already enabled. The same pass also improves the third-overlay classification: `Ctrl+F7` is not a third generic camera grid but an `EggHatcherProcess` trigger-range overlay, which can legitimately appear blank on maps without eligible live egg/hatcher processes.
|
||||
- The next cheat/overlay refinement pass is now folded back into the docs too. `docs/ne-segment1.md` now has a consolidated live-NE cheat/debug key matrix, the practical `-laurie` plus `Shift+~` bootstrap recipe for full keyboard cheats, and a fuller egg-hatcher note grounded in `EggHatcher_CreateProcess` / `EggHatcherProcess_Run`: non-monster egg families are enter/leave trigger items with X/Y/Z range checks, while `Ctrl+F7` visualizes the live egg-hatcher ranges and `Alt+F7` visualizes the related snap-process egg list.
|
||||
- The `0x85f` reader side is now clearer too. The live NE database now names the paired `13e8` transition wrappers as `Game_DisableGameplayInputAndRefreshCamera` (`13e8:0e7d`) and `Game_RestoreGameplayInputAndClearModalState` (`13e8:0ef9`), which matches their concrete behavior: `13e8:0e7d` clears the controller/key-input latch `1478:27cb`, raises the modal overlay-suppression state at `1478:2c64` / `1478:8c53`, preserves `1478:8c54` from `1478:2d24`, and refreshes camera state; `13e8:0ef9` performs the inverse restore path and clears the secondary `1478:6050` latch. The Laurie-only wrapper side is clearer as well: `Game_ShowLaurieHintComputerGump` (`13e8:0e31`) is the hidden `-laurie` computer-gump hint path, while `Game_ShowLaurieHintIfGameplayInputActive` (`13e8:0f4a`) only calls it when `0x85f` is high. The main camera pass consuming the same gate is now `Camera_RedrawViewportAndGameplayOverlays` (`1180:19c1`), with comment-backed `1188:010f` / `1188:0394` overlay helpers bracketing the viewport redraw.
|
||||
- The next blocker layer is narrower too. Those modal wrappers are not abstract helpers; inside `World_HandleKeyboardInput_13e8_14b4` they already wrap concrete user-facing lanes including exit-to-DOS confirmation (`0x22d`), quick save (`0x13f`), quick load (`0x13e`), restart/main-menu handling (`Game_RestartMaybe`), and the neighboring load/menu gump lanes. Separately, event `0x7e` remains the only other recovered writer of `0x6045` besides `Key_CheckCheatToggle`, so a successful `jassica16` match can still be undone later by that independent runtime path. `Key_CheckCheatToggle` itself is now comment-backed as keydown-only and still requires top-row `1` / `6` scan codes at the tail, leaving keypad digits and other non-matching input routes as a still-live explanation for failed tests.
|
||||
- Cross-game verification against the currently opened `REGRET.EXE` now has a runtime correction too. The F10 branch at `1148:0d0e` still reaches the same modifier helper at `11e0:01a8`, and live testing shows the practical gesture is hold `F10` first and then press `Ctrl`, not `Alt`. The same BIOS-backed helper swap should be verified directly in that target before promoting renames there. The same runtime test also explains the repeated immortality popups: the F10 branch is not debounced, so holding the keys lets repeated F10 keydown events flip immortality on and off multiple times. The real gameplay difference remains the latch code: `1148:34d2` (`Key_CheckSecretCodeSequences`) still contains a `jassica16` table at `1480:2ff0`, but the latch-enabling sequence in No Regret is the second table at `1480:2ffc`, decoded as `loosecannon`, which toggles `1480:0ac0` and mirrors the result into the F10 latch byte `1480:009b`.
|
||||
- Retail hidden-menu patching remains open, but the failed branches are now better separated from the current writable candidate. Verified file/fixup anchors are `0007:0d75` / `0007:0d79` (file `0x70d75` / relocation entry `0x71d68`) and `000c:99dd` / `000c:99e0` (file `0xc99dd`, seg126 chain `0x25e0`). The deferred `0x42f -> 000c:99dd -> 000b:9c0d` design remains explicitly rejected: it visibly entered the hidden UI path, but it halted with the retail `FILE\FLEX.C, line 83` failure and dropped into the quit line, so `0x42f` is the wrong deferred context even though the modal wrapper address itself was valid. The newer direct `0007:0d79 -> 000b:9a86` current-slot retarget with the narrowed `000b:9a8d` arg patch was also runtime-tested and produced no hidden menu, so the writable `/Writable/CRUSADER-PATCHED.EXE` test build is now moved to the next defensible variant instead: restore the direct hook to `000a:5276`, keep the current-slot wrapper unpatched, and retarget the later controller-side `000c:99e0` call to `000b:9c0d` while zeroing only the inherited modal-wrapper words at `000b:9c4a`.
|
||||
- The next retail test build is narrower still. User runtime feedback on the first `Ctrl+Q` patch is: the mouse pointer appears, then gameplay hangs with only a single right-edge pixel still updating. That makes the remaining failure look more like post-entry control-flow fallout than a bad entry address. The PowerShell patch therefore now also rewrites raw `000c:99e8` / live `13e8:25e8` from `PUSH 0x3e8` to a near jump into the shared epilogue at `13e8:29a7`, so the reused `13e8:25dd` deferred lane exits immediately after the retargeted `13e8:25e0 -> 13a0:020d` call instead of falling through into the original `0x42f` branch tail logic.
|
||||
- DOSBox-X debugger capture now shows the same hang surviving that tail-skip patch, and the stop point is materially informative: the live runtime state matches seg131 `Interpreter_NextUsecodeOp` at `1418:04c3..051d`, specifically just before the `1408:02f5` call at `1418:051d`. That means the blunt `Ctrl+Q -> 13a0:020d` patch is not merely stuck in the seg109 modal wrapper; it has activated the interpreter-side debugger-state path guarded by non-null `0x659c/0x659e`, and the freeze now looks like a bad or incomplete seg1408 debugger-state lifecycle rather than a simple wrong branch tail. Current best implication: stop iterating on the blunt modal force-open patch and pivot the next patch design toward constructing or safely emulating a real `usecode_debugger_break_state_create` object at `1408:0000` before relying on the seg109 UI lane.
|
||||
- The next executable patch still follows that pivot, but the boot-time callback rewrite is now explicitly retired. The current PowerShell build repurposes the gated `0x410` body at `13e8:230d` to lazily construct a seg1408 state object through the existing far-call slot at `13e8:2352 -> 1408:0000`, stores the returned far pointer into `0x659c/0x659e`, and then reuses the **second** existing far-call slot in that same body (`13e8:235c`) to jump directly to `usecode_debugger_open_for_current_unit` at `13a0:0086` with zeroed wrapper arguments. This keeps the patch hotkey-local instead of rewriting the shared seg1408 callback table at `1478:65ab`, while the older direct and deferred modal-force-open sites remain restored.
|
||||
- The callback-table design is now negative evidence rather than the live candidate. Even after fixing an NE-segment indexing mistake (`1478:65ab` had first been retargeted to segment `109` instead of `117` for `13a0:0086`), the global callback rewrite still caused startup failure. The surviving script fixes from that pass remain important: the large `13e8:230d` body must use on-disk `FF FF 00 00` placeholders rather than disassembly-resolved far operands, and its patched byte array must include the final trailing `0xC7` so patch/restore verification matches the retail executable length. With the global callback rewrite removed and the second local call slot retargeted instead, the script now round-trips cleanly on a fresh copied retail EXE (`apply -> patched`, `restore -> original`) and also cleans up the stale old `1478:65ab` callback retarget if that earlier crashing build had already been applied.
|
||||
- The direct hotkey-to-wrapper retarget is now negative evidence too. The local-call redesign fixed startup and let the game reach gameplay, but pressing `Ctrl+Q` immediately quit through the normal `"No pity. No mercy. No remorse."` shutdown line, which is more consistent with entering the modal UI while the original keypress is still live than with a boot-time relocation problem. The next patch therefore keeps the hotkey-local object creation but stops calling `13a0:0086` on the keypress itself.
|
||||
- The live candidate is now a per-object callback redirect. The `0x410` body at `13e8:230d` still creates/stores the seg1408 debugger-state object at `0x659c/0x659e`, but the second existing far-call slot in that body (`13e8:235c`) is now retargeted to `1408:0419` (`usecode_debugger_enable_single_step`) instead of directly opening the UI. The created object's first word is rewritten from the shared callback-table offset `0x65ab` to the private relocated slot `0x65af`, and the private dword at `1478:65af` is retargeted from `1408:0474` to `13a0:0086`. That should let the *next* interpreter-side debugger callback open the current-unit UI without inheriting the live `Ctrl+Q` key event, while the original shared `1478:65ab` slot stays restored to the retail no-op.
|
||||
- The PowerShell patcher now round-trips cleanly for this per-object callback design on a fresh copied retail EXE too: `13e8:230d` body patched/restored, `13e8:235c` step-arm call patched/restored, private callback slot `1478:65af` patched/restored, and legacy shared callback slot `1478:65ab` held at original in both states.
|
||||
- User runtime on that per-object single-step variant is now also informative negative evidence: the game boots and reaches gameplay, but pressing `Ctrl+Q` produces no visible effect at all, not even the original CD-transfer toast, which implies the hotkey body is being intercepted but the deferred break still is not surfacing. Current best read is that the single-step path at `+0x75` remains gated by the seg1418 nesting counters `+0x76/+0x78` often enough that the callback never fires in the observed test path.
|
||||
- The live patch candidate therefore now sets **break-next** mode directly instead of single-step mode. The repurposed `13e8:230d` body still constructs/stores the seg1408 debugger-state object and repoints that object to the private callback slot `1478:65af -> 13a0:0086`, but it now writes `+0x75 = 0` and `+0x74 = 1` in the object itself rather than retargeting the second `13e8:235c` call slot to `1408:0419`. That matches the surviving UI-side control path at `13a0:1e5d`, where `+0x74` is the unconditional break-on-next-callback mode while `+0x75` is the nesting-sensitive single-step mode.
|
||||
- The PowerShell patcher also round-trips cleanly for this break-next design on a fresh copied retail EXE: `13e8:230d` body patched/restored, private callback slot `1478:65af` patched/restored, shared callback slot `1478:65ab` held at original, and the stale second-call-slot cleanup removed from the write path because those bytes now belong to the patched body itself.
|
||||
- User runtime on that `1478:65af` break-next variant is now negative evidence as well: the game crashes on launch again, so even the supposedly private `65af` slot now looks too globally visible to repurpose. Current best implication is that the object-local `0x65af` first-word rewrite can stay as the arm point, but the actual callback entry must move off the live callback-table dword itself.
|
||||
- The live candidate is therefore now a **guarded trampoline** at the original seg1408 no-op callback code. The PowerShell patcher keeps the `13e8:230d` break-next object creation/store path, but restores the shared `1478:65ab` slot, stops repointing `1478:65af` to `13a0:0086`, patches `1408:0474` into a tiny guard that returns immediately unless `0x659c/0x659e` is armed, and uses the apparently unused relocated dword at `1478:6597` as the far target slot for `13a0:0086`. This newer `6597`/`1408:0474` build now also round-trips cleanly on a fresh copied retail EXE: `13e8:230d` body patched/restored, guarded callback code at file `0xCEE6F` patched/restored, wrapper target slot at file `0xEA197` patched/restored, and all older direct/deferred experiment sites held at original bytes.
|
||||
- The root-cause read on the `65af` startup failures is now sharper: `0x65af` is not an alternate vtable base at all. The constructor at `1408:0000` writes `0x65ab` to object offset `+0`, and the dispatch sites prove that object `CALLF [BX]` uses the dword at `65ab -> 1408:046f` while object `CALLF [BX+4]` uses the next dword at `65af -> 1408:0474`. Rewriting the object first word to `0x65af` therefore corrupts the second method lookup (`[BX+4]`) instead of selecting a “private callback table”, which explains the launch-time instability and the other inconsistent runtime fallout from the earlier single-step and break-next builds.
|
||||
- The live candidate is now the narrower **method-0 deferred callback** design. The PowerShell patcher still keeps the `13e8:230d` lazy object creation/store path and still arms break-next mode by writing `+0x75 = 0` / `+0x74 = 1`, but it explicitly preserves the object's vtable base as `0x65ab`, restores the method-1 helper at `1408:0474`, patches only the method-0 break callback at `1408:046f` to indirect through the spare relocated dword `1478:6597`, and uses that slot as the far target for `13a0:0086`. This corrected `046f`/`6597` build also round-trips cleanly on a fresh copied retail EXE: `13e8:230d` body patched/restored, break callback code at file `0xCEE6F` patched/restored, deferred target slot at file `0xEA197` patched/restored, and all older direct/deferred experiment sites held at original bytes.
|
||||
- User runtime on that shared-`046f` method-0 build is now negative evidence too: startup still crashes, which makes the shared method body just as globally sensitive as the shared `65ab/65af` vtable slots. Current implication: the deferred path still looks right, but the callback implementation must move to a truly private per-object table instead of any shared seg1408 body or shared vtable dword.
|
||||
- The live candidate is now a **private two-entry vtable** built from unused relocated dwords with no current data uses. The PowerShell patcher still keeps the `13e8:230d` lazy object creation/store path and still arms break-next mode with `+0x75 = 0` / `+0x74 = 1`, but it now rewrites the created object's vtable base to `0x658f`, retargets private method 0 `1478:658f -> 13a0:0086`, retargets private method 1 `1478:6593 -> 1408:0474`, and leaves the shared callback bodies and shared `65ab/65af` table entries untouched. This private-vtable build also round-trips cleanly on a fresh copied retail EXE: `13e8:230d` body patched/restored, private method 0 slot at file `0xEA18F` patched/restored, private method 1 slot at file `0xEA193` patched/restored, and all older direct/deferred experiment sites held at original bytes.
|
||||
- User runtime on that first private-vtable placement is now negative evidence too: startup still crashes, which proves the `658f/6593` pair is also startup-visible despite the lack of direct data-use hits. Current best implication is that the private-vtable strategy itself still looks structurally right, but the specific dword pair must move farther away from the debugger-global cluster and any hidden boot-time consumers.
|
||||
- The full six-candidate private-vtable harness is now retired. User runtime results:
|
||||
- Candidate A (`1478:6724/6728`) = DOSBox closes on start
|
||||
- Candidate B (`1478:672c/6730`) = fatal `Load program failed -- error code 201 -- C:\CRUSADER.EXE`
|
||||
- Candidate C (`1478:6734/6738`) = DOSBox closes on start
|
||||
- Candidate D (`1478:6718/671c`) = startup crash
|
||||
- Candidate E (`1478:6720/6724`) = startup crash
|
||||
- Candidate F (`1478:6738/673c`) = startup crash
|
||||
Ghidra follow-up now explains why: `1478:6718..673c` is a live function-pointer table containing `UsecodeProcess_*`, `Process_Terminate`, `Process_Fail`, and nearby null handlers, not spare relocated dwords. The script no longer offers those candidates.
|
||||
- The first guarded shared-callback pair is now negative evidence too. Candidates G/H still crashed on startup, and the best new explanation is structural: that design overwrote both `1408:046f` and the adjacent `1408:0474`, but `0474` is a real helper that returns `DX:AX = 0`, not dead padding. Destroying that zero-return behavior may itself be enough to destabilize startup.
|
||||
- The method-0-only shared-callback pair is now negative evidence too. User runtime on Patch 1 / Patch 2 showed both startup-crashing, which means preserving `1408:0474` was necessary but not sufficient: the shared `1408:046f` body is still too broad if it jumps straight into debugger UI code.
|
||||
- The live patch family is now an **interpreter callsite retarget** design. Candidate M/N are retired negative evidence: both startup-crashed, the embedded private stub inside the patched `13e8:230d` body was malformed, and the supposed deferred target slot at `1478:6597` is no longer treated as spare storage. The current PowerShell build still keeps the retail debugger object's real `1478:65ab` vtable base and still arms break-next through the patched `13e8:230d` body, but it now avoids both the shared seg1408 callback bodies and the `1478:6597` data slot entirely. Instead, it patches the existing interpreter `CALLF usecode_debugger_maybe_break_on_current_line` at `1418:04b5` to a corrected private stub at `13e8:232d`, and it also reuses the second retail far-call slot inside `13e8:230d` (`13e8:235c`) as the actual private UI-call target. The `13e8:230d` body itself now correctly handles both cases: reuse and arm an existing debugger-state object at `0x659c/659e`, or lazily create/store one before arming break-next. One implementation bug from the first O/P refactor is now fixed too: the second `13e8:235c` relocation write is part of candidate application and verification, so the live build now really routes to the selected wrapper instead of accidentally leaving that slot on retail `Dispatch_ModalGump`. Current candidates:
|
||||
- Candidate O = interpreter callsite retarget -> `13a0:020d`, with `13a0:024a` zeroed inherited modal-wrapper words
|
||||
- Candidate P = interpreter callsite retarget -> `13a0:0086`, with `13a0:008f` zeroed inherited current-unit-wrapper words
|
||||
Both apply/restore cleanly on a disposable retail copy and are the next runtime tests.
|
||||
- Fresh live-Ghidra re-checks now tighten the lower bound on this patch family too. Retail still shows no recovered writer that seeds `1478:659c/659e`, the constructor at `1408:0000` still only returns the debugger object and seeds the inert shared callbacks `1478:65ab -> 1408:046f` / `1478:65af -> 1408:0474`, and the interpreter pre-call guard at `1418:049e..04b5` still only checks for a non-null debugger pointer before handing off to `1408:0053`. Current best implication: there is still no evidence-backed one-site direct jump that can safely load the hidden debugger. Candidate O/P are now the smallest structurally defensible executable patches because they are the first design that preserves all four required pieces together: object bootstrap, global pointer seeding, one-shot deferred entry, and sanitized wrapper arguments.
|
||||
- Full chronology for this patch line now lives in `docs/retail-debugger-patch-attempts.md`, including the failed global callback rewrite, direct wrapper call, single-step `65af` build, break-next `65af` build, guarded `0474` trampoline, shared `046f` method patch, and the current private-vtable candidate.
|
||||
- The hidden-menu orphan model is now materially stronger too. New live renames in seg1408 (`usecode_debugger_break_state_create`, `usecode_debugger_maybe_break_on_current_line`, `usecode_debugger_breakpoint_insert_sorted`, `usecode_debugger_has_breakpoint`, `usecode_debugger_callstack_push_entry`, `usecode_debugger_callstack_pop_entry`, `usecode_debugger_enable_single_step`, `usecode_debugger_clear_step_state`, `usecode_debugger_current_entry_get_unit_name`) line up with the seg109 UI in a way the cheat-only hook never did. The concrete interpreter-side handoff at `1418:04aa..04b5` now calls `usecode_debugger_maybe_break_on_current_line` whenever the far pointer at `0x659c/0x659e` is non-null, and that helper checks `(file,line)` breakpoints before callbacking through the debugger-state object's vtable. Current best read is therefore that the retail orphan happened one layer earlier than the cheat/event experiments: the seg109 current-unit debugger UI likely used to be entered from this seg1408 breakpoint object, but retail no longer appears to instantiate/store that object at `0x659c/0x659e`. That makes the breakpoint callback lane a stronger original-entry candidate than direct event `0x103` retargeting.
|
||||
- The follow-up doc reconciliation is now closed too. `docs/ne-segment1.md` no longer presents the seg109/raw-reference UI addresses (`000b:*`) and the live seg1408 breakpoint-state addresses (`1408:*`) as if they were competing versions of one table; it now uses one combined component map that makes the layering explicit and preserves the interpreter callback at `1418:04aa..04b5` as the bridge between them.
|
||||
- The live NE `CRUSADER.EXE` mapping for that hidden-menu lane is now explicit and comment-backed in Ghidra too: direct hook `1130:2b75/2b78`, current-slot wrapper `13a0:0086` with constructor arg site `13a0:008d`, modal wrapper `13a0:020d` with inherited-arg patch subsite `13a0:024a`, listener create/dispatch `13a0:19b1` / `13a0:1df3`, compiled `0x410` CD-transfer-display body `13e8:2303`, deferred controller-side hook `13e8:25dd/25e0`, and the supporting cheat-state data cells at `1020:2833`, `1020:283d`, `1020:0844`, `1020:6045`, `1020:604f`, and `1020:6050`. The `0x410` body is still documented in place rather than renamed because it remains embedded inside the oversized `World_HandleKeyboardInput_13e8_14b4` function object. This improved live handoff and patch reproducibility still does not justify a headline estimate change by itself.
|
||||
- The retail `-debug` switch is now separated cleanly from that hidden debugger lane too. Live `HandleCommandlineArgs` recovery in `CRUSADER.EXE` confirms a real `"-debug"` branch at `1048:0a93` that sets `g_debugMsgLevel = 10` (`1478:87e0`), prints `Debugging mode ON.`, and writes `1478:0845/0859` (`g_someDebugFlag` / `g_someDebugFlag2`). The `0x87e0` threshold is read by `ConsolePrintf` / `DebugPrintAndWaitForInput`, and `0x0859` is read by the segment `1468` `VideoPlayer_*` neighborhood (`1468:2869`, `1468:2af4`, helper `1468:2de9`). Current best read is `surviving debug-output / media-instrumentation switch`, not `dead parser stub`, and still not `the missing seg109/seg1408 usecode debugger bootstrap`, because the same pass found no evidence that `-debug` constructs or stores the real debugger-state pointer at `1478:659c/659e`. Focused write-up now lives in `docs/retail-debug-arg.md`.
|
||||
- That `-debug` lane is tighter again after the follow-up deep dive and live Ghidra refinements. The seg1468 readers are now renamed/comment-backed as `VideoPlayer_AdvanceFrameAndHandleSkip`, `VideoPlayer_StreamChunks`, and `VideoPlayer_DrawDebugTimingOverlay`, and the helper body is no longer only `instrumentation-looking`: it writes two `500`-byte marker traces into adjacent scanlines near the bottom of a `0x280`-wide AVI playback buffer, using timing deltas scaled by `6000`. Current best read is therefore `built-in movie-playback timing overlay` plus the separate `g_debugMsgLevel` console/positioned-print threshold, still explicitly distinct from the hidden seg109/seg1408 usecode debugger path at `1478:659c/659e`.
|
||||
- The same `-debug` lane is now bounded more comprehensively too. The user-confirmed moving bottom-of-video dots match the static two-scanline overlay model, so that AVI timing overlay is now the first closed visible effect. Outside the video lane, `-debug` also changes the global seg12d0 print threshold (`1478:87e0 = 10`) used by `ConsolePrintf`, `DebugPrintAndWaitForInput`, and the positioned print helpers, but the sink side is now tighter as well: `ProbablyPrintDebugMessage` formats through the static stdio-style table at `1478:6c32..6c81` and writes to the handle-`1` entry at `1478:6c46`, so the non-video side is ordinary DOS `stdout` gated by the threshold, not a separate hidden debugger console. Current xrefs still show that lane mostly as existing startup/config/cache/joystick/process diagnostics and a small set of dispatch/gump allocation failure-stop paths, not as a second confirmed hidden feature. The unresolved leftover is still `1478:0845`, which remains a parser-set latch with no recovered downstream consumer.
|
||||
- The print inventory behind that same `-debug` lane is now materially tighter too. A focused pass recovered concrete `ConsolePrintf` / `DebugPrintAndWaitForInput` strings instead of only caller families: startup/arg strings such as `Debugging mode ON.`, `You DO need help!`, `Enabling ENHANCED mode. (NOT!)`, `Warping to mission %d ...`, `Defaulting to skill level %d`, and `Demo mode.`; init/config strings such as `Using map patch file.`, `Running with partial installation.`, `Running with full installation.`, and `Redirecting mission %d tune to '%s'`; cache/swap progress scaffolding such as `Creating Swap file [` and repeated `.` / bracket fragments; plus stronger failure/debug-stop fingerprints like `COULD NOT CREATE GLOB ITEM!`, `No room for Dispatcher Record/Playback.`, `End of script! (press any key)`, and `Out of Memory! [%u]`. Recovered call levels so far are `0x32` and `0xff`, both above the `-debug` threshold of `10`, which reinforces that the practical scarcity of visible text is about path frequency and graphics-mode presentation, not about the threshold still filtering these known messages out.
|
||||
- The older folklore claim about flat offset `E69FB` and a possible secondary monochrome monitor is now materially weaker too. Local NE-segment mapping puts `0xE69FB` at live address `1478:2dfb`, which falls inside the `SYSTIMER.C` string in a data/name table (`KeyboardProcess`, `KEYIO.C`, `PRIORITY.C`, `SystemTimer`, `SYSTIMER.C`, `AccWait`), not inside executable instructions. The current retail print lane still points to ordinary `stdout` at `1478:6c46`, and targeted searches found no direct `mono`/`monochrome`/`hercules`/`MDA` strings or obvious monochrome-adapter port/memory references (`0x3b4/0x3b5/0x3b8/0x3ba`, `B000`). Current best read is therefore `folklore or address-mapping mistake`, not evidence for a hidden retail secondary-monitor debug display.
|
||||
- A focused localized-build comparison is now tighter too. The live `/es/CRUSADER.EXE` pass still shows the same broad cheat/debug framework with shifted addresses rather than a rewritten system: `-laurie` sets the broad master gate at `1478:0910`, the gameplay-input gate still exists at `1478:0927`, the lower keyboard-cheat latch still exists at `1478:5fb3`, event `0x410` at `13e8:2211` still toggles the CD transfer display, and `13e8:24a5` still toggles Hack Mover with English-facing strings. But the sequence side is narrower and more specific now: a direct live byte scan found no exact `jassica16` table `24 1e 1f 1f 17 2e 1e 02 07 00` anywhere in Spanish data `1478:0000-8c3f` or `1480:0000-1fff`, and the old English-side slot at `1478:2833` now holds pointer-like words instead of the matcher bytes. The same pass also surfaced a likely Spanish post-sequence latch analog: Hack Mover is pre-gated by `1478:8ad6` before the broad gate check. So the remaining open question is no longer "does Spanish have the same cheat/debug family"; it is "where did the Spanish secret matcher move, and what writes `1478:8ad6`?" Detailed notes now live in `docs/spanish-cheat-differences.md`.
|
||||
- The follow-up Spanish-cheat pass narrows that further: in the live `/es/CRUSADER.EXE` database, `1478:0910` still has only the `-laurie` write at `1050:0985`, `1478:5fb3` still has only the Laurie-hint helper writes at `13e8:0071/0077`, and `1478:8ad6` still has no recovered direct writer even though `13e8:249b` tests it before Hack Mover. The old English matcher slot at `1478:2833` remains repurposed as pointer-like words, and the only explicit multi-key helper recovered in this pass (`11d0:024b`) is just a generic key-list membership check used from a movement/control cluster, not a cheat-toggle lane. Current best read is therefore now stronger than "unknown moved Spanish matcher": no live replacement cheat-trigger byte matcher has been recovered at all, and the remaining proof frontier is any non-`-laurie` writer of `1478:0910` or a real writer for `1478:8ad6`.
|
||||
- The next localized-build pass narrows the practical keyboard side too. Full decompilation of `World_HandleKeyboardInput` (`13e8:14b4`) still shows the Spanish broad-gate debug family (`0x141`, `0x241`, `0x410`, Hack Mover), but no recovered translated `~` / `0x7e` cheat-latch branch. That means the old English `-laurie` plus tilde bootstrap is no longer a defensible Spanish assumption: `-laurie` still raises `1478:0910`, but no runtime tilde writer of `1478:5fb3` has been recovered, and `1478:5fb3` itself now reads more like a widely consulted gate byte than a proven English-style keyboard-cheat latch.
|
||||
- The deeper keyboard-handler pass strengthens that again: `1478:5fb3` is not just missing a recovered tilde writer, it no longer behaves like a positive enable latch at all. Every recovered consumer in `World_HandleKeyboardInput` requires `1478:5fb3 == 0`, and the only recovered writers are the Laurie-hint helper pulse `13e8:0071` then `13e8:0077`, which leaves the byte cleared. Current best localized-build answer to the user-facing cheat question is therefore: `-laurie` is the only recovered positive enabler for the surviving broad Spanish cheat/debug hotkeys, while Hack Mover remains separately blocked behind the still-unwritten `1478:8ad6` gate.
|
||||
- The next Spanish follow-up narrows two remaining folklore assumptions too. First, the old English immortality-string slots at `1478:2850/2866` are repurposed as pointer-like data in `/es/CRUSADER.EXE`, and no direct uses of those addresses were recovered, so the English `F10` replenish / immortality path is now `unproven in Spanish` rather than merely `not yet re-closed`. Second, the new `8ad6` hunt found a nearby runtime-state cluster at `8ad7/8ad8/8ad9`, but the actual neighbor writes belong to gameplay-input modal helpers and the Hack-Mover-adjacent runtime helper `13e8:282f`; they still do not touch `8ad6`, which keeps the best writer hypothesis pointed at an indirect or script-driven path rather than ordinary compiled keyboard logic.
|
||||
- seg001 gameplay/input/projectile work is stable enough to support verified raw-name ports into live NE work.
|
||||
- The raw `0007` rendering/camera/tile-visibility lane has a strong structural map and now acts more as supporting evidence than as a primary unknown.
|
||||
- The `0008` dispatch-helper and `000c` state/transition lanes have broad partial coverage, including enough caller-side structure to support practical NE naming work.
|
||||
- The VM/USECODE lane now also has one earlier compiled-side producer anchored beyond the old direct `Item_GetDamaged` / `StorageDataProcess_Run` callers: `AreaSearch_CollideMove` is now verified as a paired `0x20b` / `0x20c` collision-process producer, and the local seg031 queue helpers are named structurally in the live database.
|
||||
- That same collision-storage producer surface is now wider too: current direct callers are all movement/physics/animation-side (`Item_LegalMoveToPoint`, `Item_LegalMoveToPointWithCollisionInfo`, gravity, animation, supersprite, and fast-area gravity cleanup), and no verified non-collision producer reaches the `0x236` queue yet.
|
||||
- The movement/collision lane is tighter at the helper level too: the step-aware seg029 sweep wrappers, the seg031 release-side queue cleanup pair, and the adjacent seg090 directional cache-offset helper are now named in the live database, so the remaining uncertainty in this lane sits earlier in caller policy rather than in the local helper layer.
|
||||
- The startup/display lane is materially closed. Shared dispatch-entry ownership, seg126 file-backed control flow, seg127 fade control, and the surrounding palette/presentation helpers are now understood well enough that they should not stay in the live critical path.
|
||||
- The cheat/debug lane is mostly closed at the behavior level. The secret-sequence matcher, broader cheat gates, F7 overlays, F10 modifier path, `Ctrl+L` location popup, `Ctrl+Q = 0x410` CD-transfer-display toggle, `-debug`, and `-laurie` are all separated far more cleanly than before.
|
||||
- The hidden usecode-debugger lane is now structurally understood as a layered orphaned subsystem: seg109 UI pieces, seg1408 break-state helpers, and the seg1418 interpreter handoff are no longer conflated.
|
||||
- The USECODE/VM lane now has a workable compiled-side model around `entity_vm_runtime_create`, `entity_vm_runtime_owner_resource_create`, `entity_vm_context_create_from_slot_index`, the masked-create hub at `000d:463a`, the persistence/load helpers, and the owner-loaded slot/value arithmetic.
|
||||
- The owner-loaded body/range model is no longer speculative. Class-selection uses `class_id + 2`, header/subentry math matches extracted corpus output, and concrete body windows for `NPCTRIG`, `EVENT`, and related families are now verified.
|
||||
- The map-renderer/documentation lane now has a stronger shape/controller crosswalk. Recent closures include `CRUMORPH`, `NPC_ONLY`, `WATCHNS`, `WATCHEW`, `CRYOBOX`, `CRAZYEW`, `CRAZYNS`, `VIDEOBOX`, `PANELEW`, `GENERATR`, and cross-game `DEATHBOX`, with viewer-side links kept conservative where actor-side state is still runtime-only.
|
||||
- The command-line/startup lane is much tighter across both games: `-warp <mission> [x y z]`, `-mapoff`, `-egg`, startup teleporter selection, and the `-u` EUSECODE root override all now have practical behavior models instead of folklore-level descriptions.
|
||||
- The PSX lane is no longer just side inventory. Retail/pre-alpha bundle loading, mission-briefing/passcode structure, and the reduced-content pre-alpha disc now have dedicated notes and enough stable naming to support future targeted passes.
|
||||
- The Remorse class-lift preparation lane now has a usable document cluster: overall plan, candidate inventory, endpoint spec, ABI constraints, family notes for `EntityDispatchEntry` and `SpriteNode`, a conservative `Entity` family split, a VM runtime/owner-resource layout note, a compatibility-header draft, and one grouped resume index.
|
||||
- The same class-lift prep lane is now more execution-ready: the `0x4588` broker family has its own focused object note, the toolchain story has a dedicated fingerprint-evidence note, and there is now a concrete first-batch class-authoring checklist ready for the first MCP-backed namespace/struct/vtable pass.
|
||||
|
||||
### Recently Closed Or No Longer Live
|
||||
### Areas That Are No Longer Live Priorities
|
||||
|
||||
- The most reusable `misc_crusader_notes.txt` scratch items are no longer loose leads. `STEAM2` event hints are now checked against extracted USECODE rows, the old labels `FUN_1130_0896` / `FUN_1130_32af` / `FUN_1020_0000` / `FUN_1128_026b` are closed against live NE names, `ItemNPC_AnotherCreate` is now explicitly documented as the area-search-gated helper `NPC_CreateIfAreaSearchValid`, `Kernel_11d0_2491` is narrowed to a kernel/process snapshot writer, `FREE::ordinal3C` is constrained to an alert-clearing random `FREE::ordinal21` spawner, and `Int01E` is at least tightened from `unknown fire intrinsic` to `Actor::I_maybeFire` plus live export `1128:11da`.
|
||||
- `ASYLUM.24` is resolved as `_ASS_StopAllSFX`; it is no longer an open plan item.
|
||||
- The cheat/input side lane is complete enough to leave the live queue.
|
||||
- The segment coverage ledger is no longer a missing artifact; only refinement remains.
|
||||
- The startup/display lane is now materially complete as a major section: the outer seg005 shells, seg126 setup/script/fade path, seg127 fade controller, seg136 owner split, seg137 palette-emission helpers, and seg138 late cleanup/handoff bodies all have stable structural roles.
|
||||
- The top startup/display ownership question is closed tightly enough for planning: `active_dispatch_entry_create_default` owns `g_active_dispatch_entry_farptr`, while seg049/seg126/seg138 helpers only borrow or clear the shared byte `+0x40`; the seg108 `0x4f38` lane is separate local sprite/object state.
|
||||
- The shared seg126 base-path question is effectively closed: literal-address search still shows no store into `0x6aa:0x6ac`, seg004 only mutates the pointed buffer while separately assigning sibling root `0x6ae:0x6b0`, and the startup/display family continues to treat `0x6aa:0x6ac` as an inherited mutable external/default base path.
|
||||
- The in-scope `0x31a2` transition/presentation reader pass is complete: the remaining reads in this lane now split into edge wait, modal break, deferred dispatch/state advance, and cleanup-abort roles.
|
||||
- The remaining startup/display residuals are now low-impact: the exact higher-level UI label of preset pair `0x10/0x11` is still open, and the `000c:db68` overlap still blocks clean function hygiene for `transition_preentry_step_script` even though it no longer blocks semantic recovery.
|
||||
- Startup/display transition recovery is no longer a front-line blocker unless overlap repair becomes necessary for adjacent work.
|
||||
- The general cheat/debug key matrix no longer needs broad exploratory work.
|
||||
- The `-debug` switch is no longer an open mystery; remaining work there is mostly sink-side cleanup and documentation.
|
||||
- The earlier executable-patch experiments around the hidden debugger are documented history, not a current decompilation priority unless new evidence changes the entry model.
|
||||
|
||||
## Live Blockers
|
||||
|
||||
1. The oversized overlap rooted at `000c:db68` still blocks clean recovery of the real `transition_preentry_step_script` function object, even though it no longer blocks startup/display semantics.
|
||||
2. The `0x4588` callback object is better constrained and now leans toward a video/presentation-state broker, but it still is not behaviorally classified enough for a confident subsystem rename.
|
||||
3. The USECODE/VM sequencer still lacks the real upstream selector/caller path into `entity_vm_opcode_sequence_run`, and wrappers `entity_vm_context_try_create_mask_0400_slot0a_with_offset` / `entity_vm_context_try_create_mask_0800_slot0b_with_offset` remain outward-caller-dark even though their exact signed-additive `(slot, mask)` contracts are now closed, the generic masked hub at `000d:463a` is verified, and slot-`0x12` now has two concrete caller anchors at `0005:1776` / `0005:1945`.
|
||||
4. High-value missing or weak function objects still exist in hot ranges such as `000b:2e00`, `0007:5a00`, and `000e:ffb0`; `000e:ffb0` is now caller-side constrained to the overlapped video-frame chunk lane (`00db` / `00dc`) paired with `anim_load_audio_frame`, but the overlap still blocks clean recovery.
|
||||
5. Non-CALLF far-pointer relocations and weakly covered resource/data loaders remain real second-pass blockers, even though they are not the first thing to attack.
|
||||
6. The `Ctrl+Q` / `0x410` lane still lacks a verified USECODE or higher-level emitter body, and the current blocker is now sharper. The owner-loaded format no longer blocks comparison: the class selector is now known to be `class_id + 2`, the header/subentry arithmetic at `000d:5066/51fd/53b4` matches extracted class headers and event rows exactly, and `NPCTRIG` slot `0x0a` / `0x20` now have concrete owner-loaded body ranges instead of only motif-level fits. But the compiled selector path is now also constrained enough to show what it does not provide: `000d:45c5` only maps entities into three generic category spans, `000d:44df` seeds those spans from `0x6608..0x660e`, `0005:295f` reuses the same slot index to test owner-row bit `0x0040`, and `0005:2c35` still has no caller/xref recovery. The remaining unresolved step is therefore a real upstream class-selector or caller-provenance recovery that can prove which class family is chosen before the slot body is decoded into the later `+0xd6/+0xd8` control stream and then into the `000c:fa2f` literal/replay lane.
|
||||
1. The main remaining VM uncertainty is the real upstream selector/caller path into `entity_vm_opcode_sequence_run` and adjacent masked-create helpers. One earlier producer is now closed at `AreaSearch_CollideMove` for the `0x236` collision-storage family, but the owner-loaded class-family chooser and any broader non-collision producers are still upstream-dark.
|
||||
2. The dark masked-materializer wrappers still need caller-role recovery, especially the signed-additive slot-`0x0a` / slot-`0x0b` pair and the surrounding higher-slot wrapper ladder.
|
||||
3. The callback object rooted at `0x4588` still lacks a behaviorally safe subsystem name even though its allocation/finalize neighborhood is better constrained.
|
||||
4. A few hot or awkward function ranges still lack clean function objects or good boundaries, especially around `000c:db68`, `000e:ffb0`, and several caller-dense gaps in `0007`, `000b`, and `000e`.
|
||||
5. Weakly covered resource/data-loader families and non-`CALLF` far-pointer relocations are still a second-pass blocker for some object/table recovery work.
|
||||
6. The segment ledger has improved, but it still trails the actual verified state in the notes and Ghidra database. Promoting known segments from documented evidence remains real work, not bookkeeping trivia.
|
||||
|
||||
## Current Focus
|
||||
|
||||
1. Continue the NE `CRUSADER.EXE` lane, using verified raw full-EXE and standalone-segment work as cross-reference evidence rather than as the active execution target.
|
||||
2. Continue the USECODE/VM lane where the verified masked-create hub (`000d:463a`), the internal consumer blocks (`000d:208b`, `000d:21ed`), or the newly separated `extra-word masked materializer` subfamily can still yield concrete caller, selector, or record-shape evidence rather than repeated direct-xref dead ends.
|
||||
3. Refine the coverage ledger from already-verified notes before broadening into fresh segment sweeps.
|
||||
4. Use boundary repair only on active blockers with clear payoff, with `000c:db68` now downgraded to optional hygiene unless it blocks adjacent work again.
|
||||
5. Revisit the `0x4588` callback object only when caller-side evidence is strong enough to support behavioral naming.
|
||||
6. Use the new offline map-rendering lane to cross-check shape ids, map placements, and visible world composition against `crusader-disasm` shape/map notes before promoting additional rendering- or static-object-related names in `CRUSADER.EXE`.
|
||||
1. Keep the live NE `CRUSADER.EXE` lane as the default working surface, using raw/full-EXE and standalone-segment work only as supporting evidence.
|
||||
2. Keep the VM/USECODE lane focused on selector recovery, caller-role recovery, and record-shape confirmation rather than repeating storage-format validation that is already closed.
|
||||
3. Promote ledger coverage from existing verified notes before broadening into fresh executable-wide sweeps.
|
||||
4. Use overlap repair only where it unlocks an active high-payoff lane.
|
||||
5. Use the map-renderer/tooling lane to validate shape ids, map placements, and viewer semantics before promoting additional static-object names in Ghidra.
|
||||
|
||||
## Next Resume Point
|
||||
|
||||
1. Continue the NE `CRUSADER.EXE` lane from `docs/ne-hole-filling-priorities.md`, using `docs/crusader-disasm-reference.md`, the raw-focused docs, and prior `CRUSADER-RAW.EXE` notes as supporting handoff material: prioritize one small segment or subsystem from the ranked list where the old disasm vocabulary, shape/map evidence, and verified raw names all overlap cleanly.
|
||||
2. Build one conservative shape-id / map-placement crosswalk from `shapedata_more_complete.txt` and `mapdump/map-item-dump.txt` into the current trigger-heavy class families before promoting any new NE names.
|
||||
3. Use the `unkcoffs/` Remorse/Regret function and intrinsic dumps as hint-only candidate generators for still-positional NE functions, but only when segment-local caller/data evidence agrees.
|
||||
4. Keep the USECODE/VM lane moving where the verified masked-create hub (`000d:463a`), the internal consumer blocks (`000d:208b`, `000d:21ed`), or the newly separated `extra-word masked materializer` subfamily can still yield concrete caller, selector, or record-shape evidence rather than repeated direct-xref dead ends.
|
||||
5. Refine the coverage ledger from already-verified notes before broadening into fresh segment sweeps.
|
||||
6. Use boundary repair only on active blockers with clear payoff, with `000c:db68` now downgraded to optional hygiene unless it blocks adjacent work again.
|
||||
7. Revisit the `0x4588` callback object only when caller-side evidence is strong enough to support behavioral naming.
|
||||
8. Exercise `tools/render_crusader_map.py` on a few representative No Remorse and No Regret maps, then tighten the paint order using `TYPEFLAG.DAT` footpads and any mismatches visible against in-game screenshots or `crusader-disasm` map evidence.
|
||||
9. If the map/editor-visibility lane is revisited, start from `docs/editor-object-visibility.md` and the upstream `1180:0951..095c` world-item builder gate first; rule in or rule out a second debug-only world-item builder before spending more time on cheat or command-line searches.
|
||||
10. Continue the PSX pre-alpha lane from `docs/psx/prealpha.md`: classify the surviving `LoadExec` callers around `80046aac`, confirm whether the `TALK1.XA` path is still reachable in practice, and compare the three shipped `LSET1` bundles against the retail extractor outputs before assuming the build is only a content-pruned No Remorse branch.
|
||||
|
||||
11. Recover the real upstream caller/selector path into `entity_vm_opcode_sequence_run`, most likely by finding the first non-recursive `0x6714` context-method caller or vtable dispatch site rather than by repeating raw xref queries that still return no direct edges.
|
||||
12. Recover real caller roles for `entity_vm_context_try_create_mask_0400_slot0a_with_offset` and `entity_vm_context_try_create_mask_0800_slot0b_with_offset` by treating them as the remaining dark members of the now-verified signed-additive masked-materializer subfamily and comparing them against the newly anchored slot-`0x12` caller pattern.
|
||||
13. Tighten the newly surfaced higher-slot wrapper ladder around `0005:3115..31da`, especially the two slot-`0x12` caller sites at `0005:1776` / `0005:1945` and the slot-`0x10` guarded callsite, so any future promotion to `leaveFastArea` / `func11|cast` / `justMoved` / `AvatarStoleSomething` / `animGetHit` is driven by binary caller behavior rather than by external tables alone.
|
||||
14. Tighten the outward caller chains around the renamed seg006 masked helpers `entity_vm_context_try_create_mask_0008_slot30_with_offset` (`0006:0ba4`) and `entity_vm_context_try_create_mask_0010_slot08_with_offset_if_ready` (`0006:108c`) so the local state-selector lane and the adjacent class-linked value family can be tied back to concrete gameplay subsystems rather than only to class-detail fields.
|
||||
15. Tighten the paired-file-family reading of the seg070 twin loops at `0009:67b6` and `0009:6916` by recovering which temporary buffer and record schema each family populates behind `entity_vm_runtime_owner_resource_create`.
|
||||
16. Promote additional ledger rows where the current docs already justify `Foothold`, `Partial`, or `Deep`.
|
||||
17. If the VM lane stalls again, revisit `000e:ffb0` from the now-verified `00db/00dc` caller windows and try to recover an adjacent non-overlapped helper before attempting any boundary repair.
|
||||
18. If the immortality lane is revisited, stay focused on `NPCTRIG` slot `0x0a` first, with slot `0x20` still treated as the typed/setup companion and `EVENT` only as the generic hub baseline; the three currently recovered direct `0005:295f` caller families are now all closed and comment-backed in the live NE program at `10f0:02d9`, `10f0:0379`, `10f0:03c3`, `10f0:03e5`, `1128:0ff0`, and `1138:1384`, so the next defensible step is an earlier producer that assigns subtype `0x20b/0x20c` into field `+0x3c` or otherwise chooses the owner-loaded class family before these generic damage consumers run.
|
||||
19. Use the new Pentagram-derived parser proof of concept as the first tooling bridge for raw class/slot bodies: extend opcode coverage conservatively, emit IR v1 artifacts, and only then prototype a Ghidra-side annotation importer against compiled anchors like `000d:51fd`, `000d:5572`, `000d:46ec`, `000d:22bc`, and `000d:ebe3`.
|
||||
1. Resume from `docs/ne-hole-filling-priorities.md` and pick one small NE cluster where the old disasm vocabulary, extracted corpus evidence, and live NE callers overlap cleanly.
|
||||
2. Stay on the VM lane and move one step earlier than the now-mapped movement/collision helper set around `AreaSearch_CollideMove`: the local seg029/031/090 helper layer is now named, so the next work is the policy/dispatch layer that decides when those legal-move, gravity, animation, or supersprite paths instantiate the local `0x236` collision-storage queue, plus verification of whether any non-collision producer feeds the same `StorageDataProcess_Create` / `Run` family.
|
||||
3. Recover caller roles for the remaining dark signed-additive masked wrappers, especially the slot-`0x0a` / slot-`0x0b` pair, and compare them against the now-anchored slot-`0x12` caller pattern.
|
||||
4. Tighten the higher-slot wrapper ladder around `0005:3115..31da` so future event-label promotion depends on compiled caller behavior instead of external tables.
|
||||
5. Tighten the seg006 masked-helper caller chains so the local state-selector/value family can be tied to concrete gameplay subsystems.
|
||||
6. Classify the paired seg070 loops behind `entity_vm_runtime_owner_resource_create`, especially which temporary buffers and record schemas each family populates.
|
||||
7. Promote additional ledger rows directly from already-verified docs and live comments, especially where segments already deserve `Foothold`, `Partial`, or `Deep`; the new seg029 step-aware sweep batch, seg031 queue-release batch, and seg090 movement-helper batch should be the immediate template.
|
||||
8. If the VM lane stalls, revisit `000e:ffb0` from the now-better-constrained video/audio caller windows and try to recover an adjacent non-overlapped helper before attempting broad boundary repair.
|
||||
9. Continue the map-renderer cross-check lane by building one conservative shape-id/map-placement crosswalk from `shapedata_more_complete.txt`, extracted corpora, and authored scene evidence before promoting more trigger-heavy classes in NE.
|
||||
10. Keep the PSX pre-alpha lane alive as a secondary target: classify the `LoadExec` callers, test whether the stale `TALK1.XA` path is still reachable, and compare the shipped `LSET1` bundles against the retail extractor outputs.
|
||||
|
||||
## Remaining Work To Reach A Reasonably Complete Decompilation State
|
||||
|
||||
### 1. Coverage And Tracker Completion
|
||||
|
||||
- Promote the existing 145-row ledger from a seeded first pass into a trustworthy executable-wide coverage dashboard.
|
||||
- Sweep untouched segments cluster-by-cluster instead of one-off function hunting, using adjacency and call relationships.
|
||||
- Convert more segments from `None` to `Foothold` / `Partial` where current notes already support it.
|
||||
- Close the largest remaining hot-target gaps so the far-call ranking list stays representative of real coverage.
|
||||
- Keep the plan, docs, and ledger synchronized after each verified batch.
|
||||
- Keep turning the seeded 145-row ledger into a trustworthy whole-program dashboard.
|
||||
- Sweep remaining lightly covered segment clusters by adjacency and call relationships rather than one-off function hunting.
|
||||
- Keep the plan, the docs, the ledger, and the live Ghidra comments synchronized after each verified batch.
|
||||
|
||||
### 2. Startup/Display And Presentation Lane
|
||||
### 2. VM / USECODE / Scripting Lane
|
||||
|
||||
- Keep the startup/display lane closed unless new caller evidence materially changes its current model.
|
||||
- Classify the exact higher-level UI label of preset pair `0x10/0x11` only if stronger caller or string evidence appears.
|
||||
- Revisit the remaining seg049/seg108/seg138 naming ambiguity only when it supports a defensible behavioral rename rather than another structural pass.
|
||||
- Repair `000c:db68` only when a clean `transition_preentry_step_script` function object or adjacent active work makes the overlap fix worth the risk.
|
||||
- Close the upstream selector/caller path into the sequencer and masked-create families.
|
||||
- Finish separating owner-row-backed data from runtime-decoded control streams and dispatch-entry seed records.
|
||||
- Expand caller-backed event-label promotion only where binary behavior and slot reuse agree.
|
||||
- Keep maturing the tooling bridge from extracted corpora into compiled-side annotation/import workflows.
|
||||
|
||||
### 3. VM / USECODE / Scripting Lane
|
||||
### 3. Callback / Allocator / Object-Role Lane
|
||||
|
||||
- Recover the upstream selector into `entity_vm_opcode_sequence_run` and map payload-shape handlers to real opcode dispatch.
|
||||
- Recover real caller roles for the dark mask wrappers `entity_vm_context_try_create_mask_0400_slot0a_with_offset` and `entity_vm_context_try_create_mask_0800_slot0b_with_offset`.
|
||||
- Keep separating owner-table-backed `0x39ca` rows from static dispatch-entry seed rows.
|
||||
- Finish classifying the seg069/070 helper behind `entity_vm_runtime_owner_resource_create`.
|
||||
- Broaden owner-loaded class/event validation beyond the first strong sample families.
|
||||
- Keep event-label mapping conservative: only promote ScummVM event names where binary behavior and slot reuse agree.
|
||||
- Mature the reversible script IR until it can represent raw headers, event rows, payload forms, and unresolved opcodes without information loss.
|
||||
- Continue extracting readable descriptor-family artifacts, but treat them as evidence aids rather than rename authority.
|
||||
- Classify the `0x4588` callback object strongly enough for a real subsystem name.
|
||||
- Separate generic cache/allocator mechanics from game-specific client behavior where caller evidence supports it.
|
||||
- Keep low-level helper names conservative until behavior, not just structure, is clear.
|
||||
|
||||
### 4. Cache / Allocator / Callback-Object Lane
|
||||
### 4. Rendering / Animation / UI Support Lanes
|
||||
|
||||
- Finish classifying the object rooted at `0x4588` so the allocator finalize path and callback emissions can receive behaviorally meaningful names.
|
||||
- Tighten the role of `allocator_phase_finalize_pass` only where it intersects callback-object semantics or active runtime users.
|
||||
- Separate generic cache-manager mechanics from game-specific client behavior wherever caller evidence supports it.
|
||||
- Clarify remaining object-role names around tracked handles, dispatch-entry lifecycle helpers, and palette-backed state builders.
|
||||
- Keep `_ASS_StopAllSFX` and the resolved audio-import lane closed; do not treat it as an open blocker again.
|
||||
- Keep the rendering/palette/animation lanes focused on caller-side semantics and cleanup, not exploratory renaming in isolation.
|
||||
- Revisit `000e:ffb0` and adjacent overlap-heavy video helpers only when the payoff is clear.
|
||||
- Use map-renderer evidence and extracted corpora to validate static-object and helper/controller naming before promoting it into live NE work.
|
||||
|
||||
### 5. Rendering, Palette, Animation, And UI Support Lanes
|
||||
### 5. Data / Resource / Relocation Coverage
|
||||
|
||||
- Finish the remaining caller-side semantics for raw 0007 rendering helpers, seg049 controller dispatch, seg108 sprite/object helpers, and seg137/138 palette state builders.
|
||||
- Revisit `000e:ffb0` and adjacent 000e video/animation overlap only when it blocks active analysis or offers a strong isolated win.
|
||||
- Expand the palette/VGA helper family only where it clarifies higher-level behavior rather than duplicating low-level helper names.
|
||||
- Keep validating startup/display assumptions against raw 0007/0008/000d caller behavior instead of renaming isolated helpers in a vacuum.
|
||||
|
||||
### 6. Boundary Repair And Function Hygiene
|
||||
|
||||
- Create or repair missing function objects in the highest-traffic unresolved ranges first.
|
||||
- Fix only overlaps that block live lanes or high-caller targets.
|
||||
- Preserve conservative naming for repaired functions until direct caller or data evidence justifies promotion.
|
||||
- Continue rejecting disproven ports or stale hypotheses instead of preserving them in live work queues.
|
||||
|
||||
### 7. Data, Imports, And Resource-Format Coverage
|
||||
|
||||
- Work through the deferred non-CALLF far-pointer relocations when they become necessary for object/table recovery.
|
||||
- Expand coverage of weakly mapped resource/data loaders such as FLEX-derived descriptors, tables, caches, and per-shape data files.
|
||||
- Cross-check current data-structure assumptions against external references like ScummVM only as supporting evidence, not as rename authority.
|
||||
- Keep external import identities synchronized with verified import-table evidence.
|
||||
|
||||
### 8. Completion Criteria
|
||||
|
||||
A reasonably complete decompilation state should mean:
|
||||
|
||||
- most actively used subsystems are behaviorally named rather than only structurally named,
|
||||
- the major live blockers (`000c:db68`, `000e:ffb0`, hot missing function objects, dark VM selector path, `0x4588` object role) are either resolved or reduced to low-impact residuals,
|
||||
- the far-call hot list has very few meaningful unknowns left,
|
||||
- the ledger gives a credible whole-program view rather than a sparse seed set,
|
||||
- and the remaining gaps are mostly long-tail cleanup, low-traffic helpers, or data polish instead of core architecture uncertainty.
|
||||
- Tackle deferred non-`CALLF` far-pointer relocations when they are needed for active table/object recovery.
|
||||
- Broaden weakly covered resource/data-loader families where they block real subsystem classification.
|
||||
- Keep external references like ScummVM or older disasm corpora as evidence aids, not rename authority.
|
||||
|
||||
## Priority Order
|
||||
|
||||
1. Startup/display transition lane
|
||||
2. VM / USECODE selector and loader lane
|
||||
3. Coverage-ledger refinement from already-verified notes
|
||||
4. High-value overlap repair (`000c:db68`, then `000e:ffb0` when justified)
|
||||
5. `0x4588` callback-object classification
|
||||
6. Broader segment sweeps and second-pass data/relocation work
|
||||
1. VM / USECODE selector and caller recovery
|
||||
2. Coverage-ledger refinement from already-verified notes
|
||||
3. Callback-object classification around `0x4588`
|
||||
4. High-value boundary repair when it unlocks active work
|
||||
5. Broader segment sweeps and second-pass data/relocation work
|
||||
6. Secondary map-renderer and PSX follow-up lanes
|
||||
|
||||
## Evidence Anchors
|
||||
|
||||
|
|
@ -301,15 +146,14 @@ Primary files backing this plan state:
|
|||
- `crusader_segment_coverage_ledger.csv`
|
||||
- `crusader_decompilation_notes.md`
|
||||
- `docs/overview.md`
|
||||
- `docs/ne-hole-filling-priorities.md`
|
||||
- `docs/crusader-disasm-reference.md`
|
||||
- `docs/raw-porting-progress.md`
|
||||
- `docs/raw-0008-000c.md`
|
||||
- `docs/raw-000a-000d.md`
|
||||
- `docs/raw-000e.md`
|
||||
- `docs/crusader-disasm-reference.md`
|
||||
- `docs/ne-hole-filling-priorities.md`
|
||||
- `docs/far-call-targets.md`
|
||||
- `docs/usecode-roundtrip-ir.md`
|
||||
- `docs/scummvm-crusader-reference.md`
|
||||
|
||||
## Update Rule
|
||||
|
||||
|
|
@ -321,4 +165,4 @@ Update this file when one of the following happens:
|
|||
- a segment cluster is promoted materially in the ledger,
|
||||
- or the next resume point changes enough that the current handoff would mislead the next pass.
|
||||
|
||||
Keep the file short. Move detailed completed analysis into the appropriate file under `docs/` and leave only the current state, blockers, and forward path here.
|
||||
Keep this file short. Move detailed completed analysis into the appropriate file under `docs/` and leave only the current state, blockers, and forward path here.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue