Add detailed class event processing and family comparison tools

- Enhance `extract_eusecode_flx.py` to derive class event rows with additional metadata including derived body windows and repeated template statuses.
- Introduce `usecode_family_compare.py` for comparing event families, analyzing commonalities in event bodies, and generating reports on identical groups and differences.
- Implement new data structures for managing class event rows and family artifact specifications.
- Update output formats to include derived body information and repeated family regression checks.
- Ensure robust validation of repeated family expectations against actual extracted data.
This commit is contained in:
MaddoScientisto 2026-03-22 23:24:46 +01:00
commit 4d3c8cd81b
23 changed files with 15033 additions and 14221 deletions

View file

@ -9,644 +9,7 @@
</SAVE_STATE>
</PROJECT_DATA_XML_NAME>
<TOOL_MANAGER ACTIVE_WORKSPACE="Workspace">
<WORKSPACE NAME="Workspace" ACTIVE="true">
<RUNNING_TOOL TOOL_NAME="CodeBrowser">
<ROOT_NODE X_POS="1" Y_POS="73" WIDTH="1815" HEIGHT="1110" EX_STATE="0">
<SPLIT_NODE WIDTH="100" HEIGHT="100" DIVIDER_LOCATION="0" ORIENTATION="VERTICAL">
<SPLIT_NODE WIDTH="1801" HEIGHT="1014" DIVIDER_LOCATION="880" ORIENTATION="VERTICAL">
<SPLIT_NODE WIDTH="1801" HEIGHT="889" DIVIDER_LOCATION="863" ORIENTATION="VERTICAL">
<SPLIT_NODE WIDTH="100" HEIGHT="100" DIVIDER_LOCATION="0" ORIENTATION="VERTICAL">
<SPLIT_NODE WIDTH="1621" HEIGHT="816" DIVIDER_LOCATION="148" ORIENTATION="VERTICAL">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Entropy" OWNER="EntropyPlugin" TITLE="Entropy" ACTIVE="false" GROUP="Header" INSTANCE_ID="3207819926581772885" />
<COMPONENT_INFO NAME="Overview" OWNER="OverviewPlugin" TITLE="Overview" ACTIVE="false" GROUP="Header" INSTANCE_ID="3207819926581772883" />
</COMPONENT_NODE>
<SPLIT_NODE WIDTH="1801" HEIGHT="764" DIVIDER_LOCATION="143" ORIENTATION="HORIZONTAL">
<SPLIT_NODE WIDTH="257" HEIGHT="764" DIVIDER_LOCATION="640" ORIENTATION="VERTICAL">
<SPLIT_NODE WIDTH="257" HEIGHT="486" DIVIDER_LOCATION="502" ORIENTATION="VERTICAL">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Program Tree" OWNER="ProgramTreePlugin" TITLE="Program Trees" ACTIVE="true" GROUP="Default" INSTANCE_ID="3720737536777513708" />
</COMPONENT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Symbol Tree" OWNER="SymbolTreePlugin" TITLE="Symbol Tree" ACTIVE="true" GROUP="Default" INSTANCE_ID="3720737536777513703" />
</COMPONENT_NODE>
</SPLIT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="DataTypes Provider" OWNER="DataTypeManagerPlugin" TITLE="Data Type Manager" ACTIVE="true" GROUP="Default" INSTANCE_ID="3720738032686852857" />
</COMPONENT_NODE>
</SPLIT_NODE>
<SPLIT_NODE WIDTH="1540" HEIGHT="764" DIVIDER_LOCATION="785" ORIENTATION="VERTICAL">
<SPLIT_NODE WIDTH="1386" HEIGHT="638" DIVIDER_LOCATION="705" ORIENTATION="VERTICAL">
<SPLIT_NODE WIDTH="1540" HEIGHT="597" DIVIDER_LOCATION="490" ORIENTATION="HORIZONTAL">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Listing" OWNER="CodeBrowserPlugin" TITLE="Listing: CRUSADER-RAW.EXE" ACTIVE="true" GROUP="Core" INSTANCE_ID="3720737536777513718" />
</COMPONENT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Decompiler" OWNER="DecompilePlugin" TITLE="Decompile: keyboard_state_read" ACTIVE="true" GROUP="Default" INSTANCE_ID="3720737536777513709" />
<COMPONENT_INFO NAME="Bytes" OWNER="ByteViewerPlugin" TITLE="Bytes: CRUSADER-RAW.EXE" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720737536777513711" />
<COMPONENT_INFO NAME="Data Window" OWNER="DataWindowPlugin" TITLE="Defined Data" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720738032686852862" />
<COMPONENT_INFO NAME="Defined Strings" OWNER="ViewStringsPlugin" TITLE="Defined Strings" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720738032917539554" />
<COMPONENT_INFO NAME="Equates Table" OWNER="EquateTablePlugin" TITLE="Equates Table" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720737536777513715" />
<COMPONENT_INFO NAME="External Programs" OWNER="ReferencesPlugin" TITLE="External Programs" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720737536777513719" />
<COMPONENT_INFO NAME="Functions Window" OWNER="FunctionWindowPlugin" TITLE="Functions" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720737536777513722" />
<COMPONENT_INFO NAME="Relocation Table" OWNER="RelocationTablePlugin" TITLE="Relocation Table" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720738032917539553" />
</COMPONENT_NODE>
</SPLIT_NODE>
<SPLIT_NODE WIDTH="1386" HEIGHT="189" DIVIDER_LOCATION="495" ORIENTATION="HORIZONTAL">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Data Type Preview" OWNER="DataTypePreviewPlugin" TITLE="Data Type Preview" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720738032686852855" />
</COMPONENT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Virtual Disassembler - Current Instruction" OWNER="DisassembledViewPlugin" TITLE="Disassembled View" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720737536777513714" />
</COMPONENT_NODE>
</SPLIT_NODE>
</SPLIT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Console" OWNER="ConsolePlugin" TITLE="Console" ACTIVE="true" GROUP="Default" INSTANCE_ID="3720737536777513710" />
<COMPONENT_INFO NAME="Bookmarks" OWNER="BookmarkPlugin" TITLE="Bookmarks" ACTIVE="false" GROUP="Core.Bookmarks" INSTANCE_ID="3720737536777513707" />
</COMPONENT_NODE>
</SPLIT_NODE>
</SPLIT_NODE>
</SPLIT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Function Call Trees" OWNER="CallTreePlugin" TITLE="Function Call Trees" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720737536777513704" />
</COMPONENT_NODE>
</SPLIT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Domain Events" OWNER="DomainEventDisplayPlugin" TITLE="Domain Object Event Display" ACTIVE="true" GROUP="Default" INSTANCE_ID="3720737536777513716" />
</COMPONENT_NODE>
</SPLIT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Plugin Event Display" OWNER="EventDisplayPlugin" TITLE="Plugin Event Display" ACTIVE="true" GROUP="Default" INSTANCE_ID="3720737536777513713" />
</COMPONENT_NODE>
</SPLIT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Database Viewer" OWNER="DbViewerPlugin" TITLE="Database Viewer" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720738034586872545" />
</COMPONENT_NODE>
</SPLIT_NODE>
<WINDOW_NODE X_POS="426" Y_POS="178" WIDTH="1033" HEIGHT="689">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Script Manager" OWNER="GhidraScriptMgrPlugin" TITLE="Script Manager" ACTIVE="false" GROUP="Script Group" INSTANCE_ID="3720737536777513705" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="423" Y_POS="144" WIDTH="927" HEIGHT="370">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Memory Map" OWNER="MemoryMapPlugin" TITLE="Memory Map" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720737536777513700" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="383" Y_POS="7" WIDTH="1020" HEIGHT="1038">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Function Graph" OWNER="FunctionGraphPlugin" TITLE="Function Graph" ACTIVE="false" GROUP="Function Graph" INSTANCE_ID="3720738032917539555" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="550" Y_POS="206" WIDTH="655" HEIGHT="509">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Register Manager" OWNER="RegisterPlugin" TITLE="Register Manager" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720737536777513721" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="287" Y_POS="186" WIDTH="1424" HEIGHT="666">
<SPLIT_NODE WIDTH="1408" HEIGHT="559" DIVIDER_LOCATION="573" ORIENTATION="HORIZONTAL">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Symbol Table" OWNER="SymbolTablePlugin" TITLE="Symbol Table" ACTIVE="false" GROUP="symbolTable" INSTANCE_ID="3720738032686852863" />
</COMPONENT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Symbol References" OWNER="SymbolTablePlugin" TITLE="Symbol References" ACTIVE="false" GROUP="symbolTable" INSTANCE_ID="3720738032917539552" />
</COMPONENT_NODE>
</SPLIT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="-1" Y_POS="-1" WIDTH="0" HEIGHT="0">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Checksum Generator" OWNER="ComputeChecksumsPlugin" TITLE="Checksum Generator" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720737536777513717" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="-1" Y_POS="-1" WIDTH="0" HEIGHT="0">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Function Tags" OWNER="FunctionTagPlugin" TITLE="Function Tags" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720738032686852858" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="-1" Y_POS="-1" WIDTH="0" HEIGHT="0">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Comment Window" OWNER="CommentWindowPlugin" TITLE="Comments" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720738032686852861" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="-1" Y_POS="-1" WIDTH="0" HEIGHT="0">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Python" OWNER="InterpreterPanelPlugin" TITLE="Python" ACTIVE="false" GROUP="Default" INSTANCE_ID="3207819978370941531" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="0" Y_POS="0" WIDTH="0" HEIGHT="0">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Jython" OWNER="Jython" TITLE="Jython" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720738032686852859" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="0" Y_POS="0" WIDTH="0" HEIGHT="0">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Bundle Manager" OWNER="GhidraScriptMgrPlugin" TITLE="Bundle Manager" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720737536777513706" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="0" Y_POS="0" WIDTH="0" HEIGHT="0">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="PyGhidra" OWNER="PyGhidra" TITLE="PyGhidra" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720738032686852860" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="0" Y_POS="0" WIDTH="0" HEIGHT="0">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Source Files and Transforms" OWNER="SourceFilesTablePlugin" TITLE="Source Files and Transforms" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720737536777513720" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="0" Y_POS="0" WIDTH="0" HEIGHT="0">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Function Call Graph" OWNER="FunctionCallGraphPlugin" TITLE="Function Call Graph" ACTIVE="false" GROUP="Function Call Graph" INSTANCE_ID="3720738032686852856" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="588" Y_POS="50" WIDTH="1018" HEIGHT="1087">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Graph" OWNER="DefaultGraphDisplay" TITLE="AST Data Flow Graph For entity_state_tick_dispatch" ACTIVE="false" GROUP="ProgramGraph" INSTANCE_ID="3720233517670421199" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="0" Y_POS="0" WIDTH="0" HEIGHT="0">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Window Locations" OWNER="WindowLocationPlugin" TITLE="Window Locations" ACTIVE="false" GROUP="Default" INSTANCE_ID="3720737536777513701" />
</COMPONENT_NODE>
</WINDOW_NODE>
</ROOT_NODE>
<DATA_STATE>
<PLUGIN NAME="NavigationHistoryPlugin">
<XML NAME="HISTORY_LIST_0">
<SAVE_STATE>
<STATE NAME="CURRENT_LOC_INDEX" TYPE="int" VALUE="6" />
<STATE NAME="LOCATION_COUNT" TYPE="int" VALUE="7" />
<STATE NAME="MEMENTO_CLASS0" TYPE="string" VALUE="ghidra.app.plugin.core.gotoquery.DefaultNavigatableLocationMemento" />
<STATE NAME="MEMENTO_CLASS1" TYPE="string" VALUE="ghidra.app.plugin.core.gotoquery.DefaultNavigatableLocationMemento" />
<STATE NAME="MEMENTO_CLASS2" TYPE="string" VALUE="ghidra.app.plugin.core.gotoquery.DefaultNavigatableLocationMemento" />
<STATE NAME="MEMENTO_CLASS3" TYPE="string" VALUE="ghidra.app.plugin.core.gotoquery.DefaultNavigatableLocationMemento" />
<STATE NAME="MEMENTO_CLASS4" TYPE="string" VALUE="ghidra.app.plugin.core.gotoquery.DefaultNavigatableLocationMemento" />
<STATE NAME="MEMENTO_CLASS5" TYPE="string" VALUE="ghidra.app.plugin.core.gotoquery.DefaultNavigatableLocationMemento" />
<STATE NAME="MEMENTO_CLASS6" TYPE="string" VALUE="ghidra.app.plugin.core.gotoquery.DefaultNavigatableLocationMemento" />
<XML NAME="MEMENTO_DATA0">
<SAVE_STATE>
<XML NAME="MEMENTO0">
<SAVE_STATE>
<STATE NAME="CURSOR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.codebrowser.CodeViewerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3720737536777513718" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0000:0000" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0000:0000" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.MemoryBlockStartFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<ARRAY NAME="_COMMENT" TYPE="string">
<A VALUE="//" />
<A VALUE="// ram" />
<A VALUE="// ram:0000:0000-ram:000f:2285" />
<A VALUE="//" />
</ARRAY>
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_TYPE" TYPE="int" VALUE="-1" />
</SAVE_STATE>
</XML>
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.gotoquery.DefaultNavigatableLocationMemento" />
<STATE NAME="NUM_MEMENTOS" TYPE="int" VALUE="1" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0000:0000" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0000:0000" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.MemoryBlockStartFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<ARRAY NAME="_COMMENT" TYPE="string">
<A VALUE="//" />
<A VALUE="// ram" />
<A VALUE="// ram:0000:0000-ram:000f:2285" />
<A VALUE="//" />
</ARRAY>
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_TYPE" TYPE="int" VALUE="-1" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO_DATA1">
<SAVE_STATE>
<XML NAME="MEMENTO0">
<SAVE_STATE>
<STATE NAME="INDEX" TYPE="int" VALUE="0" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.decompile.DecompilerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3720737536777513709" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="X_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="Y_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0007:04dc" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0007:04dc" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0007:04dc" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined __cdecl16far keyboard_input_cheat_dispatch()" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO1">
<SAVE_STATE>
<STATE NAME="CURSOR_OFFSET" TYPE="int" VALUE="283" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.codebrowser.CodeViewerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3720737536777513718" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0007:04dc" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0007:04dc" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0007:04dc" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined __cdecl16far keyboard_input_cheat_dispatch()" />
</SAVE_STATE>
</XML>
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.gotoquery.DefaultNavigatableLocationMemento" />
<STATE NAME="NUM_MEMENTOS" TYPE="int" VALUE="2" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0007:04dc" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0007:04dc" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0007:04dc" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined __cdecl16far keyboard_input_cheat_dispatch()" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO_DATA2">
<SAVE_STATE>
<XML NAME="MEMENTO0">
<SAVE_STATE>
<STATE NAME="INDEX" TYPE="int" VALUE="0" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.decompile.DecompilerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3720737536777513709" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="X_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="Y_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0008:a3fb" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0008:a3fb" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0008:a3fb" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined keyboard_acquire()" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO1">
<SAVE_STATE>
<STATE NAME="CURSOR_OFFSET" TYPE="int" VALUE="278" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.codebrowser.CodeViewerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3720737536777513718" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0008:a3fb" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0008:a3fb" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0008:a3fb" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined keyboard_acquire()" />
</SAVE_STATE>
</XML>
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.gotoquery.DefaultNavigatableLocationMemento" />
<STATE NAME="NUM_MEMENTOS" TYPE="int" VALUE="2" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0008:a3fb" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0008:a3fb" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0008:a3fb" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined keyboard_acquire()" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO_DATA3">
<SAVE_STATE>
<XML NAME="MEMENTO0">
<SAVE_STATE>
<STATE NAME="INDEX" TYPE="int" VALUE="210" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.decompile.DecompilerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3720737536777513709" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="X_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="Y_OFFSET" TYPE="int" VALUE="-9" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0007:04dc" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0007:04dc" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0007:04dc" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined keyboard_input_cheat_dispatch()" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO1">
<SAVE_STATE>
<STATE NAME="CURSOR_OFFSET" TYPE="int" VALUE="278" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.codebrowser.CodeViewerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3720737536777513718" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0007:04dc" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0007:04dc" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0007:04dc" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined __cdecl16far keyboard_input_cheat_dispatch()" />
</SAVE_STATE>
</XML>
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.gotoquery.DefaultNavigatableLocationMemento" />
<STATE NAME="NUM_MEMENTOS" TYPE="int" VALUE="2" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0007:04dc" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0007:04dc" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0007:04dc" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined keyboard_input_cheat_dispatch()" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO_DATA4">
<SAVE_STATE>
<XML NAME="MEMENTO0">
<SAVE_STATE>
<STATE NAME="INDEX" TYPE="int" VALUE="0" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.decompile.DecompilerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3720737536777513709" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="X_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="Y_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0008:a77d" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0008:a77d" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0008:a77d" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined keyboard_interrupt_call()" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO1">
<SAVE_STATE>
<STATE NAME="CURSOR_OFFSET" TYPE="int" VALUE="278" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.codebrowser.CodeViewerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3720737536777513718" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0008:a77d" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0008:a77d" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0008:a77d" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined __cdecl16far keyboard_interrupt_call()" />
</SAVE_STATE>
</XML>
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.gotoquery.DefaultNavigatableLocationMemento" />
<STATE NAME="NUM_MEMENTOS" TYPE="int" VALUE="2" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0008:a77d" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0008:a77d" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0008:a77d" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined keyboard_interrupt_call()" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO_DATA5">
<SAVE_STATE>
<XML NAME="MEMENTO0">
<SAVE_STATE>
<STATE NAME="INDEX" TYPE="int" VALUE="0" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.decompile.DecompilerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3720737536777513709" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="X_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="Y_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0008:a43c" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0008:a43c" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0008:a43c" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined keyboard_release()" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO1">
<SAVE_STATE>
<STATE NAME="CURSOR_OFFSET" TYPE="int" VALUE="278" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.codebrowser.CodeViewerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3720737536777513718" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0008:a43c" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0008:a43c" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0008:a43c" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined __cdecl16far keyboard_release()" />
</SAVE_STATE>
</XML>
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.gotoquery.DefaultNavigatableLocationMemento" />
<STATE NAME="NUM_MEMENTOS" TYPE="int" VALUE="2" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0008:a43c" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0008:a43c" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0008:a43c" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined keyboard_release()" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO_DATA6">
<SAVE_STATE>
<XML NAME="MEMENTO0">
<SAVE_STATE>
<STATE NAME="INDEX" TYPE="int" VALUE="0" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.decompile.DecompilerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3720737536777513709" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="X_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="Y_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0008:a43c" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0008:a43c" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0008:a43c" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined keyboard_release()" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO1">
<SAVE_STATE>
<STATE NAME="CURSOR_OFFSET" TYPE="int" VALUE="278" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.codebrowser.CodeViewerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3720737536777513718" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0008:a3e6" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0008:a3e6" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0008:a3e6" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined __cdecl16far keyboard_state_read()" />
</SAVE_STATE>
</XML>
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.gotoquery.DefaultNavigatableLocationMemento" />
<STATE NAME="NUM_MEMENTOS" TYPE="int" VALUE="2" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3720180275162699156" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/CRUSADER-RAW.EXE" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0008:a3e6" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0008:a3e6" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0008:a3e6" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined keyboard_state_read()" />
</SAVE_STATE>
</XML>
<STATE NAME="NAV_ID" TYPE="long" VALUE="-1" />
</SAVE_STATE>
</XML>
<STATE NAME="LIST_COUNT" TYPE="int" VALUE="1" />
</PLUGIN>
<PLUGIN NAME="ProgramTreePlugin">
<STATE NAME="Current Viewname" TYPE="string" VALUE="Program Tree" />
<ARRAY NAME="GroupNameProgram Tree0" TYPE="string">
<A VALUE="CRUSADER.EXE" />
</ARRAY>
<STATE NAME="NavigationToggleState" TYPE="boolean" VALUE="false" />
<STATE NAME="NumberOfGroupsProgram Tree" TYPE="int" VALUE="1" />
<STATE NAME="NumberOfViews" TYPE="int" VALUE="1" />
<STATE NAME="TreeName-0" TYPE="string" VALUE="Program Tree" />
</PLUGIN>
<PLUGIN NAME="DecompilePlugin">
<STATE NAME="INDEX" TYPE="int" VALUE="0" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3720737536777513709" />
<STATE NAME="Num Disconnected" TYPE="int" VALUE="0" />
<STATE NAME="Y_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0008:a3e6" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0008:a3e6" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0008:a3e6" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined keyboard_state_read()" />
</PLUGIN>
<PLUGIN NAME="ByteViewerPlugin">
<STATE NAME="Block Column" TYPE="int" VALUE="0" />
<STATE NAME="Block Num" TYPE="int" VALUE="0" />
<STATE NAME="Block Offset" TYPE="string" VALUE="0" />
<STATE NAME="Index" TYPE="int" VALUE="0" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3720737536777513711" />
<STATE NAME="Num Disconnected" TYPE="int" VALUE="0" />
<STATE NAME="X Offset" TYPE="int" VALUE="0" />
<STATE NAME="Y Offset" TYPE="int" VALUE="0" />
</PLUGIN>
<PLUGIN NAME="ProgramManagerPlugin">
<STATE NAME="CURRENT_FILE" TYPE="string" VALUE="CRUSADER-RAW.EXE" />
<STATE NAME="LOCATION_0" TYPE="string" VALUE="/K:/ghidra/Crusader_Decomp/" />
<STATE NAME="NUM_PROGRAMS" TYPE="int" VALUE="1" />
<STATE NAME="PATHNAME_0" TYPE="string" VALUE="/CRUSADER-RAW.EXE" />
<STATE NAME="PROJECT_NAME_0" TYPE="string" VALUE="Crusader" />
<STATE NAME="VERSION_0" TYPE="int" VALUE="-1" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0008:a3e6" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0008:a3e6" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0008:a3e6" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined keyboard_state_read()" />
</PLUGIN>
<PLUGIN NAME="FunctionGraphPlugin">
<SAVE_STATE NAME="COMPLEX_LAYOUT_NAME" TYPE="SaveState">
<COMPLEX_LAYOUT_NAME>
<STATE NAME="LAYOUT_CLASS_NAME" TYPE="string" VALUE="ghidra.app.plugin.core.functiongraph.graph.layout.DecompilerNestedLayoutProvider" />
<STATE NAME="LAYOUT_NAME" TYPE="string" VALUE="Nested Code Layout" />
</COMPLEX_LAYOUT_NAME>
</SAVE_STATE>
<STATE NAME="DISPLAY_POPUPS" TYPE="boolean" VALUE="true" />
<STATE NAME="DISPLAY_SATELLITE" TYPE="boolean" VALUE="true" />
<STATE NAME="DOCK_SATELLITE" TYPE="boolean" VALUE="true" />
<STATE NAME="DOCK_SATELLITE_POSITION" TYPE="string" VALUE="LOWER_RIGHT" />
<STATE NAME="Disconnected Count" TYPE="int" VALUE="0" />
<ENUM NAME="EDGE_HOVER_HIGHLIGHT" TYPE="enum" CLASS="ghidra.app.plugin.core.functiongraph.EdgeDisplayType" VALUE="ScopedFlowsFromVertex" />
<ENUM NAME="EDGE_SELECTION_HIGHLIGHT" TYPE="enum" CLASS="ghidra.app.plugin.core.functiongraph.EdgeDisplayType" VALUE="AllCycles" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3720738032917539555" />
</PLUGIN>
<PLUGIN NAME="CodeBrowserPlugin">
<STATE NAME="INDEX" TYPE="int" VALUE="566227" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3720737536777513718" />
<STATE NAME="Num Disconnected" TYPE="int" VALUE="0" />
<STATE NAME="Y_OFFSET" TYPE="int" VALUE="-10" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="0008:a3e6" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="0008:a3e6" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.FunctionReturnTypeFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNC_ADDRESS" TYPE="string" VALUE="0008:a3e6" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined __cdecl16far keyboard_state_read()" />
</PLUGIN>
</DATA_STATE>
</RUNNING_TOOL>
</WORKSPACE>
<WORKSPACE NAME="Workspace" ACTIVE="true" />
</TOOL_MANAGER>
</PROJECT>

View file

@ -1,6 +1,6 @@
# EUSECODE.FLX First-Pass Extraction
Input: k:\ghidra\Crusader_Decomp\USECODE\EUSECODE.FLX
Input: USECODE\EUSECODE.FLX
File size: 0x87E45 (556613 bytes)
Candidate entries: 403
@ -423,7 +423,9 @@ ASCII: `........................................................................
- `.strings.txt` files are the main human-readable output for now; `.txt` files are emitted only for chunks that look text-like.
- `descriptor_index.tsv` summarizes guessed class labels, field names, and compact tag patterns for descriptor-like chunks.
- `class_layout_index.tsv` records the conservative owner-loaded class parsing state: object index, class id, class-name hint, raw bytes-8..11 field, derived code-base-minus-one, and event-count/table-end values when the local divisibility and bounds checks succeed.
- `class_event_index.tsv` expands parsed owner-loaded classes into raw 6-byte event rows with slot numbers, ScummVM event-name hints for `0x00..0x1f`, unresolved leading words, and raw code-offset dwords for round-trip tooling work.
- `class_event_index.tsv` now also emits derived body-window columns (`derived_body_start`, `derived_body_end`, `derived_body_length`) plus conservative `repeated_template_status` tags for verified repeated families.
- `boot_family_decompile.md` / `.tsv`, `callback_family_decompile.md` / `.tsv`, and `environmental_family_decompile.md` / `.tsv` now provide reversible per-class decompile artifacts for the `_BOOT`, `SURCAM*`, and environmental repeated-family lanes.
- `repeated_family_regressions.tsv` enforces the current repeated-family slot sets plus the verified raw-row and derived body-window fields for `JELYHACK/JELYH2`, `_BOOT`, `SURCAM*`, and `FLAMEBOX/NOSTRIL/STEAMBOX`.
- `descriptor_neighborhoods.tsv` captures local table neighborhoods around trigger/event-related classes such as `JELYHACK`, `NPCTRIG`, `CRUZTRIG`, `TRIGPAD`, and `SPECIAL`.
- `referent_anchor_event_graph.tsv` groups referent-bearing descriptors with nearby event-bearing neighbors so the attachment model can be inspected without ad hoc grepping.
- `jelyhack_island_graph.md` now uses a wider local window so the `JELYHACK` / `JELYH2` anchors can be inspected alongside the nearby event-bearing `REE_BOOT`, `SURCAMEW`, and `SFXTRIG` descriptors rather than stopping at the referent-only neighbors.

View file

@ -0,0 +1,239 @@
# _BOOT Family Decompiled Event Sketches
This is a reversible per-class rendering derived directly from `class_event_index.tsv` plus the raw extracted chunk bytes.
ScummVM event labels remain hints only; the authoritative data here is the slot id, raw row bytes, and derived body window.
## AND_BOOT
```yaml
class:
entry_index: 0x0AB
class_id: 0x314
class_name: AND_BOOT
class_object_index: 0x316
raw_code_base_u32: 0xD4
code_base_minus_one: 0xD3
conservative_event_count: 32
events:
- slot: 0x0a
event_name_hint: equip
raw_event_entry_word: 0x0253
raw_code_offset: 0x00000001
derived_body_start: 0x00d4
derived_body_end: 0x0327
derived_body_length: 595
repeated_template_status: boot-event-core/shared-slot-0x0A/shared-slot-template
body_sha1: d7a28ffc24ab488970328a576f16af20f76d2a71
body_prefix_hex: 5a025c2902414e44
body_suffix_hex: 6f756e746572007a
- slot: 0x0f
event_name_hint: enterFastArea
raw_event_entry_word: 0x0237
raw_code_offset: 0x00000254
derived_body_start: 0x0327
derived_body_end: 0x055e
derived_body_length: 567
repeated_template_status: boot-event-core/shared-slot-0x0F/shared-slot-template
body_sha1: 29fa661d1f7934f505c9efa6e7ec0bcbb7506b77
body_prefix_hex: 5a045c0e02414e44
body_suffix_hex: 6f756e746572007a
- slot: 0x10
event_name_hint: leaveFastArea
raw_event_entry_word: 0x003b
raw_code_offset: 0x0000048b
derived_body_start: 0x055e
derived_body_end: 0x0599
derived_body_length: 59
repeated_template_status: boot-event-core/shared-slot-0x10/same-length-template
body_sha1: 7e3cc8034632df1963951b6c26e8cef2f18e2616
body_prefix_hex: 5a005c2700414e44
body_suffix_hex: 666572656e74007a
```
## BRO_BOOT
```yaml
class:
entry_index: 0x0AC
class_id: 0x316
class_name: BRO_BOOT
class_object_index: 0x318
raw_code_base_u32: 0xD4
code_base_minus_one: 0xD3
conservative_event_count: 32
events:
- slot: 0x0a
event_name_hint: equip
raw_event_entry_word: 0x02d5
raw_code_offset: 0x00000001
derived_body_start: 0x00d4
derived_body_end: 0x03a9
derived_body_length: 725
repeated_template_status: boot-event-core/shared-slot-0x0A/shared-slot-template
body_sha1: bd08c6f4b1201d500ee2722b9cd4a2e3eb89af5f
body_prefix_hex: 5a025cab0242524f
body_suffix_hex: 6f756e746572007a
- slot: 0x0f
event_name_hint: enterFastArea
raw_event_entry_word: 0x024c
raw_code_offset: 0x000002d6
derived_body_start: 0x03a9
derived_body_end: 0x05f5
derived_body_length: 588
repeated_template_status: boot-event-core/shared-slot-0x0F/shared-slot-template
body_sha1: 8b22769c8386fb4f8592aaf958820a452679a6b0
body_prefix_hex: 5a045c230242524f
body_suffix_hex: 6f756e746572007a
- slot: 0x10
event_name_hint: leaveFastArea
raw_event_entry_word: 0x003b
raw_code_offset: 0x00000522
derived_body_start: 0x05f5
derived_body_end: 0x0630
derived_body_length: 59
repeated_template_status: boot-event-core/shared-slot-0x10/same-length-template
body_sha1: dd5ecced3b31dda2e5cd6a8d8cb2e9df41669ebd
body_prefix_hex: 5a005c270042524f
body_suffix_hex: 666572656e74007a
```
## COR_BOOT
```yaml
class:
entry_index: 0x0BD
class_id: 0x360
class_name: COR_BOOT
class_object_index: 0x362
raw_code_base_u32: 0xD4
code_base_minus_one: 0xD3
conservative_event_count: 32
events:
- slot: 0x0a
event_name_hint: equip
raw_event_entry_word: 0x0227
raw_code_offset: 0x00000001
derived_body_start: 0x00d4
derived_body_end: 0x02fb
derived_body_length: 551
repeated_template_status: boot-event-core/shared-slot-0x0A/shared-slot-template
body_sha1: 97f1df8d8e6c9c7e151904d9ac1296f27d93581d
body_prefix_hex: 5a025cfd01434f52
body_suffix_hex: 6f756e746572007a
- slot: 0x0f
event_name_hint: enterFastArea
raw_event_entry_word: 0x0234
raw_code_offset: 0x00000228
derived_body_start: 0x02fb
derived_body_end: 0x052f
derived_body_length: 564
repeated_template_status: boot-event-core/shared-slot-0x0F/shared-slot-template
body_sha1: 7b2509b86cd4228c2a06efbdcabe5b3b660fba4d
body_prefix_hex: 5a045c0b02434f52
body_suffix_hex: 6f756e746572007a
- slot: 0x10
event_name_hint: leaveFastArea
raw_event_entry_word: 0x003b
raw_code_offset: 0x0000045c
derived_body_start: 0x052f
derived_body_end: 0x056a
derived_body_length: 59
repeated_template_status: boot-event-core/shared-slot-0x10/same-length-template
body_sha1: c0958d58cd7492fdc4b809db0634325f70fea009
body_prefix_hex: 5a005c2700434f52
body_suffix_hex: 666572656e74007a
```
## REE_BOOT
```yaml
class:
entry_index: 0x11B
class_id: 0x4DB
class_name: REE_BOOT
class_object_index: 0x4DD
raw_code_base_u32: 0xD4
code_base_minus_one: 0xD3
conservative_event_count: 32
events:
- slot: 0x0a
event_name_hint: equip
raw_event_entry_word: 0x034b
raw_code_offset: 0x00000001
derived_body_start: 0x00d4
derived_body_end: 0x041f
derived_body_length: 843
repeated_template_status: boot-event-core/shared-slot-0x0A/shared-slot-template
body_sha1: bae629a3de3884d6919863daab5fe25dfc24cf13
body_prefix_hex: 5a025c2103524545
body_suffix_hex: 6f756e746572007a
- slot: 0x0f
event_name_hint: enterFastArea
raw_event_entry_word: 0x025c
raw_code_offset: 0x0000034c
derived_body_start: 0x041f
derived_body_end: 0x067b
derived_body_length: 604
repeated_template_status: boot-event-core/shared-slot-0x0F/shared-slot-template
body_sha1: fb0e1e9e0a7b508f635df648aa8d3e3c72b6d0a2
body_prefix_hex: 5a045c3302524545
body_suffix_hex: 6f756e746572007a
- slot: 0x10
event_name_hint: leaveFastArea
raw_event_entry_word: 0x003b
raw_code_offset: 0x000005a8
derived_body_start: 0x067b
derived_body_end: 0x06b6
derived_body_length: 59
repeated_template_status: boot-event-core/shared-slot-0x10/same-length-template
body_sha1: 577c61e9c4c6fb3e8b38f1e998699184b8e6e4f5
body_prefix_hex: 5a005c2700524545
body_suffix_hex: 666572656e74007a
```
## VAR_BOOT
```yaml
class:
entry_index: 0x0FC
class_id: 0x45C
class_name: VAR_BOOT
class_object_index: 0x45E
raw_code_base_u32: 0xD4
code_base_minus_one: 0xD3
conservative_event_count: 32
events:
- slot: 0x0a
event_name_hint: equip
raw_event_entry_word: 0x029a
raw_code_offset: 0x00000001
derived_body_start: 0x00d4
derived_body_end: 0x036e
derived_body_length: 666
repeated_template_status: boot-event-core/shared-slot-0x0A/shared-slot-template
body_sha1: edc529e375d63cc79454b66c00acef51c5a0bd8a
body_prefix_hex: 5a025c7002564152
body_suffix_hex: 6f756e746572007a
- slot: 0x0f
event_name_hint: enterFastArea
raw_event_entry_word: 0x0244
raw_code_offset: 0x0000029b
derived_body_start: 0x036e
derived_body_end: 0x05b2
derived_body_length: 580
repeated_template_status: boot-event-core/shared-slot-0x0F/shared-slot-template
body_sha1: 6cdb54664e36def9bb6770e19632cdccbdf280a1
body_prefix_hex: 5a045c1b02564152
body_suffix_hex: 6f756e746572007a
- slot: 0x10
event_name_hint: leaveFastArea
raw_event_entry_word: 0x003b
raw_code_offset: 0x000004df
derived_body_start: 0x05b2
derived_body_end: 0x05ed
derived_body_length: 59
repeated_template_status: boot-event-core/shared-slot-0x10/same-length-template
body_sha1: 83c328a3cadc280a1798c2362f8d1e9ccbe3f78e
body_prefix_hex: 5a005c2700564152
body_suffix_hex: 666572656e74007a
```

View file

@ -0,0 +1,16 @@
entry_index class_id class_name slot event_name_hint raw_event_entry_word raw_code_offset derived_body_start derived_body_end derived_body_length repeated_template_status body_sha1 body_prefix_hex body_suffix_hex
171 0x314 AND_BOOT 0x0A equip 0x0253 0x00000001 0x00D4 0x0327 595 boot-event-core/shared-slot-0x0A/shared-slot-template d7a28ffc24ab488970328a576f16af20f76d2a71 5a025c2902414e44 6f756e746572007a
171 0x314 AND_BOOT 0x0F enterFastArea 0x0237 0x00000254 0x0327 0x055E 567 boot-event-core/shared-slot-0x0F/shared-slot-template 29fa661d1f7934f505c9efa6e7ec0bcbb7506b77 5a045c0e02414e44 6f756e746572007a
171 0x314 AND_BOOT 0x10 leaveFastArea 0x003B 0x0000048B 0x055E 0x0599 59 boot-event-core/shared-slot-0x10/same-length-template 7e3cc8034632df1963951b6c26e8cef2f18e2616 5a005c2700414e44 666572656e74007a
172 0x316 BRO_BOOT 0x0A equip 0x02D5 0x00000001 0x00D4 0x03A9 725 boot-event-core/shared-slot-0x0A/shared-slot-template bd08c6f4b1201d500ee2722b9cd4a2e3eb89af5f 5a025cab0242524f 6f756e746572007a
172 0x316 BRO_BOOT 0x0F enterFastArea 0x024C 0x000002D6 0x03A9 0x05F5 588 boot-event-core/shared-slot-0x0F/shared-slot-template 8b22769c8386fb4f8592aaf958820a452679a6b0 5a045c230242524f 6f756e746572007a
172 0x316 BRO_BOOT 0x10 leaveFastArea 0x003B 0x00000522 0x05F5 0x0630 59 boot-event-core/shared-slot-0x10/same-length-template dd5ecced3b31dda2e5cd6a8d8cb2e9df41669ebd 5a005c270042524f 666572656e74007a
189 0x360 COR_BOOT 0x0A equip 0x0227 0x00000001 0x00D4 0x02FB 551 boot-event-core/shared-slot-0x0A/shared-slot-template 97f1df8d8e6c9c7e151904d9ac1296f27d93581d 5a025cfd01434f52 6f756e746572007a
189 0x360 COR_BOOT 0x0F enterFastArea 0x0234 0x00000228 0x02FB 0x052F 564 boot-event-core/shared-slot-0x0F/shared-slot-template 7b2509b86cd4228c2a06efbdcabe5b3b660fba4d 5a045c0b02434f52 6f756e746572007a
189 0x360 COR_BOOT 0x10 leaveFastArea 0x003B 0x0000045C 0x052F 0x056A 59 boot-event-core/shared-slot-0x10/same-length-template c0958d58cd7492fdc4b809db0634325f70fea009 5a005c2700434f52 666572656e74007a
283 0x4DB REE_BOOT 0x0A equip 0x034B 0x00000001 0x00D4 0x041F 843 boot-event-core/shared-slot-0x0A/shared-slot-template bae629a3de3884d6919863daab5fe25dfc24cf13 5a025c2103524545 6f756e746572007a
283 0x4DB REE_BOOT 0x0F enterFastArea 0x025C 0x0000034C 0x041F 0x067B 604 boot-event-core/shared-slot-0x0F/shared-slot-template fb0e1e9e0a7b508f635df648aa8d3e3c72b6d0a2 5a045c3302524545 6f756e746572007a
283 0x4DB REE_BOOT 0x10 leaveFastArea 0x003B 0x000005A8 0x067B 0x06B6 59 boot-event-core/shared-slot-0x10/same-length-template 577c61e9c4c6fb3e8b38f1e998699184b8e6e4f5 5a005c2700524545 666572656e74007a
252 0x45C VAR_BOOT 0x0A equip 0x029A 0x00000001 0x00D4 0x036E 666 boot-event-core/shared-slot-0x0A/shared-slot-template edc529e375d63cc79454b66c00acef51c5a0bd8a 5a025c7002564152 6f756e746572007a
252 0x45C VAR_BOOT 0x0F enterFastArea 0x0244 0x0000029B 0x036E 0x05B2 580 boot-event-core/shared-slot-0x0F/shared-slot-template 6cdb54664e36def9bb6770e19632cdccbdf280a1 5a045c1b02564152 6f756e746572007a
252 0x45C VAR_BOOT 0x10 leaveFastArea 0x003B 0x000004DF 0x05B2 0x05ED 59 boot-event-core/shared-slot-0x10/same-length-template 83c328a3cadc280a1798c2362f8d1e9ccbe3f78e 5a005c2700564152 666572656e74007a
1 entry_index class_id class_name slot event_name_hint raw_event_entry_word raw_code_offset derived_body_start derived_body_end derived_body_length repeated_template_status body_sha1 body_prefix_hex body_suffix_hex
2 171 0x314 AND_BOOT 0x0A equip 0x0253 0x00000001 0x00D4 0x0327 595 boot-event-core/shared-slot-0x0A/shared-slot-template d7a28ffc24ab488970328a576f16af20f76d2a71 5a025c2902414e44 6f756e746572007a
3 171 0x314 AND_BOOT 0x0F enterFastArea 0x0237 0x00000254 0x0327 0x055E 567 boot-event-core/shared-slot-0x0F/shared-slot-template 29fa661d1f7934f505c9efa6e7ec0bcbb7506b77 5a045c0e02414e44 6f756e746572007a
4 171 0x314 AND_BOOT 0x10 leaveFastArea 0x003B 0x0000048B 0x055E 0x0599 59 boot-event-core/shared-slot-0x10/same-length-template 7e3cc8034632df1963951b6c26e8cef2f18e2616 5a005c2700414e44 666572656e74007a
5 172 0x316 BRO_BOOT 0x0A equip 0x02D5 0x00000001 0x00D4 0x03A9 725 boot-event-core/shared-slot-0x0A/shared-slot-template bd08c6f4b1201d500ee2722b9cd4a2e3eb89af5f 5a025cab0242524f 6f756e746572007a
6 172 0x316 BRO_BOOT 0x0F enterFastArea 0x024C 0x000002D6 0x03A9 0x05F5 588 boot-event-core/shared-slot-0x0F/shared-slot-template 8b22769c8386fb4f8592aaf958820a452679a6b0 5a045c230242524f 6f756e746572007a
7 172 0x316 BRO_BOOT 0x10 leaveFastArea 0x003B 0x00000522 0x05F5 0x0630 59 boot-event-core/shared-slot-0x10/same-length-template dd5ecced3b31dda2e5cd6a8d8cb2e9df41669ebd 5a005c270042524f 666572656e74007a
8 189 0x360 COR_BOOT 0x0A equip 0x0227 0x00000001 0x00D4 0x02FB 551 boot-event-core/shared-slot-0x0A/shared-slot-template 97f1df8d8e6c9c7e151904d9ac1296f27d93581d 5a025cfd01434f52 6f756e746572007a
9 189 0x360 COR_BOOT 0x0F enterFastArea 0x0234 0x00000228 0x02FB 0x052F 564 boot-event-core/shared-slot-0x0F/shared-slot-template 7b2509b86cd4228c2a06efbdcabe5b3b660fba4d 5a045c0b02434f52 6f756e746572007a
10 189 0x360 COR_BOOT 0x10 leaveFastArea 0x003B 0x0000045C 0x052F 0x056A 59 boot-event-core/shared-slot-0x10/same-length-template c0958d58cd7492fdc4b809db0634325f70fea009 5a005c2700434f52 666572656e74007a
11 283 0x4DB REE_BOOT 0x0A equip 0x034B 0x00000001 0x00D4 0x041F 843 boot-event-core/shared-slot-0x0A/shared-slot-template bae629a3de3884d6919863daab5fe25dfc24cf13 5a025c2103524545 6f756e746572007a
12 283 0x4DB REE_BOOT 0x0F enterFastArea 0x025C 0x0000034C 0x041F 0x067B 604 boot-event-core/shared-slot-0x0F/shared-slot-template fb0e1e9e0a7b508f635df648aa8d3e3c72b6d0a2 5a045c3302524545 6f756e746572007a
13 283 0x4DB REE_BOOT 0x10 leaveFastArea 0x003B 0x000005A8 0x067B 0x06B6 59 boot-event-core/shared-slot-0x10/same-length-template 577c61e9c4c6fb3e8b38f1e998699184b8e6e4f5 5a005c2700524545 666572656e74007a
14 252 0x45C VAR_BOOT 0x0A equip 0x029A 0x00000001 0x00D4 0x036E 666 boot-event-core/shared-slot-0x0A/shared-slot-template edc529e375d63cc79454b66c00acef51c5a0bd8a 5a025c7002564152 6f756e746572007a
15 252 0x45C VAR_BOOT 0x0F enterFastArea 0x0244 0x0000029B 0x036E 0x05B2 580 boot-event-core/shared-slot-0x0F/shared-slot-template 6cdb54664e36def9bb6770e19632cdccbdf280a1 5a045c1b02564152 6f756e746572007a
16 252 0x45C VAR_BOOT 0x10 leaveFastArea 0x003B 0x000004DF 0x05B2 0x05ED 59 boot-event-core/shared-slot-0x10/same-length-template 83c328a3cadc280a1798c2362f8d1e9ccbe3f78e 5a005c2700564152 666572656e74007a

View file

@ -0,0 +1,142 @@
# SURCAM Callback Family Decompiled Event Sketches
This is a reversible per-class rendering derived directly from `class_event_index.tsv` plus the raw extracted chunk bytes.
ScummVM event labels remain hints only; the authoritative data here is the slot id, raw row bytes, and derived body window.
## SURCAMEW
```yaml
class:
entry_index: 0x11C
class_id: 0x4DE
class_name: SURCAMEW
class_object_index: 0x4E0
raw_code_base_u32: 0xE6
code_base_minus_one: 0xE5
conservative_event_count: 35
events:
- slot: 0x01
event_name_hint: use
raw_event_entry_word: 0x00f7
raw_code_offset: 0x000000d2
derived_body_start: 0x01b7
derived_body_end: 0x02ae
derived_body_length: 247
repeated_template_status: callback-eventtrigger/shared-slot-0x01/shared-slot-template
body_sha1: a132370f9360cae36a81dd0372108c555a964d88
body_prefix_hex: 5a005ce300535552
body_suffix_hex: 666572656e74007a
- slot: 0x0a
event_name_hint: equip
raw_event_entry_word: 0x00d1
raw_code_offset: 0x00000001
derived_body_start: 0x00e6
derived_body_end: 0x01b7
derived_body_length: 209
repeated_template_status: callback-eventtrigger/shared-slot-0x0A/same-length-template
body_sha1: 61ffc6347df026ded22dfebd2afe55826f1e9ad2
body_prefix_hex: 5a005cb500535552
body_suffix_hex: 690a00766172007a
- slot: 0x20
event_name_hint:
raw_event_entry_word: 0x02ba
raw_code_offset: 0x000001c9
derived_body_start: 0x02ae
derived_body_end: 0x0568
derived_body_length: 698
repeated_template_status: callback-eventtrigger/shared-slot-0x20/same-length-template
body_sha1: 155c3cf663c03a6f53846938ac7c289aeb3c4c26
body_prefix_hex: 5a0b5c6302535552
body_suffix_hex: f500636f6465007a
- slot: 0x21
event_name_hint:
raw_event_entry_word: 0x0655
raw_code_offset: 0x00000483
derived_body_start: 0x0568
derived_body_end: 0x0bbd
derived_body_length: 1621
repeated_template_status: callback-eventtrigger/shared-slot-0x21/shared-slot-template
body_sha1: dd8da26eae780920efc8ae8c51db5e9e8151914c
body_prefix_hex: 5a145ce205535552
body_suffix_hex: 000062ec007a007a
- slot: 0x22
event_name_hint:
raw_event_entry_word: 0x01a3
raw_code_offset: 0x00000ad8
derived_body_start: 0x0bbd
derived_body_end: 0x0d60
derived_body_length: 419
repeated_template_status: callback-eventtrigger/shared-slot-0x22/same-length-template
body_sha1: 0dd40a9416581d71aed72d5cdb63656468f50d43
body_prefix_hex: 5a035c6b01535552
body_suffix_hex: 756e6447756e007a
```
## SURCAMNS
```yaml
class:
entry_index: 0x10D
class_id: 0x4C6
class_name: SURCAMNS
class_object_index: 0x4C8
raw_code_base_u32: 0xE6
code_base_minus_one: 0xE5
conservative_event_count: 35
events:
- slot: 0x01
event_name_hint: use
raw_event_entry_word: 0x0051
raw_code_offset: 0x000000d2
derived_body_start: 0x01b7
derived_body_end: 0x0208
derived_body_length: 81
repeated_template_status: callback-eventtrigger/shared-slot-0x01/shared-slot-template
body_sha1: af6e6f93e4879920b189bfdeede69bb18e3307d5
body_prefix_hex: 5a005c3d00535552
body_suffix_hex: 666572656e74007a
- slot: 0x0a
event_name_hint: equip
raw_event_entry_word: 0x00d1
raw_code_offset: 0x00000001
derived_body_start: 0x00e6
derived_body_end: 0x01b7
derived_body_length: 209
repeated_template_status: callback-eventtrigger/shared-slot-0x0A/same-length-template
body_sha1: bb2bc85fb9064de32bb1d2807ab41d0634fba228
body_prefix_hex: 5a005cb500535552
body_suffix_hex: 690a00766172007a
- slot: 0x20
event_name_hint:
raw_event_entry_word: 0x02ba
raw_code_offset: 0x00000123
derived_body_start: 0x0208
derived_body_end: 0x04c2
derived_body_length: 698
repeated_template_status: callback-eventtrigger/shared-slot-0x20/same-length-template
body_sha1: 137f2bb8750946fa2c84750edcc6866fb77b2874
body_prefix_hex: 5a0b5c6302535552
body_suffix_hex: f500636f6465007a
- slot: 0x21
event_name_hint:
raw_event_entry_word: 0x0709
raw_code_offset: 0x000003dd
derived_body_start: 0x04c2
derived_body_end: 0x0bcb
derived_body_length: 1801
repeated_template_status: callback-eventtrigger/shared-slot-0x21/shared-slot-template
body_sha1: 215c83fb3e76bf447b8768b537edfb99f58e600b
body_prefix_hex: 5a145c9606535552
body_suffix_hex: 000062ec007a007a
- slot: 0x22
event_name_hint:
raw_event_entry_word: 0x01a3
raw_code_offset: 0x00000ae6
derived_body_start: 0x0bcb
derived_body_end: 0x0d6e
derived_body_length: 419
repeated_template_status: callback-eventtrigger/shared-slot-0x22/same-length-template
body_sha1: e01ce4b7741b642ddc4ebd220aafe847bd07300b
body_prefix_hex: 5a035c6b01535552
body_suffix_hex: 756e6447756e007a
```

View file

@ -0,0 +1,11 @@
entry_index class_id class_name slot event_name_hint raw_event_entry_word raw_code_offset derived_body_start derived_body_end derived_body_length repeated_template_status body_sha1 body_prefix_hex body_suffix_hex
284 0x4DE SURCAMEW 0x01 use 0x00F7 0x000000D2 0x01B7 0x02AE 247 callback-eventtrigger/shared-slot-0x01/shared-slot-template a132370f9360cae36a81dd0372108c555a964d88 5a005ce300535552 666572656e74007a
284 0x4DE SURCAMEW 0x0A equip 0x00D1 0x00000001 0x00E6 0x01B7 209 callback-eventtrigger/shared-slot-0x0A/same-length-template 61ffc6347df026ded22dfebd2afe55826f1e9ad2 5a005cb500535552 690a00766172007a
284 0x4DE SURCAMEW 0x20 0x02BA 0x000001C9 0x02AE 0x0568 698 callback-eventtrigger/shared-slot-0x20/same-length-template 155c3cf663c03a6f53846938ac7c289aeb3c4c26 5a0b5c6302535552 f500636f6465007a
284 0x4DE SURCAMEW 0x21 0x0655 0x00000483 0x0568 0x0BBD 1621 callback-eventtrigger/shared-slot-0x21/shared-slot-template dd8da26eae780920efc8ae8c51db5e9e8151914c 5a145ce205535552 000062ec007a007a
284 0x4DE SURCAMEW 0x22 0x01A3 0x00000AD8 0x0BBD 0x0D60 419 callback-eventtrigger/shared-slot-0x22/same-length-template 0dd40a9416581d71aed72d5cdb63656468f50d43 5a035c6b01535552 756e6447756e007a
269 0x4C6 SURCAMNS 0x01 use 0x0051 0x000000D2 0x01B7 0x0208 81 callback-eventtrigger/shared-slot-0x01/shared-slot-template af6e6f93e4879920b189bfdeede69bb18e3307d5 5a005c3d00535552 666572656e74007a
269 0x4C6 SURCAMNS 0x0A equip 0x00D1 0x00000001 0x00E6 0x01B7 209 callback-eventtrigger/shared-slot-0x0A/same-length-template bb2bc85fb9064de32bb1d2807ab41d0634fba228 5a005cb500535552 690a00766172007a
269 0x4C6 SURCAMNS 0x20 0x02BA 0x00000123 0x0208 0x04C2 698 callback-eventtrigger/shared-slot-0x20/same-length-template 137f2bb8750946fa2c84750edcc6866fb77b2874 5a0b5c6302535552 f500636f6465007a
269 0x4C6 SURCAMNS 0x21 0x0709 0x000003DD 0x04C2 0x0BCB 1801 callback-eventtrigger/shared-slot-0x21/shared-slot-template 215c83fb3e76bf447b8768b537edfb99f58e600b 5a145c9606535552 000062ec007a007a
269 0x4C6 SURCAMNS 0x22 0x01A3 0x00000AE6 0x0BCB 0x0D6E 419 callback-eventtrigger/shared-slot-0x22/same-length-template e01ce4b7741b642ddc4ebd220aafe847bd07300b 5a035c6b01535552 756e6447756e007a
1 entry_index class_id class_name slot event_name_hint raw_event_entry_word raw_code_offset derived_body_start derived_body_end derived_body_length repeated_template_status body_sha1 body_prefix_hex body_suffix_hex
2 284 0x4DE SURCAMEW 0x01 use 0x00F7 0x000000D2 0x01B7 0x02AE 247 callback-eventtrigger/shared-slot-0x01/shared-slot-template a132370f9360cae36a81dd0372108c555a964d88 5a005ce300535552 666572656e74007a
3 284 0x4DE SURCAMEW 0x0A equip 0x00D1 0x00000001 0x00E6 0x01B7 209 callback-eventtrigger/shared-slot-0x0A/same-length-template 61ffc6347df026ded22dfebd2afe55826f1e9ad2 5a005cb500535552 690a00766172007a
4 284 0x4DE SURCAMEW 0x20 0x02BA 0x000001C9 0x02AE 0x0568 698 callback-eventtrigger/shared-slot-0x20/same-length-template 155c3cf663c03a6f53846938ac7c289aeb3c4c26 5a0b5c6302535552 f500636f6465007a
5 284 0x4DE SURCAMEW 0x21 0x0655 0x00000483 0x0568 0x0BBD 1621 callback-eventtrigger/shared-slot-0x21/shared-slot-template dd8da26eae780920efc8ae8c51db5e9e8151914c 5a145ce205535552 000062ec007a007a
6 284 0x4DE SURCAMEW 0x22 0x01A3 0x00000AD8 0x0BBD 0x0D60 419 callback-eventtrigger/shared-slot-0x22/same-length-template 0dd40a9416581d71aed72d5cdb63656468f50d43 5a035c6b01535552 756e6447756e007a
7 269 0x4C6 SURCAMNS 0x01 use 0x0051 0x000000D2 0x01B7 0x0208 81 callback-eventtrigger/shared-slot-0x01/shared-slot-template af6e6f93e4879920b189bfdeede69bb18e3307d5 5a005c3d00535552 666572656e74007a
8 269 0x4C6 SURCAMNS 0x0A equip 0x00D1 0x00000001 0x00E6 0x01B7 209 callback-eventtrigger/shared-slot-0x0A/same-length-template bb2bc85fb9064de32bb1d2807ab41d0634fba228 5a005cb500535552 690a00766172007a
9 269 0x4C6 SURCAMNS 0x20 0x02BA 0x00000123 0x0208 0x04C2 698 callback-eventtrigger/shared-slot-0x20/same-length-template 137f2bb8750946fa2c84750edcc6866fb77b2874 5a0b5c6302535552 f500636f6465007a
10 269 0x4C6 SURCAMNS 0x21 0x0709 0x000003DD 0x04C2 0x0BCB 1801 callback-eventtrigger/shared-slot-0x21/shared-slot-template 215c83fb3e76bf447b8768b537edfb99f58e600b 5a145c9606535552 000062ec007a007a
11 269 0x4C6 SURCAMNS 0x22 0x01A3 0x00000AE6 0x0BCB 0x0D6E 419 callback-eventtrigger/shared-slot-0x22/same-length-template e01ce4b7741b642ddc4ebd220aafe847bd07300b 5a035c6b01535552 756e6447756e007a

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,145 @@
# Environmental Family Decompiled Event Sketches
This is a reversible per-class rendering derived directly from `class_event_index.tsv` plus the raw extracted chunk bytes.
ScummVM event labels remain hints only; the authoritative data here is the slot id, raw row bytes, and derived body window.
## FLAMEBOX
```yaml
class:
entry_index: 0x0E5
class_id: 0x403
class_name: FLAMEBOX
class_object_index: 0x405
raw_code_base_u32: 0xE0
code_base_minus_one: 0xDF
conservative_event_count: 34
events:
- slot: 0x0a
event_name_hint: equip
raw_event_entry_word: 0x026a
raw_code_offset: 0x00000001
derived_body_start: 0x00e0
derived_body_end: 0x034a
derived_body_length: 618
repeated_template_status: environmental-event/shared-slot-0x0A/shared-slot-template
body_sha1: ae2a680ccf984b74404e7c105e0b2f9331e58a11
body_prefix_hex: 5a065c2902464c41
body_suffix_hex: 656374696f6e007a
- slot: 0x20
event_name_hint:
raw_event_entry_word: 0x01ac
raw_code_offset: 0x0000026b
derived_body_start: 0x034a
derived_body_end: 0x04f6
derived_body_length: 428
repeated_template_status: environmental-event/shared-slot-0x20/shared-slot-template
body_sha1: 73ea45e739673c86eb92fe6a328a1a8442da767d
body_prefix_hex: 5a045c6b01464c41
body_suffix_hex: 666c616d6532007a
- slot: 0x21
event_name_hint:
raw_event_entry_word: 0x029a
raw_code_offset: 0x00000417
derived_body_start: 0x04f6
derived_body_end: 0x0790
derived_body_length: 666
repeated_template_status: environmental-event/shared-slot-0x21/shared-slot-template
body_sha1: 8bdf9b3c2792514b300c2a4768f21e525313e86d
body_prefix_hex: 5a065c4d02464c41
body_suffix_hex: 657754797065007a
```
## NOSTRIL
```yaml
class:
entry_index: 0x0ED
class_id: 0x43E
class_name: NOSTRIL
class_object_index: 0x440
raw_code_base_u32: 0xE0
code_base_minus_one: 0xDF
conservative_event_count: 34
events:
- slot: 0x0a
event_name_hint: equip
raw_event_entry_word: 0x00c0
raw_code_offset: 0x00000001
derived_body_start: 0x00e0
derived_body_end: 0x01a0
derived_body_length: 192
repeated_template_status: environmental-event/shared-slot-0x0A/shared-slot-template
body_sha1: a9d5700c4ff677d813fffdd67444ebd4ac6f4b19
body_prefix_hex: 5a025c99004e4f53
body_suffix_hex: fe0266697265007a
- slot: 0x20
event_name_hint:
raw_event_entry_word: 0x0129
raw_code_offset: 0x000000c1
derived_body_start: 0x01a0
derived_body_end: 0x02c9
derived_body_length: 297
repeated_template_status: environmental-event/shared-slot-0x20/shared-slot-template
body_sha1: fd7fca65f43267c20931c91f1db269cccde92947
body_prefix_hex: 5a045cf8004e4f53
body_suffix_hex: 026669726532007a
- slot: 0x21
event_name_hint:
raw_event_entry_word: 0x01be
raw_code_offset: 0x000001ea
derived_body_start: 0x02c9
derived_body_end: 0x0487
derived_body_length: 446
repeated_template_status: environmental-event/shared-slot-0x21/shared-slot-template
body_sha1: e3257250cfa12e430d872bd0ecc03e7e8d41a63d
body_prefix_hex: 5a065c8d014e4f53
body_suffix_hex: 00636f756e74007a
```
## STEAMBOX
```yaml
class:
entry_index: 0x128
class_id: 0x500
class_name: STEAMBOX
class_object_index: 0x502
raw_code_base_u32: 0xE0
code_base_minus_one: 0xDF
conservative_event_count: 34
events:
- slot: 0x0a
event_name_hint: equip
raw_event_entry_word: 0x0266
raw_code_offset: 0x00000001
derived_body_start: 0x00e0
derived_body_end: 0x0346
derived_body_length: 614
repeated_template_status: environmental-event/shared-slot-0x0A/shared-slot-template
body_sha1: 0c7a45d14c66b5b6d8611f3eea647657e982e8c9
body_prefix_hex: 5a065c2502535445
body_suffix_hex: 656374696f6e007a
- slot: 0x20
event_name_hint:
raw_event_entry_word: 0x01f6
raw_code_offset: 0x00000267
derived_body_start: 0x0346
derived_body_end: 0x053c
derived_body_length: 502
repeated_template_status: environmental-event/shared-slot-0x20/shared-slot-template
body_sha1: b42657cfea765abc07c9b0e99020d7c8783f06ef
body_prefix_hex: 5a045cb501535445
body_suffix_hex: 737465616d32007a
- slot: 0x21
event_name_hint:
raw_event_entry_word: 0x02a7
raw_code_offset: 0x0000045d
derived_body_start: 0x053c
derived_body_end: 0x07e3
derived_body_length: 679
repeated_template_status: environmental-event/shared-slot-0x21/shared-slot-template
body_sha1: a7cf9924083dbcb16dbb2372a6c4f8ffeec578db
body_prefix_hex: 5a045c6602535445
body_suffix_hex: 737465616d32007a
```

View file

@ -0,0 +1,10 @@
entry_index class_id class_name slot event_name_hint raw_event_entry_word raw_code_offset derived_body_start derived_body_end derived_body_length repeated_template_status body_sha1 body_prefix_hex body_suffix_hex
229 0x403 FLAMEBOX 0x0A equip 0x026A 0x00000001 0x00E0 0x034A 618 environmental-event/shared-slot-0x0A/shared-slot-template ae2a680ccf984b74404e7c105e0b2f9331e58a11 5a065c2902464c41 656374696f6e007a
229 0x403 FLAMEBOX 0x20 0x01AC 0x0000026B 0x034A 0x04F6 428 environmental-event/shared-slot-0x20/shared-slot-template 73ea45e739673c86eb92fe6a328a1a8442da767d 5a045c6b01464c41 666c616d6532007a
229 0x403 FLAMEBOX 0x21 0x029A 0x00000417 0x04F6 0x0790 666 environmental-event/shared-slot-0x21/shared-slot-template 8bdf9b3c2792514b300c2a4768f21e525313e86d 5a065c4d02464c41 657754797065007a
237 0x43E NOSTRIL 0x0A equip 0x00C0 0x00000001 0x00E0 0x01A0 192 environmental-event/shared-slot-0x0A/shared-slot-template a9d5700c4ff677d813fffdd67444ebd4ac6f4b19 5a025c99004e4f53 fe0266697265007a
237 0x43E NOSTRIL 0x20 0x0129 0x000000C1 0x01A0 0x02C9 297 environmental-event/shared-slot-0x20/shared-slot-template fd7fca65f43267c20931c91f1db269cccde92947 5a045cf8004e4f53 026669726532007a
237 0x43E NOSTRIL 0x21 0x01BE 0x000001EA 0x02C9 0x0487 446 environmental-event/shared-slot-0x21/shared-slot-template e3257250cfa12e430d872bd0ecc03e7e8d41a63d 5a065c8d014e4f53 00636f756e74007a
296 0x500 STEAMBOX 0x0A equip 0x0266 0x00000001 0x00E0 0x0346 614 environmental-event/shared-slot-0x0A/shared-slot-template 0c7a45d14c66b5b6d8611f3eea647657e982e8c9 5a065c2502535445 656374696f6e007a
296 0x500 STEAMBOX 0x20 0x01F6 0x00000267 0x0346 0x053C 502 environmental-event/shared-slot-0x20/shared-slot-template b42657cfea765abc07c9b0e99020d7c8783f06ef 5a045cb501535445 737465616d32007a
296 0x500 STEAMBOX 0x21 0x02A7 0x0000045D 0x053C 0x07E3 679 environmental-event/shared-slot-0x21/shared-slot-template a7cf9924083dbcb16dbb2372a6c4f8ffeec578db 5a045c6602535445 737465616d32007a
1 entry_index class_id class_name slot event_name_hint raw_event_entry_word raw_code_offset derived_body_start derived_body_end derived_body_length repeated_template_status body_sha1 body_prefix_hex body_suffix_hex
2 229 0x403 FLAMEBOX 0x0A equip 0x026A 0x00000001 0x00E0 0x034A 618 environmental-event/shared-slot-0x0A/shared-slot-template ae2a680ccf984b74404e7c105e0b2f9331e58a11 5a065c2902464c41 656374696f6e007a
3 229 0x403 FLAMEBOX 0x20 0x01AC 0x0000026B 0x034A 0x04F6 428 environmental-event/shared-slot-0x20/shared-slot-template 73ea45e739673c86eb92fe6a328a1a8442da767d 5a045c6b01464c41 666c616d6532007a
4 229 0x403 FLAMEBOX 0x21 0x029A 0x00000417 0x04F6 0x0790 666 environmental-event/shared-slot-0x21/shared-slot-template 8bdf9b3c2792514b300c2a4768f21e525313e86d 5a065c4d02464c41 657754797065007a
5 237 0x43E NOSTRIL 0x0A equip 0x00C0 0x00000001 0x00E0 0x01A0 192 environmental-event/shared-slot-0x0A/shared-slot-template a9d5700c4ff677d813fffdd67444ebd4ac6f4b19 5a025c99004e4f53 fe0266697265007a
6 237 0x43E NOSTRIL 0x20 0x0129 0x000000C1 0x01A0 0x02C9 297 environmental-event/shared-slot-0x20/shared-slot-template fd7fca65f43267c20931c91f1db269cccde92947 5a045cf8004e4f53 026669726532007a
7 237 0x43E NOSTRIL 0x21 0x01BE 0x000001EA 0x02C9 0x0487 446 environmental-event/shared-slot-0x21/shared-slot-template e3257250cfa12e430d872bd0ecc03e7e8d41a63d 5a065c8d014e4f53 00636f756e74007a
8 296 0x500 STEAMBOX 0x0A equip 0x0266 0x00000001 0x00E0 0x0346 614 environmental-event/shared-slot-0x0A/shared-slot-template 0c7a45d14c66b5b6d8611f3eea647657e982e8c9 5a065c2502535445 656374696f6e007a
9 296 0x500 STEAMBOX 0x20 0x01F6 0x00000267 0x0346 0x053C 502 environmental-event/shared-slot-0x20/shared-slot-template b42657cfea765abc07c9b0e99020d7c8783f06ef 5a045cb501535445 737465616d32007a
10 296 0x500 STEAMBOX 0x21 0x02A7 0x0000045D 0x053C 0x07E3 679 environmental-event/shared-slot-0x21/shared-slot-template a7cf9924083dbcb16dbb2372a6c4f8ffeec578db 5a045c6602535445 737465616d32007a

View file

@ -0,0 +1,49 @@
record_type class_name slot expected actual status
slot-set AND_BOOT * 0x0A,0x0F,0x10 0x0A,0x0F,0x10 ok
slot-set BRO_BOOT * 0x0A,0x0F,0x10 0x0A,0x0F,0x10 ok
slot-set COR_BOOT * 0x0A,0x0F,0x10 0x0A,0x0F,0x10 ok
slot-set FLAMEBOX * 0x0A,0x20,0x21 0x0A,0x20,0x21 ok
slot-set JELYH2 * 0x01 0x01 ok
slot-set JELYHACK * 0x01 0x01 ok
slot-set NOSTRIL * 0x0A,0x20,0x21 0x0A,0x20,0x21 ok
slot-set REE_BOOT * 0x0A,0x0F,0x10 0x0A,0x0F,0x10 ok
slot-set STEAMBOX * 0x0A,0x20,0x21 0x0A,0x20,0x21 ok
slot-set SURCAMEW * 0x01,0x0A,0x20,0x21,0x22 0x01,0x0A,0x20,0x21,0x22 ok
slot-set SURCAMNS * 0x01,0x0A,0x20,0x21,0x22 0x01,0x0A,0x20,0x21,0x22 ok
slot-set VAR_BOOT * 0x0A,0x0F,0x10 0x0A,0x0F,0x10 ok
row JELYHACK 0x01 0x002A|0x00000001|0x00D4|0x00FE|42|referent-anchor-twin/shared-slot-0x01/same-length-template 0x002A|0x00000001|0x00D4|0x00FE|42|referent-anchor-twin/shared-slot-0x01/same-length-template ok
row JELYH2 0x01 0x002A|0x00000001|0x00D4|0x00FE|42|referent-anchor-twin/shared-slot-0x01/same-length-template 0x002A|0x00000001|0x00D4|0x00FE|42|referent-anchor-twin/shared-slot-0x01/same-length-template ok
row AND_BOOT 0x0A 0x0253|0x00000001|0x00D4|0x0327|595|boot-event-core/shared-slot-0x0A/shared-slot-template 0x0253|0x00000001|0x00D4|0x0327|595|boot-event-core/shared-slot-0x0A/shared-slot-template ok
row AND_BOOT 0x0F 0x0237|0x00000254|0x0327|0x055E|567|boot-event-core/shared-slot-0x0F/shared-slot-template 0x0237|0x00000254|0x0327|0x055E|567|boot-event-core/shared-slot-0x0F/shared-slot-template ok
row AND_BOOT 0x10 0x003B|0x0000048B|0x055E|0x0599|59|boot-event-core/shared-slot-0x10/same-length-template 0x003B|0x0000048B|0x055E|0x0599|59|boot-event-core/shared-slot-0x10/same-length-template ok
row BRO_BOOT 0x0A 0x02D5|0x00000001|0x00D4|0x03A9|725|boot-event-core/shared-slot-0x0A/shared-slot-template 0x02D5|0x00000001|0x00D4|0x03A9|725|boot-event-core/shared-slot-0x0A/shared-slot-template ok
row BRO_BOOT 0x0F 0x024C|0x000002D6|0x03A9|0x05F5|588|boot-event-core/shared-slot-0x0F/shared-slot-template 0x024C|0x000002D6|0x03A9|0x05F5|588|boot-event-core/shared-slot-0x0F/shared-slot-template ok
row BRO_BOOT 0x10 0x003B|0x00000522|0x05F5|0x0630|59|boot-event-core/shared-slot-0x10/same-length-template 0x003B|0x00000522|0x05F5|0x0630|59|boot-event-core/shared-slot-0x10/same-length-template ok
row COR_BOOT 0x0A 0x0227|0x00000001|0x00D4|0x02FB|551|boot-event-core/shared-slot-0x0A/shared-slot-template 0x0227|0x00000001|0x00D4|0x02FB|551|boot-event-core/shared-slot-0x0A/shared-slot-template ok
row COR_BOOT 0x0F 0x0234|0x00000228|0x02FB|0x052F|564|boot-event-core/shared-slot-0x0F/shared-slot-template 0x0234|0x00000228|0x02FB|0x052F|564|boot-event-core/shared-slot-0x0F/shared-slot-template ok
row COR_BOOT 0x10 0x003B|0x0000045C|0x052F|0x056A|59|boot-event-core/shared-slot-0x10/same-length-template 0x003B|0x0000045C|0x052F|0x056A|59|boot-event-core/shared-slot-0x10/same-length-template ok
row REE_BOOT 0x0A 0x034B|0x00000001|0x00D4|0x041F|843|boot-event-core/shared-slot-0x0A/shared-slot-template 0x034B|0x00000001|0x00D4|0x041F|843|boot-event-core/shared-slot-0x0A/shared-slot-template ok
row REE_BOOT 0x0F 0x025C|0x0000034C|0x041F|0x067B|604|boot-event-core/shared-slot-0x0F/shared-slot-template 0x025C|0x0000034C|0x041F|0x067B|604|boot-event-core/shared-slot-0x0F/shared-slot-template ok
row REE_BOOT 0x10 0x003B|0x000005A8|0x067B|0x06B6|59|boot-event-core/shared-slot-0x10/same-length-template 0x003B|0x000005A8|0x067B|0x06B6|59|boot-event-core/shared-slot-0x10/same-length-template ok
row VAR_BOOT 0x0A 0x029A|0x00000001|0x00D4|0x036E|666|boot-event-core/shared-slot-0x0A/shared-slot-template 0x029A|0x00000001|0x00D4|0x036E|666|boot-event-core/shared-slot-0x0A/shared-slot-template ok
row VAR_BOOT 0x0F 0x0244|0x0000029B|0x036E|0x05B2|580|boot-event-core/shared-slot-0x0F/shared-slot-template 0x0244|0x0000029B|0x036E|0x05B2|580|boot-event-core/shared-slot-0x0F/shared-slot-template ok
row VAR_BOOT 0x10 0x003B|0x000004DF|0x05B2|0x05ED|59|boot-event-core/shared-slot-0x10/same-length-template 0x003B|0x000004DF|0x05B2|0x05ED|59|boot-event-core/shared-slot-0x10/same-length-template ok
row SURCAMNS 0x01 0x0051|0x000000D2|0x01B7|0x0208|81|callback-eventtrigger/shared-slot-0x01/shared-slot-template 0x0051|0x000000D2|0x01B7|0x0208|81|callback-eventtrigger/shared-slot-0x01/shared-slot-template ok
row SURCAMNS 0x0A 0x00D1|0x00000001|0x00E6|0x01B7|209|callback-eventtrigger/shared-slot-0x0A/same-length-template 0x00D1|0x00000001|0x00E6|0x01B7|209|callback-eventtrigger/shared-slot-0x0A/same-length-template ok
row SURCAMNS 0x20 0x02BA|0x00000123|0x0208|0x04C2|698|callback-eventtrigger/shared-slot-0x20/same-length-template 0x02BA|0x00000123|0x0208|0x04C2|698|callback-eventtrigger/shared-slot-0x20/same-length-template ok
row SURCAMNS 0x21 0x0709|0x000003DD|0x04C2|0x0BCB|1801|callback-eventtrigger/shared-slot-0x21/shared-slot-template 0x0709|0x000003DD|0x04C2|0x0BCB|1801|callback-eventtrigger/shared-slot-0x21/shared-slot-template ok
row SURCAMNS 0x22 0x01A3|0x00000AE6|0x0BCB|0x0D6E|419|callback-eventtrigger/shared-slot-0x22/same-length-template 0x01A3|0x00000AE6|0x0BCB|0x0D6E|419|callback-eventtrigger/shared-slot-0x22/same-length-template ok
row SURCAMEW 0x01 0x00F7|0x000000D2|0x01B7|0x02AE|247|callback-eventtrigger/shared-slot-0x01/shared-slot-template 0x00F7|0x000000D2|0x01B7|0x02AE|247|callback-eventtrigger/shared-slot-0x01/shared-slot-template ok
row SURCAMEW 0x0A 0x00D1|0x00000001|0x00E6|0x01B7|209|callback-eventtrigger/shared-slot-0x0A/same-length-template 0x00D1|0x00000001|0x00E6|0x01B7|209|callback-eventtrigger/shared-slot-0x0A/same-length-template ok
row SURCAMEW 0x20 0x02BA|0x000001C9|0x02AE|0x0568|698|callback-eventtrigger/shared-slot-0x20/same-length-template 0x02BA|0x000001C9|0x02AE|0x0568|698|callback-eventtrigger/shared-slot-0x20/same-length-template ok
row SURCAMEW 0x21 0x0655|0x00000483|0x0568|0x0BBD|1621|callback-eventtrigger/shared-slot-0x21/shared-slot-template 0x0655|0x00000483|0x0568|0x0BBD|1621|callback-eventtrigger/shared-slot-0x21/shared-slot-template ok
row SURCAMEW 0x22 0x01A3|0x00000AD8|0x0BBD|0x0D60|419|callback-eventtrigger/shared-slot-0x22/same-length-template 0x01A3|0x00000AD8|0x0BBD|0x0D60|419|callback-eventtrigger/shared-slot-0x22/same-length-template ok
row FLAMEBOX 0x0A 0x026A|0x00000001|0x00E0|0x034A|618|environmental-event/shared-slot-0x0A/shared-slot-template 0x026A|0x00000001|0x00E0|0x034A|618|environmental-event/shared-slot-0x0A/shared-slot-template ok
row FLAMEBOX 0x20 0x01AC|0x0000026B|0x034A|0x04F6|428|environmental-event/shared-slot-0x20/shared-slot-template 0x01AC|0x0000026B|0x034A|0x04F6|428|environmental-event/shared-slot-0x20/shared-slot-template ok
row FLAMEBOX 0x21 0x029A|0x00000417|0x04F6|0x0790|666|environmental-event/shared-slot-0x21/shared-slot-template 0x029A|0x00000417|0x04F6|0x0790|666|environmental-event/shared-slot-0x21/shared-slot-template ok
row NOSTRIL 0x0A 0x00C0|0x00000001|0x00E0|0x01A0|192|environmental-event/shared-slot-0x0A/shared-slot-template 0x00C0|0x00000001|0x00E0|0x01A0|192|environmental-event/shared-slot-0x0A/shared-slot-template ok
row NOSTRIL 0x20 0x0129|0x000000C1|0x01A0|0x02C9|297|environmental-event/shared-slot-0x20/shared-slot-template 0x0129|0x000000C1|0x01A0|0x02C9|297|environmental-event/shared-slot-0x20/shared-slot-template ok
row NOSTRIL 0x21 0x01BE|0x000001EA|0x02C9|0x0487|446|environmental-event/shared-slot-0x21/shared-slot-template 0x01BE|0x000001EA|0x02C9|0x0487|446|environmental-event/shared-slot-0x21/shared-slot-template ok
row STEAMBOX 0x0A 0x0266|0x00000001|0x00E0|0x0346|614|environmental-event/shared-slot-0x0A/shared-slot-template 0x0266|0x00000001|0x00E0|0x0346|614|environmental-event/shared-slot-0x0A/shared-slot-template ok
row STEAMBOX 0x20 0x01F6|0x00000267|0x0346|0x053C|502|environmental-event/shared-slot-0x20/shared-slot-template 0x01F6|0x00000267|0x0346|0x053C|502|environmental-event/shared-slot-0x20/shared-slot-template ok
row STEAMBOX 0x21 0x02A7|0x0000045D|0x053C|0x07E3|679|environmental-event/shared-slot-0x21/shared-slot-template 0x02A7|0x0000045D|0x053C|0x07E3|679|environmental-event/shared-slot-0x21/shared-slot-template ok
1 record_type class_name slot expected actual status
2 slot-set AND_BOOT * 0x0A,0x0F,0x10 0x0A,0x0F,0x10 ok
3 slot-set BRO_BOOT * 0x0A,0x0F,0x10 0x0A,0x0F,0x10 ok
4 slot-set COR_BOOT * 0x0A,0x0F,0x10 0x0A,0x0F,0x10 ok
5 slot-set FLAMEBOX * 0x0A,0x20,0x21 0x0A,0x20,0x21 ok
6 slot-set JELYH2 * 0x01 0x01 ok
7 slot-set JELYHACK * 0x01 0x01 ok
8 slot-set NOSTRIL * 0x0A,0x20,0x21 0x0A,0x20,0x21 ok
9 slot-set REE_BOOT * 0x0A,0x0F,0x10 0x0A,0x0F,0x10 ok
10 slot-set STEAMBOX * 0x0A,0x20,0x21 0x0A,0x20,0x21 ok
11 slot-set SURCAMEW * 0x01,0x0A,0x20,0x21,0x22 0x01,0x0A,0x20,0x21,0x22 ok
12 slot-set SURCAMNS * 0x01,0x0A,0x20,0x21,0x22 0x01,0x0A,0x20,0x21,0x22 ok
13 slot-set VAR_BOOT * 0x0A,0x0F,0x10 0x0A,0x0F,0x10 ok
14 row JELYHACK 0x01 0x002A|0x00000001|0x00D4|0x00FE|42|referent-anchor-twin/shared-slot-0x01/same-length-template 0x002A|0x00000001|0x00D4|0x00FE|42|referent-anchor-twin/shared-slot-0x01/same-length-template ok
15 row JELYH2 0x01 0x002A|0x00000001|0x00D4|0x00FE|42|referent-anchor-twin/shared-slot-0x01/same-length-template 0x002A|0x00000001|0x00D4|0x00FE|42|referent-anchor-twin/shared-slot-0x01/same-length-template ok
16 row AND_BOOT 0x0A 0x0253|0x00000001|0x00D4|0x0327|595|boot-event-core/shared-slot-0x0A/shared-slot-template 0x0253|0x00000001|0x00D4|0x0327|595|boot-event-core/shared-slot-0x0A/shared-slot-template ok
17 row AND_BOOT 0x0F 0x0237|0x00000254|0x0327|0x055E|567|boot-event-core/shared-slot-0x0F/shared-slot-template 0x0237|0x00000254|0x0327|0x055E|567|boot-event-core/shared-slot-0x0F/shared-slot-template ok
18 row AND_BOOT 0x10 0x003B|0x0000048B|0x055E|0x0599|59|boot-event-core/shared-slot-0x10/same-length-template 0x003B|0x0000048B|0x055E|0x0599|59|boot-event-core/shared-slot-0x10/same-length-template ok
19 row BRO_BOOT 0x0A 0x02D5|0x00000001|0x00D4|0x03A9|725|boot-event-core/shared-slot-0x0A/shared-slot-template 0x02D5|0x00000001|0x00D4|0x03A9|725|boot-event-core/shared-slot-0x0A/shared-slot-template ok
20 row BRO_BOOT 0x0F 0x024C|0x000002D6|0x03A9|0x05F5|588|boot-event-core/shared-slot-0x0F/shared-slot-template 0x024C|0x000002D6|0x03A9|0x05F5|588|boot-event-core/shared-slot-0x0F/shared-slot-template ok
21 row BRO_BOOT 0x10 0x003B|0x00000522|0x05F5|0x0630|59|boot-event-core/shared-slot-0x10/same-length-template 0x003B|0x00000522|0x05F5|0x0630|59|boot-event-core/shared-slot-0x10/same-length-template ok
22 row COR_BOOT 0x0A 0x0227|0x00000001|0x00D4|0x02FB|551|boot-event-core/shared-slot-0x0A/shared-slot-template 0x0227|0x00000001|0x00D4|0x02FB|551|boot-event-core/shared-slot-0x0A/shared-slot-template ok
23 row COR_BOOT 0x0F 0x0234|0x00000228|0x02FB|0x052F|564|boot-event-core/shared-slot-0x0F/shared-slot-template 0x0234|0x00000228|0x02FB|0x052F|564|boot-event-core/shared-slot-0x0F/shared-slot-template ok
24 row COR_BOOT 0x10 0x003B|0x0000045C|0x052F|0x056A|59|boot-event-core/shared-slot-0x10/same-length-template 0x003B|0x0000045C|0x052F|0x056A|59|boot-event-core/shared-slot-0x10/same-length-template ok
25 row REE_BOOT 0x0A 0x034B|0x00000001|0x00D4|0x041F|843|boot-event-core/shared-slot-0x0A/shared-slot-template 0x034B|0x00000001|0x00D4|0x041F|843|boot-event-core/shared-slot-0x0A/shared-slot-template ok
26 row REE_BOOT 0x0F 0x025C|0x0000034C|0x041F|0x067B|604|boot-event-core/shared-slot-0x0F/shared-slot-template 0x025C|0x0000034C|0x041F|0x067B|604|boot-event-core/shared-slot-0x0F/shared-slot-template ok
27 row REE_BOOT 0x10 0x003B|0x000005A8|0x067B|0x06B6|59|boot-event-core/shared-slot-0x10/same-length-template 0x003B|0x000005A8|0x067B|0x06B6|59|boot-event-core/shared-slot-0x10/same-length-template ok
28 row VAR_BOOT 0x0A 0x029A|0x00000001|0x00D4|0x036E|666|boot-event-core/shared-slot-0x0A/shared-slot-template 0x029A|0x00000001|0x00D4|0x036E|666|boot-event-core/shared-slot-0x0A/shared-slot-template ok
29 row VAR_BOOT 0x0F 0x0244|0x0000029B|0x036E|0x05B2|580|boot-event-core/shared-slot-0x0F/shared-slot-template 0x0244|0x0000029B|0x036E|0x05B2|580|boot-event-core/shared-slot-0x0F/shared-slot-template ok
30 row VAR_BOOT 0x10 0x003B|0x000004DF|0x05B2|0x05ED|59|boot-event-core/shared-slot-0x10/same-length-template 0x003B|0x000004DF|0x05B2|0x05ED|59|boot-event-core/shared-slot-0x10/same-length-template ok
31 row SURCAMNS 0x01 0x0051|0x000000D2|0x01B7|0x0208|81|callback-eventtrigger/shared-slot-0x01/shared-slot-template 0x0051|0x000000D2|0x01B7|0x0208|81|callback-eventtrigger/shared-slot-0x01/shared-slot-template ok
32 row SURCAMNS 0x0A 0x00D1|0x00000001|0x00E6|0x01B7|209|callback-eventtrigger/shared-slot-0x0A/same-length-template 0x00D1|0x00000001|0x00E6|0x01B7|209|callback-eventtrigger/shared-slot-0x0A/same-length-template ok
33 row SURCAMNS 0x20 0x02BA|0x00000123|0x0208|0x04C2|698|callback-eventtrigger/shared-slot-0x20/same-length-template 0x02BA|0x00000123|0x0208|0x04C2|698|callback-eventtrigger/shared-slot-0x20/same-length-template ok
34 row SURCAMNS 0x21 0x0709|0x000003DD|0x04C2|0x0BCB|1801|callback-eventtrigger/shared-slot-0x21/shared-slot-template 0x0709|0x000003DD|0x04C2|0x0BCB|1801|callback-eventtrigger/shared-slot-0x21/shared-slot-template ok
35 row SURCAMNS 0x22 0x01A3|0x00000AE6|0x0BCB|0x0D6E|419|callback-eventtrigger/shared-slot-0x22/same-length-template 0x01A3|0x00000AE6|0x0BCB|0x0D6E|419|callback-eventtrigger/shared-slot-0x22/same-length-template ok
36 row SURCAMEW 0x01 0x00F7|0x000000D2|0x01B7|0x02AE|247|callback-eventtrigger/shared-slot-0x01/shared-slot-template 0x00F7|0x000000D2|0x01B7|0x02AE|247|callback-eventtrigger/shared-slot-0x01/shared-slot-template ok
37 row SURCAMEW 0x0A 0x00D1|0x00000001|0x00E6|0x01B7|209|callback-eventtrigger/shared-slot-0x0A/same-length-template 0x00D1|0x00000001|0x00E6|0x01B7|209|callback-eventtrigger/shared-slot-0x0A/same-length-template ok
38 row SURCAMEW 0x20 0x02BA|0x000001C9|0x02AE|0x0568|698|callback-eventtrigger/shared-slot-0x20/same-length-template 0x02BA|0x000001C9|0x02AE|0x0568|698|callback-eventtrigger/shared-slot-0x20/same-length-template ok
39 row SURCAMEW 0x21 0x0655|0x00000483|0x0568|0x0BBD|1621|callback-eventtrigger/shared-slot-0x21/shared-slot-template 0x0655|0x00000483|0x0568|0x0BBD|1621|callback-eventtrigger/shared-slot-0x21/shared-slot-template ok
40 row SURCAMEW 0x22 0x01A3|0x00000AD8|0x0BBD|0x0D60|419|callback-eventtrigger/shared-slot-0x22/same-length-template 0x01A3|0x00000AD8|0x0BBD|0x0D60|419|callback-eventtrigger/shared-slot-0x22/same-length-template ok
41 row FLAMEBOX 0x0A 0x026A|0x00000001|0x00E0|0x034A|618|environmental-event/shared-slot-0x0A/shared-slot-template 0x026A|0x00000001|0x00E0|0x034A|618|environmental-event/shared-slot-0x0A/shared-slot-template ok
42 row FLAMEBOX 0x20 0x01AC|0x0000026B|0x034A|0x04F6|428|environmental-event/shared-slot-0x20/shared-slot-template 0x01AC|0x0000026B|0x034A|0x04F6|428|environmental-event/shared-slot-0x20/shared-slot-template ok
43 row FLAMEBOX 0x21 0x029A|0x00000417|0x04F6|0x0790|666|environmental-event/shared-slot-0x21/shared-slot-template 0x029A|0x00000417|0x04F6|0x0790|666|environmental-event/shared-slot-0x21/shared-slot-template ok
44 row NOSTRIL 0x0A 0x00C0|0x00000001|0x00E0|0x01A0|192|environmental-event/shared-slot-0x0A/shared-slot-template 0x00C0|0x00000001|0x00E0|0x01A0|192|environmental-event/shared-slot-0x0A/shared-slot-template ok
45 row NOSTRIL 0x20 0x0129|0x000000C1|0x01A0|0x02C9|297|environmental-event/shared-slot-0x20/shared-slot-template 0x0129|0x000000C1|0x01A0|0x02C9|297|environmental-event/shared-slot-0x20/shared-slot-template ok
46 row NOSTRIL 0x21 0x01BE|0x000001EA|0x02C9|0x0487|446|environmental-event/shared-slot-0x21/shared-slot-template 0x01BE|0x000001EA|0x02C9|0x0487|446|environmental-event/shared-slot-0x21/shared-slot-template ok
47 row STEAMBOX 0x0A 0x0266|0x00000001|0x00E0|0x0346|614|environmental-event/shared-slot-0x0A/shared-slot-template 0x0266|0x00000001|0x00E0|0x0346|614|environmental-event/shared-slot-0x0A/shared-slot-template ok
48 row STEAMBOX 0x20 0x01F6|0x00000267|0x0346|0x053C|502|environmental-event/shared-slot-0x20/shared-slot-template 0x01F6|0x00000267|0x0346|0x053C|502|environmental-event/shared-slot-0x20/shared-slot-template ok
49 row STEAMBOX 0x21 0x02A7|0x0000045D|0x053C|0x07E3|679|environmental-event/shared-slot-0x21/shared-slot-template 0x02A7|0x0000045D|0x053C|0x07E3|679|environmental-event/shared-slot-0x21/shared-slot-template ok

View file

@ -1,5 +1,5 @@
{
"input_path": "k:\\ghidra\\Crusader_Decomp\\USECODE\\EUSECODE.FLX",
"input_path": "USECODE\\EUSECODE.FLX",
"file_size": 556613,
"header_preview_hex": "1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a0000020c000001000000000000000000000000000000000000000000000000000000000000000000000000000000",
"header_preview_ascii": "................................................................................................................................",

View file

@ -16,4 +16,4 @@ This file is an index. Detailed notes have been split into the `docs/` folder by
| [docs/raw-000a-000d.md](docs/raw-000a-000d.md) | 000d proximity/visibility buckets, 000a tracked handles, cache manager, init/shutdown, seg082 allocator, seg137/138 palette helpers, seg004/005 startup, 0x4588 object-role evidence, 000d VM owner/resource loader follow-up |
| [docs/far-call-targets.md](docs/far-call-targets.md) | Top-104 most-called far-call targets (Tiers 1-5, ranks 1-104), supporting functions discovered, analysis gaps and seg043 reconciliation |
| [docs/scummvm-crusader-reference.md](docs/scummvm-crusader-reference.md) | ScummVM Ultima8/Pentagram Crusader integration survey: USECODE/event tables, FLEX/resource formats, world/map loaders, HUD/media, and RE follow-up priorities |
| [docs/usecode-roundtrip-ir.md](docs/usecode-roundtrip-ir.md) | ScummVM-to-binary USECODE cross-walk, owner-loaded class-layout and header/event-count reconciliation, and conservative IR v0 plan for human-readable, editable, recompilable scripts |
| [docs/usecode-roundtrip-ir.md](docs/usecode-roundtrip-ir.md) | ScummVM-to-binary USECODE cross-walk, owner-loaded class-layout and header/event-count reconciliation, conservative IR v0 plan, and the generated class-event/body-window outputs that now ground reversible `_BOOT`, `SURCAM*`, and environmental family decompile artifacts plus repeated-family regression checks |

View file

@ -3,7 +3,7 @@
"2","code","0x40000","0x2B0","None","","","","crusader_ne_segments.csv"
"3","code","0x40400","0x55A","None","","","","crusader_ne_segments.csv"
"4","code","0x40A00","0x10B1","Foothold","Reset/cache entry path","runtime_cache_reset_sequence","ASYLUM.24 and downstream reset callers still need tighter classification","crusader_decompilation_notes.md; plan-mid.md"
"5","code","0x41E00","0x8D7","Foothold","Startup/display orchestration and large runtime handoff","FUN_0004_60c0; FUN_0004_1e00","Recovered bodies now exist, but exact subsystem naming and higher-level transition meaning remain open","crusader_decompilation_notes.md; plan-mid.md"
"5","code","0x41E00","0x8D7","Partial","Startup/display transition prepare/driver lane","startup_display_transition_prepare; startup_display_transition_driver","The two main seg005 bodies are now named and tied to caller-side validation through vtable +0x0c, the seg108 0x4f38 sprite/object helper lane, the shared active-dispatch hold byte at 0x6828, the seg049 watch/controller lane at 0x2bd8, and the seg126 follow-up path; the exact higher-level state label is still unresolved","crusader_decompilation_notes.md; plan-mid.md"
"6","code","0x42C00","0x75E","None","","","","crusader_ne_segments.csv"
"7","code","0x43600","0x484","None","","","","crusader_ne_segments.csv"
"8","code","0x43C00","0x1386","None","","","","crusader_ne_segments.csv"
@ -47,7 +47,7 @@
"46","code","0x7A200","0x7DC","None","","","","crusader_ne_segments.csv"
"47","code","0x7AC00","0x9B4","None","","","","crusader_ne_segments.csv"
"48","code","0x7B800","0x63","None","","","","crusader_ne_segments.csv"
"49","code","0x7BA00","0x1E3F","Foothold","Watch/camera controller object lane","watch_entity_controller_create_global; watch_entity_controller_create; watch_entity_controller_dispatch_if_present; entity_set_watch_ptr","Exact controller-vs-watched-entity ownership still needs caller-side confirmation, but 0x2bd8 is now clearly a real controller object lane","crusader_decompilation_notes.md; plan-mid.md"
"49","code","0x7BA00","0x1E3F","Foothold","Watch/camera controller object lane","watch_entity_controller_create_global; watch_entity_controller_create; watch_entity_controller_dispatch_if_present; entity_set_watch_ptr","Exact controller-vs-watched-entity ownership is still open, but startup_display_transition_driver now gives caller-side confirmation that the shared active-dispatch hold byte is raised before the 0x2bd8 vtable +0x2c dispatch and cleared again immediately after the same watch/controller phase","crusader_decompilation_notes.md; plan-mid.md"
"50","code","0x7DE00","0x9C8","None","","","","crusader_ne_segments.csv"
"51","code","0x7EA00","0x1D02","None","","","","crusader_ne_segments.csv"
"52","code","0x80A00","0x1D65","None","","","","crusader_ne_segments.csv"
@ -106,7 +106,7 @@
"105","code","0xAEC00","0x9F6","None","","","","crusader_ne_segments.csv"
"106","code","0xAF800","0x1795","None","","","","crusader_ne_segments.csv"
"107","code","0xB1400","0x40C","None","","","","crusader_ne_segments.csv"
"108","code","0xB1A00","0x113F","Foothold","Active sprite/object state lane","sprite_object_clear_flag40_if_present; sprite_object_set_flag40_if_present","Higher-level meaning of bit 0x40 and its relation to 0x2bd8 and 0x4588 is still unresolved","crusader_decompilation_notes.md; plan-mid.md"
"108","code","0xB1A00","0x113F","Foothold","Active sprite/object state lane","sprite_object_clear_flag40_if_present; sprite_object_set_flag40_if_present","startup_display_transition_prepare now confirms repeated seg108 helper use around the shared active-dispatch creation, and the same window shows a bounded local counter/stack at object +0x196/+0x186 rather than reuse of the caller object validated through vtable +0x0c; the local bit 0x40 contract at 0x4f38+0x32 is now separated from the shared active-dispatch owner byte at 0x6828+0x40, but the higher-level meaning of the sprite/object lane and its relation to 0x4588 is still unresolved","crusader_decompilation_notes.md; plan-mid.md"
"109","code","0xB2E00","0x1424","None","","","High-value gap around 000b:2e00 still unresolved","crusader_ne_segments.csv; crusader_decomp_progress.md"
"110","code","0xB4400","0x4C4","None","","","","crusader_ne_segments.csv"
"111","code","0xB4A00","0x489","None","","","","crusader_ne_segments.csv"
@ -124,8 +124,8 @@
"123","code","0xC3C00","0xE6D","None","","","","crusader_ne_segments.csv"
"124","code","0xC4E00","0x3DD","None","","","","crusader_ne_segments.csv"
"125","code","0xC5400","0x1A3E","None","","","","crusader_ne_segments.csv"
"126","code","0xC7400","0x402A","Partial","Transition-entry wrappers, pre-entry setup/script, and exit control","FUN_000c_7412; transition_preentry_setup_resources; transition_preentry_release_resources; transition_preentry_run_until_complete_or_abort; transition_preentry_step_script; wait_for_vga_vertical_retrace; thunk_callf_0000_ffff_000c_827d; thunk_callf_0000_ffff_000c_82f9; FUN_000c_834a","The seg126 helper family is structurally recovered and now ties into a paired temporary text-renderer lane at 0x8c5c/0x8c60, an external input/event gate at 0x31a2, and the shared active-dispatch owner at 0x6828 whose +0x40 byte follows that same gate; remaining open work is the exact UI role of the renderer pair, the DS:0x6341 to 0x6828 animation-owner relationship, and the separate oversized overlap rooted at 000c:db68","crusader_decompilation_notes.md; plan-mid.md"
"127","code","0xCC600","0x8F6","Partial","Palette fade controller and transition-state gate","palette_fade_begin_full_up; palette_fade_begin_full_down; transition_palette_fade_begin; transition_palette_fade_tick; transition_palette_fade_out_step; transition_palette_fade_in_step","Exact transition states and palette-source owners are still unresolved, but the local fade controller, default fade entry paths, and active/direction state at 0x630a/0x630b are now clear","crusader_decompilation_notes.md; plan-mid.md"
"126","code","0xC7400","0x402A","Partial","Transition-entry wrappers, pre-entry setup/script, and exit control","FUN_000c_7412; transition_preentry_setup_resources; transition_preentry_release_resources; transition_preentry_run_until_complete_or_abort; transition_preentry_step_script; wait_for_vga_vertical_retrace; thunk_callf_0000_ffff_000c_827d; thunk_callf_0000_ffff_000c_82f9; FUN_000c_834a","The seg126 helper family is structurally recovered and now ties into a paired temporary text-renderer lane at 0x8c5c/0x8c60, a shared async break/hold depth at 0x31a2 whose outer-loop exit test is visible at 000c:ca11, and the shared active-dispatch owner at 0x6828 whose +0x40 byte is raised immediately after the DS:0x6341 animation ctor path; remaining open work is the exact UI role of the renderer pair, the unresolved script bytes beyond the now-anchored fade controls, and the separate oversized overlap rooted at 000c:db68","crusader_decompilation_notes.md; plan-mid.md"
"127","code","0xCC600","0x8F6","Partial","Palette fade controller and transition-state gate","palette_fade_begin_full_up; palette_fade_begin_full_down; transition_palette_fade_begin; transition_palette_fade_tick; transition_palette_fade_out_step; transition_palette_fade_in_step","Exact higher-level transition states and palette-source owners are still unresolved, but the local fade controller, default fade entry paths, active/direction state at 0x630a/0x630b, and the seg126 script-byte selectors 0x5e -> full-down / 0x26 -> full-up (with 0x2a sharing the same post-fade bookkeeping path) are now clear","crusader_decompilation_notes.md; plan-mid.md"
"128","code","0xCD200","0x5D0","None","","","","crusader_ne_segments.csv"
"129","code","0xCDA00","0xD77","None","","","","crusader_ne_segments.csv"
"130","code","0xCEA00","0x47D","None","","","","crusader_ne_segments.csv"
@ -134,9 +134,9 @@
"133","code","0xD3800","0x215A","None","","","","crusader_ne_segments.csv"
"134","code","0xD6000","0xEF0","Foothold","VM runtime bootstrap and post-init seeding","entity_vm_runtime_init_from_path_if_configured; entity_vm_referent_registry_init; entity_vm_runtime_release_slots; entity_vm_runtime_init_slots","Configured path/global at 0x65a and the exact external file format behind the 0x6611 runtime owner table still need tighter classification","plan-mid.md; docs/raw-0008-000c.md"
"135","code","0xD7000","0x3B7","Foothold","VM runtime owner-resource helper","entity_vm_runtime_owner_resource_create; entity_vm_runtime_owner_resource_destroy","Embedded file-backed helper class and 0x0d-stride slot-table population semantics still need callee-side recovery","plan-mid.md; docs/raw-0008-000c.md"
"136","code","0xD7600","0x5BD","Foothold","Active dispatch-entry lifecycle helpers","active_dispatch_entry_mark_enabled; active_dispatch_entry_mark_disabled; active_dispatch_entry_create_default","Broader meaning of the active dispatch entry and its relationship to the startup/display lane still needs caller-side confirmation","crusader_decompilation_notes.md; plan-mid.md"
"136","code","0xD7600","0x5BD","Partial","Shared active dispatch-entry owner and hold-state controller","active_dispatch_entry_mark_enabled; active_dispatch_entry_mark_disabled; active_dispatch_entry_create_default","The shared active entry is now tied to the seg126 DS:0x6341 transition-animation path and to the shared 0x31a2 break/hold depth; current evidence also separates its borrowed +0x40 presentation hold token from the seg108-local 0x4f38 bit-0x40 lane, but the exact higher-level transition/callback subsystem name is still unresolved","crusader_decompilation_notes.md; plan-mid.md"
"137","code","0xD7E00","0xFBB","Partial","Palette and dispatch-entry emission helper family","entity_dispatch_entry_init_runtime_state; entity_dispatch_entry_release_runtime_state; vga_palette_set_all_black; vga_palette_set_all_white; vga_palette_set_all_rgb; dispatch_entry_create_black_palette_state_active; dispatch_entry_create_grayscale_palette_state_active; dispatch_entry_create_solid_palette_state_active","Higher-level event/script meaning is still unresolved, especially the paired 0x68bf object and the exact role of the 0004:5ad4-5b6e caller sequence","crusader_decompilation_notes.md; plan-mid.md"
"138","code","0xD9200","0x32E4","Foothold","Entity cleanup/finalize with callback and dispatch-entry palette emission","entity_cleanup_resources_and_dispatch; sprite_redraw_global_if_active","Concrete callback-object subsystem naming is still unresolved; FUN_000d_938c is now verified as a caller-side dispatch-entry/palette emission helper but remains intentionally unnamed","crusader_decompilation_notes.md; plan-mid.md"
"138","code","0xD9200","0x32E4","Partial","Entity cleanup/finalize with callback, watch-controller release, and dispatch-entry palette emission","entity_cleanup_resources_and_dispatch; sprite_redraw_global_if_active; FUN_000d_938c","Concrete callback-object subsystem naming is still unresolved, but this lane now has verified caller-side control of watch/controller state at 0x2bd8, uses the shared active-dispatch byte +0x40 as a borrowed presentation hold token rather than a local owner install, and emits two distinct 0x4588 payload pairs (entity +0x12d/+0x12f and +0x74f/+0x751) in addition to the palette-emission helpers","crusader_decompilation_notes.md; plan-mid.md"
"139","code","0xDCC00","0x984","None","","","","crusader_ne_segments.csv"
"140","code","0xDD800","0xC6F","None","","","","crusader_ne_segments.csv"
"141","code","0xDE600","0x2B","None","","","Short stub-sized segment","crusader_ne_segments.csv"

1 Segment Type FileOffset Length CoverageStatus KnownSubsystem KeyNamedFunctions Blockers NotesSource
3 2 code 0x40000 0x2B0 None crusader_ne_segments.csv
4 3 code 0x40400 0x55A None crusader_ne_segments.csv
5 4 code 0x40A00 0x10B1 Foothold Reset/cache entry path runtime_cache_reset_sequence ASYLUM.24 and downstream reset callers still need tighter classification crusader_decompilation_notes.md; plan-mid.md
6 5 code 0x41E00 0x8D7 Foothold Partial Startup/display orchestration and large runtime handoff Startup/display transition prepare/driver lane FUN_0004_60c0; FUN_0004_1e00 startup_display_transition_prepare; startup_display_transition_driver Recovered bodies now exist, but exact subsystem naming and higher-level transition meaning remain open The two main seg005 bodies are now named and tied to caller-side validation through vtable +0x0c, the seg108 0x4f38 sprite/object helper lane, the shared active-dispatch hold byte at 0x6828, the seg049 watch/controller lane at 0x2bd8, and the seg126 follow-up path; the exact higher-level state label is still unresolved crusader_decompilation_notes.md; plan-mid.md
7 6 code 0x42C00 0x75E None crusader_ne_segments.csv
8 7 code 0x43600 0x484 None crusader_ne_segments.csv
9 8 code 0x43C00 0x1386 None crusader_ne_segments.csv
47 46 code 0x7A200 0x7DC None crusader_ne_segments.csv
48 47 code 0x7AC00 0x9B4 None crusader_ne_segments.csv
49 48 code 0x7B800 0x63 None crusader_ne_segments.csv
50 49 code 0x7BA00 0x1E3F Foothold Watch/camera controller object lane watch_entity_controller_create_global; watch_entity_controller_create; watch_entity_controller_dispatch_if_present; entity_set_watch_ptr Exact controller-vs-watched-entity ownership still needs caller-side confirmation, but 0x2bd8 is now clearly a real controller object lane Exact controller-vs-watched-entity ownership is still open, but startup_display_transition_driver now gives caller-side confirmation that the shared active-dispatch hold byte is raised before the 0x2bd8 vtable +0x2c dispatch and cleared again immediately after the same watch/controller phase crusader_decompilation_notes.md; plan-mid.md
51 50 code 0x7DE00 0x9C8 None crusader_ne_segments.csv
52 51 code 0x7EA00 0x1D02 None crusader_ne_segments.csv
53 52 code 0x80A00 0x1D65 None crusader_ne_segments.csv
106 105 code 0xAEC00 0x9F6 None crusader_ne_segments.csv
107 106 code 0xAF800 0x1795 None crusader_ne_segments.csv
108 107 code 0xB1400 0x40C None crusader_ne_segments.csv
109 108 code 0xB1A00 0x113F Foothold Active sprite/object state lane sprite_object_clear_flag40_if_present; sprite_object_set_flag40_if_present Higher-level meaning of bit 0x40 and its relation to 0x2bd8 and 0x4588 is still unresolved startup_display_transition_prepare now confirms repeated seg108 helper use around the shared active-dispatch creation, and the same window shows a bounded local counter/stack at object +0x196/+0x186 rather than reuse of the caller object validated through vtable +0x0c; the local bit 0x40 contract at 0x4f38+0x32 is now separated from the shared active-dispatch owner byte at 0x6828+0x40, but the higher-level meaning of the sprite/object lane and its relation to 0x4588 is still unresolved crusader_decompilation_notes.md; plan-mid.md
110 109 code 0xB2E00 0x1424 None High-value gap around 000b:2e00 still unresolved crusader_ne_segments.csv; crusader_decomp_progress.md
111 110 code 0xB4400 0x4C4 None crusader_ne_segments.csv
112 111 code 0xB4A00 0x489 None crusader_ne_segments.csv
124 123 code 0xC3C00 0xE6D None crusader_ne_segments.csv
125 124 code 0xC4E00 0x3DD None crusader_ne_segments.csv
126 125 code 0xC5400 0x1A3E None crusader_ne_segments.csv
127 126 code 0xC7400 0x402A Partial Transition-entry wrappers, pre-entry setup/script, and exit control FUN_000c_7412; transition_preentry_setup_resources; transition_preentry_release_resources; transition_preentry_run_until_complete_or_abort; transition_preentry_step_script; wait_for_vga_vertical_retrace; thunk_callf_0000_ffff_000c_827d; thunk_callf_0000_ffff_000c_82f9; FUN_000c_834a The seg126 helper family is structurally recovered and now ties into a paired temporary text-renderer lane at 0x8c5c/0x8c60, an external input/event gate at 0x31a2, and the shared active-dispatch owner at 0x6828 whose +0x40 byte follows that same gate; remaining open work is the exact UI role of the renderer pair, the DS:0x6341 to 0x6828 animation-owner relationship, and the separate oversized overlap rooted at 000c:db68 The seg126 helper family is structurally recovered and now ties into a paired temporary text-renderer lane at 0x8c5c/0x8c60, a shared async break/hold depth at 0x31a2 whose outer-loop exit test is visible at 000c:ca11, and the shared active-dispatch owner at 0x6828 whose +0x40 byte is raised immediately after the DS:0x6341 animation ctor path; remaining open work is the exact UI role of the renderer pair, the unresolved script bytes beyond the now-anchored fade controls, and the separate oversized overlap rooted at 000c:db68 crusader_decompilation_notes.md; plan-mid.md
128 127 code 0xCC600 0x8F6 Partial Palette fade controller and transition-state gate palette_fade_begin_full_up; palette_fade_begin_full_down; transition_palette_fade_begin; transition_palette_fade_tick; transition_palette_fade_out_step; transition_palette_fade_in_step Exact transition states and palette-source owners are still unresolved, but the local fade controller, default fade entry paths, and active/direction state at 0x630a/0x630b are now clear Exact higher-level transition states and palette-source owners are still unresolved, but the local fade controller, default fade entry paths, active/direction state at 0x630a/0x630b, and the seg126 script-byte selectors 0x5e -> full-down / 0x26 -> full-up (with 0x2a sharing the same post-fade bookkeeping path) are now clear crusader_decompilation_notes.md; plan-mid.md
129 128 code 0xCD200 0x5D0 None crusader_ne_segments.csv
130 129 code 0xCDA00 0xD77 None crusader_ne_segments.csv
131 130 code 0xCEA00 0x47D None crusader_ne_segments.csv
134 133 code 0xD3800 0x215A None crusader_ne_segments.csv
135 134 code 0xD6000 0xEF0 Foothold VM runtime bootstrap and post-init seeding entity_vm_runtime_init_from_path_if_configured; entity_vm_referent_registry_init; entity_vm_runtime_release_slots; entity_vm_runtime_init_slots Configured path/global at 0x65a and the exact external file format behind the 0x6611 runtime owner table still need tighter classification plan-mid.md; docs/raw-0008-000c.md
136 135 code 0xD7000 0x3B7 Foothold VM runtime owner-resource helper entity_vm_runtime_owner_resource_create; entity_vm_runtime_owner_resource_destroy Embedded file-backed helper class and 0x0d-stride slot-table population semantics still need callee-side recovery plan-mid.md; docs/raw-0008-000c.md
137 136 code 0xD7600 0x5BD Foothold Partial Active dispatch-entry lifecycle helpers Shared active dispatch-entry owner and hold-state controller active_dispatch_entry_mark_enabled; active_dispatch_entry_mark_disabled; active_dispatch_entry_create_default Broader meaning of the active dispatch entry and its relationship to the startup/display lane still needs caller-side confirmation The shared active entry is now tied to the seg126 DS:0x6341 transition-animation path and to the shared 0x31a2 break/hold depth; current evidence also separates its borrowed +0x40 presentation hold token from the seg108-local 0x4f38 bit-0x40 lane, but the exact higher-level transition/callback subsystem name is still unresolved crusader_decompilation_notes.md; plan-mid.md
138 137 code 0xD7E00 0xFBB Partial Palette and dispatch-entry emission helper family entity_dispatch_entry_init_runtime_state; entity_dispatch_entry_release_runtime_state; vga_palette_set_all_black; vga_palette_set_all_white; vga_palette_set_all_rgb; dispatch_entry_create_black_palette_state_active; dispatch_entry_create_grayscale_palette_state_active; dispatch_entry_create_solid_palette_state_active Higher-level event/script meaning is still unresolved, especially the paired 0x68bf object and the exact role of the 0004:5ad4-5b6e caller sequence crusader_decompilation_notes.md; plan-mid.md
139 138 code 0xD9200 0x32E4 Foothold Partial Entity cleanup/finalize with callback and dispatch-entry palette emission Entity cleanup/finalize with callback, watch-controller release, and dispatch-entry palette emission entity_cleanup_resources_and_dispatch; sprite_redraw_global_if_active entity_cleanup_resources_and_dispatch; sprite_redraw_global_if_active; FUN_000d_938c Concrete callback-object subsystem naming is still unresolved; FUN_000d_938c is now verified as a caller-side dispatch-entry/palette emission helper but remains intentionally unnamed Concrete callback-object subsystem naming is still unresolved, but this lane now has verified caller-side control of watch/controller state at 0x2bd8, uses the shared active-dispatch byte +0x40 as a borrowed presentation hold token rather than a local owner install, and emits two distinct 0x4588 payload pairs (entity +0x12d/+0x12f and +0x74f/+0x751) in addition to the palette-emission helpers crusader_decompilation_notes.md; plan-mid.md
140 139 code 0xDCC00 0x984 None crusader_ne_segments.csv
141 140 code 0xDD800 0xC6F None crusader_ne_segments.csv
142 141 code 0xDE600 0x2B None Short stub-sized segment crusader_ne_segments.csv

View file

@ -198,7 +198,7 @@ Second sweep through `000c` adjacent helpers — gated thunk wrappers and input/
| `000c:9f74` | `entity_state_flag100_check_and_dispatch` | Init latch guard at `[0x6053]`; clears `[0x8c55]` on first call; checks `[ptr+0x5b]` bits `0x100` and `0x40`; three-path thunk dispatch |
| `000c:a1ad` | `entity_state_clear_flag40_and_dispatch` | Skips if `[ptr+0x5b]` has `0x180` bits set; if `0x40` set, clears it and calls far ptr at `[0x5e82/0x5e84]`; then dispatches twice with args `(0x0b,0x10,0x1,0x0)` (record/state-key pattern) |
| `000c:a74e` | `entity_state_dispatch_if_flag_bit2` | Tests `[ptr+0x5b]` bit `0x2`; if set pushes extra arg + ptr and dispatches via thunk |
| `000c:84c3` | `entity_state_set_byte40_at_global_ptr` | Sets byte `[DAT_0000_6828 ptr + 0x40] = 1` then calls thunk unconditionally; enables global entity flag |
| `000c:84c3` | `entity_state_set_byte40_at_global_ptr` | Sets byte `[g_active_dispatch_entry_farptr + 0x40] = 1` then calls thunk unconditionally; current evidence treats this as raising the shared active-entry transition/display hold byte rather than toggling an unrelated global |
| `000c:ac55` | `entity_state_fire_if_handle_valid` | Guard: fires thunk dispatch only when `[0x6054] != -1`; no-op otherwise |
| `000c:ac6d` | `entity_state_fire_with_args_if_handle_valid` | 3-arg variant: pushes `[BP+0xe]` (byte), `[BP+0xc]`, `[BP+0xa]`, handle `[0x6054]`, then `CALLF 0000:ffff` |
| `000c:afa5` | `entity_state_check_field49_and_call_vfunc3c` | Checks field `[ptr+0x49]`: 1→reset to 0 return 1; 2→call `vtable[0x3c]` return 0; else thunk dispatch |
@ -214,7 +214,7 @@ Second sweep through `000c` adjacent helpers — gated thunk wrappers and input/
- `field49` = state-sequence index; 0=reset, 2=vtable callback, 4=triggered end
- `field47` = keystroke-combo counter
- `field3f` = linked data pointer (event/record reference)
- `[0x6054]` = current entity handle; `[0x6828]` = another global entity far pointer
- `[0x6054]` = current entity handle; `[0x6828]` = `g_active_dispatch_entry_farptr`, the shared active-dispatch entry owner whose byte `+0x40` is reused across the startup/display lane as a hold/busy token
- Bits in `[ptr+0x5b]`: `0x1=init`, `0x2=active/event`, `0x40=pending dispatch`, `0x100=flag100`, `0x180=skip-all mask`
---
@ -263,7 +263,8 @@ Globals used: `[0x6312]`=start index, `[0x6314]`=count, `[0x630e]`=palette src p
- `entity_vm_context_save` / `entity_vm_context_load` / `entity_vm_context_destroy` / `entity_vm_context_free_buffer` (`000d:498f`, `000d:4a78`, `000d:4962`, `000d:48b6`) now pin down the lifecycle of this object family rather than leaving the whole `000d:45xx..4exx` island anonymous
- `entity_vm_context_try_create_masked_for_entity` is now better constrained at the return-value level too: after the runtime-disable check at `0x6610` and the owner-side slot-mask test succeed, it reports two distinct success shapes. Immediate-flagged contexts (`+0x16 & 0x0008`) clear the caller output word, while object-backed contexts return the created object's low word. That makes the helper a typed bridge from gameplay entities into VM-backed object results, not only a yes/no mask probe.
- `entity_vm_runtime_owner_resource_create` (`000d:7000`) is now one step tighter too: the embedded seg069/070 helper is file-backed rather than abstract. Construction starts with `dos_file_handle_init` (`0009:1c00`), then uses helper vtable slot `+0x04` as the size query that drives the child `+0x10/+0x12` allocation and helper vtable slot `+0x0c` as the table-population callback for the `0x0d`-stride owner table.
- That file-backed helper is now tighter one step deeper as well. The seg070 loops rooted at raw windows `0009:67b6` and `0009:6916` walk helper-owned record arrays at object `+0x10/+0x18`, format per-entry paths through the seg001 string helpers (`0003:e4d3` / `0003:e590`), then open, read, and close each file through `file_handle_alloc_init_and_open` (`0009:1c3a`), `dos_file_seek` (`0009:2034`), and `dos_file_close` (`0009:1e61`). That is strong evidence that `000d:7000` seeds the owner table from an indexed external file set rather than by copying one monolithic in-memory descriptor blob.
- That file-backed helper is now tighter one step deeper as well. The seg070 loops rooted at raw windows `0009:67b6` and `0009:6916` walk helper-owned record arrays at object `+0x10/+0x18`, format per-entry paths through the seg001 string helpers (`0003:e4d3` / `0003:e590`), then open, read, and close each file through `file_handle_alloc_init_and_open` (`0009:1c3a`), `dos_file_seek` (`0009:2034`), and `dos_file_close` (`0009:1e61`). The paired `+0x18` entries are consumed as 16-bit ids passed into those path-format loops beside the far-pointer path table at `+0x10`; no object-1 or `classid + 2` arithmetic appears there, so the safest current read is slot-local file ids rather than exposed original class/object indices. That is strong evidence that `000d:7000` seeds the owner table from an indexed external file set rather than by copying one monolithic in-memory descriptor blob.
- A final loader-side tightening from the current pass is that `0009:67b6` and `0009:6916` now read as twin entry walkers rather than one isolated path-format callback. Both windows iterate the helper-owned count at `+0x14`, index the far-pointer path table at `+0x10` and paired 16-bit id table at `+0x18`, check the source path through `0003:e669`, build formatted paths with distinct local format strings (`DS:3f2d` vs `DS:3f40`), and then reach the same file open/read/close lane. The remaining open question is not whether they are file-backed, but whether they represent two file families, two record templates, or two load phases inside the same helper class.
- The caller-side bootstrap for that helper is now anchored too: `entity_vm_runtime_init_from_path_if_configured` (`000d:44df`) first checks the configured byte/string global at `0x65a`, builds a path through seg072 helper `0009:3600` using globals `0x6d6:0x6d8` plus `0x65a`, validates that path through `000a:500a`, then calls `entity_vm_runtime_create(0,0,path)`. This is the first verified source-argument path for `entity_vm_runtime_owner_resource_create`, and it strongly suggests the owner/resource table is loaded from an external configured file rather than from a purely in-memory descriptor blob.
- Seg072 helper `0009:3600` is now classified more tightly as a rotating slash-aware path composer rather than a generic buffer advance helper. Its prologue cycles through five `0x50`-byte temp buffers, and its inner cases append optional string parts while inserting `\` only when adjacent path components need a separator. That narrows the two globals used by `000d:44df`: `0x65a` behaves as the configured relative runtime-owner filename/path component, while `0x6d6:0x6d8` behaves as the mutable base/resource-root path buffer that gets joined with `0x65a` before `000a:500a` validation.
- The two still-xref-dark wrappers `0005:2c35` and `0005:2c68` are also narrower now. Their signed extra word does not participate in owner-mask selection inside `entity_vm_context_try_create_masked_for_entity`; it is forwarded into `entity_vm_context_create_from_slot_index`, stored in context field `+0x34`, and passed on to `entity_vm_slot_load_value_plus_offset`. The best current reading is therefore `offset-specialized masked context creation`, not a separate direct selector lane.
@ -289,7 +290,8 @@ Globals used: `[0x6312]`=start index, `[0x6314]`=count, `[0x630e]`=palette src p
- `entity_vm_state_copy` (`000c:f772`) copies that same `+0xcc..+0xd2` stream/base quartet verbatim when one mini-VM object is cloned.
- Upstream of the setup helper, `000d:46ec` derives the source payload from the runtime owner table behind `0x6611 -> +0x1315/+0x1317`: with slot index `SI`, it walks owner table `*(owner+0x10/+0x12) + 0x0d*SI + 4`, passes that far pointer into `000c:f844`, and mirrors the resulting per-slot source into `0x39ca[slot]`.
- This sharpens the current JELYHACK-side model rather than overturning it: the code-side producer recovered in this batch is still a generic slot-backed VM source object keyed by gameplay-entity slot selection and owner-side mask bits, not a direct hard-coded descriptor-class switch on `JELYHACK` or `JELYH2`. Combined with the extractor evidence that `JELYHACK` / `JELYH2` remain referent-only while `REE_BOOT` / `SFXTRIG` keep active `event` tags and `SURCAMEW` keeps `eventTrigger`, the better fit is still `referent anchor -> slot-backed payload chain -> neighboring event-bearing attachment`.
- The `0x39ca` mirror question is narrower now too. Fresh windows at `0008:709c/70cb`, `0008:7309/7338`, and `0008:85f9/8617` show only global base-pointer save/restore and allocation/zeroing of the `0x39ca:0x39cc` table itself. The only verified per-slot row writer in this lane remains `entity_vm_context_create_from_slot_index` (`000d:46ec`), which writes `0x39ca[context_slot] = {source_off, source_seg}` after it derives the slot-backed payload source.
- The `0x39ca` mirror question is now split more cleanly. Fresh windows at `0008:709c/70cb`, `0008:7309/7338`, and `0008:85f9/8617` still show only global base-pointer save/restore and allocation/zeroing of the `0x39ca:0x39cc` table itself, but two additional per-slot row writers are now verified in `000d`: `FUN_000d_7299` writes static source `DS:67f2` to `0x39ca[obj+2]` after creating a `0x44`-byte object, and `active_dispatch_entry_create_default` (`000d:761c`) writes static source `DS:6872` to `0x39ca[obj+2]` for the default active dispatch entry. `entity_vm_context_create_from_slot_index` (`000d:46ec`) remains the only confirmed owner-table-derived writer, but it is no longer the only concrete row writer overall.
- The current pass narrows that split one step further. `entity_vm_context_create_from_slot_index` (`000d:46ec`) still derives its row from runtime owner table `(+0x10/+0x12) + 0x0d*slot + 4` before mirroring it into `0x39ca[slot]`, while `000d:7299` and `000d:761c` never touch the owner table at all in the verified windows. Instead they allocate local dispatch-entry-style objects, derive the row index from object field `+0x2`, and seed `0x39ca[row]` from fixed static sources `DS:67f2` and `DS:6872`. The safest current interpretation is therefore `owner-backed VM source mirror` versus `dispatch-entry-local static seed rows`, not three competing writers to the same semantic lane.
- One exact numeric collision is now ruled out as unrelated noise rather than a second VM source: `000e:0953` in the animation/audio lane pushes literal `0x410` into imported `ASYLUM.27` immediately after setting the local audio-completion byte at `+0xef1`. Because `ASYLUM.DLL` is the `ASS_*` audio/media library, this does not weaken the attribution of gameplay event `0x410` to the `000d` VM/USECODE lane.
- Current best JELYHACK reading after this pass: `JELYHACK` itself still looks like a referent-only map/object descriptor, but that no longer makes it inert. A referent-only record can still matter by supplying the referent id that populates the VM referent registry, while neighboring classes such as `REE_BOOT`, `SURCAMEW`, and `SFXTRIG` supply the event-bearing logic attached to the same local object island.
@ -337,8 +339,9 @@ Conservative case identity mapping from this pass:
Still unresolved after this pass:
- Direct CALL xrefs into `FUN_000d_ebe3` are now confirmed from `animation_ctor_variant_a/b/c` at `000e:283e`, `000e:2931`, and `000e:29e4`, so the entry is no longer globally xref-dark.
- Those constructor callsites still do not expose a new concrete wrapper-level opcode number or the direct write/read path for `[BP-0x32]`; no additional opcode id can yet be assigned uniquely beyond the internal `0x19/0x1a/0x1b` family already proven inside `000d:0988`.
- The animation constructor near calls at `000e:283e`, `000e:2931`, and `000e:29e4` land on a separate mis-split `000e:ebe3` region, not on `FUN_000d_ebe3`. They therefore no longer count as direct xref evidence for the `000d` dispatcher.
- The true upstream selector/write path for `[BP-0x32]` in `FUN_000d_ebe3` is still unresolved, and no additional opcode id can yet be assigned uniquely beyond the internal `0x19/0x1a/0x1b` family already proven inside `000d:0988`.
- Repeated MCP-visible instruction and data-use searches still do not produce a real direct caller edge for `FUN_000d_ebe3`, `0005:2c35`, or `0005:2c68`. For now that makes the next defensible route `caller-frame / shared-consumer recovery`, not more recycled raw call searches or the retired `000a:44fd` and `000e:ebe3` hypotheses.
### First readable VM IR sketch (verified-only)
@ -461,6 +464,7 @@ The next gameplay-side wrapper pass now extends well past the three earlier seed
#### Concrete caller/xref addendum from the next pass
- Direct callsites are now pinned for the simpler wrappers: `0005:0292 -> 0005:2c06`, `0005:0fee -> 0005:2cd2`, `0005:5946/59e9 -> 0005:2c9b`, and `0007:814e/822e -> 0005:2d01`.
- The two direct `0005:2d30` callers are now role-shaped as well: `0005:5370` reaches slot `0x0f` only after `entity_class_has_flag2000` succeeds and class-word bit `0x8000` is clear, while `0005:6f47` reaches the same gate from the complementary branch where class-word bit `0x2000` is still clear before the caller continues into its larger state/update flow.
- `0005:2c68` is no longer usable as indirect selector evidence. The `0007:e521` and `0007:e73c` instruction windows do push `0x2c68` immediately before `CALLF 000a:44fd`, but decompile now shows that value is the caller-local data pointer `DAT_0000_2c68` passed into a fatal-report helper, not an indirect call to wrapper `0005:2c68`.
- `0005:2c35` and `0005:2c68` therefore both remain unresolved in direct caller/xref evidence, and the real selector work stays centered on the still-xref-dark upstream edge into `FUN_000d_ebe3` rather than the disproven `000a:44fd` hypothesis.
- Net effect: the active-event ecosystem fit is reinforced by direct caller behavior and payload shapes, but final slot-to-descriptor ownership still requires real caller-role recovery for the remaining xref-dark entry points.

View file

@ -220,11 +220,103 @@ Current verified behavior:
| Address | Name | Notes |
|---------|------|-------|
| `0004:60c0` | `FUN_0004_60c0` | Startup/display orchestration path: broad setup calls, reads the live VGA palette, validates a caller-provided object through vtable slot `+0x0c`, drives the sprite/object lane through `0x4f38`, runs the seg137 palette and dispatch-entry helper family, creates the default active dispatch entry through `active_dispatch_entry_create_default`, programs mouse interrupt state via seg056 `INT 33h` wrappers, then hands off into the still-unrecovered `0004:1e00` routine. |
| `0004:60c0` | `startup_display_transition_prepare` | Startup/display transition prepare step: broad setup calls, reads the live VGA palette, validates a caller-provided object through vtable slot `+0x0c`, drives the sprite/object lane through `0x4f38`, creates the default active dispatch entry through `active_dispatch_entry_create_default`, programs mouse interrupt state via seg056 `INT 33h` wrappers, then hands off into `startup_display_transition_driver`. |
| `0004:1e00` | `startup_display_transition_driver` | Non-return startup/display transition driver: raises the shared active-dispatch hold byte around the seg049 watch/controller lane, then clears it before the seg080 redraw and seg126 follow-up path. |
| `000d:7600` | `active_dispatch_entry_mark_enabled` | Marks the active dispatch entry enabled |
| `000d:760e` | `active_dispatch_entry_mark_disabled` | Marks the active dispatch entry disabled |
| `000d:761c` | `active_dispatch_entry_create_default` | Creates the default active dispatch entry |
Current verified caller-side detail:
- `startup_display_transition_prepare` now has enough exact instruction evidence to pin the seg108 lane down more tightly. Window `0004:618e..620c` calls the `0x4f38` sprite/object helpers in a stable sequence around the shared active-entry creation: seg108 `000b:1e39`, `000b:2492`, and `000b:26bd` run before `active_dispatch_entry_create_default`, and seg108 `000b:2706` runs again immediately before the handoff into `startup_display_transition_driver`.
- The same seg108 window now shows a local bounded counter/stack contract instead of reuse of the validated caller object. `000b:26bd` increments object word `+0x196` up to `7` before calling the common local helper at `000b:2592`, and `000b:2706` reads one prior slot from `+0x186`, decrements `+0x196`, and replays the same helper before `startup_display_transition_driver` takes over.
- The seg108 helper pair is now named too: `sprite_object_push_state_word` (`000b:26bd`) increments the bounded local stack depth at `+0x196`, stores the incoming word into the per-object stack at `+0x186`, refreshes local sprite state through `000b:2592`, and replays redraw when the object was already marked dirty; `sprite_object_pop_state_word` (`000b:2706`) returns the previous top word, decrements the same bounded depth, and reapplies the new top through that same helper. This makes the `0x4f38` lane read more like a self-contained sprite/object state stack than a reused validated caller object.
- `startup_display_transition_driver` now has exact hold-token ordering too. Window `0004:2013..212c` raises `g_active_dispatch_entry_farptr[+0x40]`, dispatches the seg049 watch/controller object at `0x2bd8` through vtable slot `+0x2c`, runs the intervening transition call at `0004:eece`, and then clears the same shared hold byte again just before the seg080 redraw pair and seg126 follow-up.
- Upstream caller tracing now shows that the four `0004:eece` call shapes are chosen from one startup switch-parser lane rather than from a named local phase enum. Window `0004:63c1..66fb` walks an argv-like table against a local dispatch/jump table, and the `0004:64ff..65c1` case loads globals `0x84a/0x84c/0x84e/0x850` plus scalar `0x856`; `startup_display_transition_driver` later fans those values into the `0004:2049`, `0004:20b3`, `0004:20c6`, and `0004:20fe` call variants. The other direct caller anchors at `0004:2657`, `000c:8786`, and `000c:742c` all remain inside the same startup/display presentation-handoff family, so the safest output is still tighter caller-family semantics rather than a new neutral state label.
- The seg049 and seg108 globals are now better separated by direct decompile evidence rather than only call-window correlation. `watch_entity_controller_dispatch_if_present` confirms that `0x2bd8` is a real controller object with active vtable dispatch at slots `+0x2c` and `+0x30`, while `sprite_object_set_flag40_if_present` and `sprite_object_clear_flag40_if_present` show that `0x4f38` is a separate global object lane whose immediate local contract is only bit `0x40` in object word `+0x32`.
- The seg127 fade-controller ownership is also one step tighter in the same lane. `transition_preentry_setup_resources` resets `0x630a` at `000c:c855`, `transition_preentry_step_script` now has a verified early gate at `000c:ca25` that yields to the fade controller whenever `0x630a` is active, and `transition_palette_fade_begin` at `000c:cdca` explicitly installs palette source/range/step state into `0x630e..0x6316`, asserts `0x630a`, and kicks one immediate fade tick.
- Fade direction is now pinned to seg126 script-control bytes rather than the outer seg005 wrappers. Inside `transition_preentry_step_script`, control byte `0x5e` reaches `palette_fade_begin_full_down` at `000c:cb06`, while control byte `0x26` reaches `palette_fade_begin_full_up` at `000c:cd1a`; control byte `0x2a` shares the same post-fade bookkeeping path after the full-up call.
- The upstream producer path for the remaining seg126 control bytes is now tighter too. `transition_preentry_setup_resources` composes one path from the mutable base at `0x6aa:0x6ac` plus local name buffers (`0x631c`, `0x6335`) through the seg072 slash-aware path helper `0009:3600`, opens that file through `file_handle_alloc_init_and_open`, allocates a buffer of the returned size, reads the full payload into `0x6301:0x6303`, and seeds `0x62fa/0x62fc/0x62ff/0x6305/0x630a/0x6318` before the loop starts. Current best reading is therefore `file-backed transition script/control buffer`, not locally synthesized opcodes.
- The remaining `transition_preentry_step_script` opcodes now have stable local mechanics even though the higher-level text semantics are still open. Control byte `0x21` consumes the next script word into `SI` and advances `0x62ff` by two, which makes it the current baseline/start-position loader for later text draws. Control byte `0x40` renders one null-terminated entry from the same script buffer through renderer object `0x8c5c:0x8c5e`, while control byte `0x24` mirrors that behavior through `0x8c60:0x8c62`; both paths measure width through the renderer vtable, draw through seg088 `000a:30d7`, blit through seg080 `0009:943a`, advance `SI` by rendered width plus four, and then scan forward to the next opcode byte. Control byte `0x23` sets local completion byte `0x62fe = 1` and returns, so the outer shell exits on the next loop test instead of iterating further.
- Secondary renderer-factory sampling keeps the `0x8c5c` / `0x8c60` split conservative. Other sampled `000a:9748` xrefs use different adjacent preset pairs such as `0x0d/0x0c` at `0007:df30/df3f` and `0x0c/0x0f` at `0008:47c9/4851`, while no sampled caller reproduced the exact `0x10/0x11` startup pair outside `transition_preentry_setup_resources`. That supports keeping these as paired preset text renderers without forcing a title/body or normal/highlight label.
- The missing seg126 step body at `000c:ca1d` still cannot be split out safely because `create_function_by_address` collides with the existing oversized overlap namespace, so this pass preserved the recovery as a decompiler comment instead of forcing a destructive boundary repair. Current best reading is still that `000c:ca1d..cd34` is the real `transition_preentry_step_script` body and that `000c:cd35` starts the fade-tick helper.
---
## Follow-up: `0x31a2` Break/Hold Depth and Active Dispatch Ownership
This pass tightened the shared startup/display transition lane enough to preserve the gate semantics directly in Ghidra and to promote the active-dispatch helpers out of an isolated foothold.
### Verified `0x31a2` role
- `0008:a283` increments `0x31a2` while installing one live far-pointer slot record into the seg008 per-index table, and the paired path at `0008:a314` decrements `0x31a2` while clearing that same record.
- `0005:453a` is now commented in Ghidra as a plain getter for the shared `0x31a2` depth word.
- `transition_preentry_run_until_complete_or_abort` (`000c:c9f4`) now decompiles cleanly and confirms the main transition loop runs until either local completion byte `0x62fe` becomes non-zero or `0x31a2` becomes positive.
- The shell around `transition_preentry_step_script` is now tighter too: `000c:ca11` is the direct second exit test in the outer seg126 loop, so a positive `0x31a2` falls straight into `transition_preentry_release_resources` even when local completion byte `0x62fe` is still clear.
- `0004:c24d` and `000c:e4d8` are now tightened as pure busy-wait edge loops on `0x31a2`, while `000c:e546` and `000c:e5c6` are the same break-depth check embedded in local presentation/cleanup loops rather than plain one-shot flag tests.
- The blocking/waiting readers around `000c:e4d8`, `000c:e546`, and `000c:e5c6` treat `0x31a2` as an asynchronous break condition rather than a local state bit: they either busy-wait for a positive edge or abort their local presentation loop early when the depth is already positive.
- Additional caller-side reads at `000d:9304`, `000d:b6b1`, and `000d:c0ee` all use `0x31a2 > 0` to short-circuit or advance local dispatch-entry state, which fits a shared break/hold depth better than a one-shot acknowledge flag. `dispatch_entry_kind2_tick_hold_and_maybe_dispatch` (`000d:92eb`) is still the clearest recovered dispatch-entry example: when a kind-`0x0002` entry is pending, a positive `0x31a2` lets the helper dispatch vtable slot `+0x08` even if the local `+0x40` hold byte is still asserted, after which it decrements that local byte. The newly checked `000d:b6b1` reader is narrower: it advances one local state-`5` branch only when entity byte `+0x78` is set and class/state word `+0x16` carries bit `0x4000`, then optionally runs the seg092 follow-up when the `0x45aa` gate and entity byte `+0x74a` are both active.
Current best neutral description: `0x31a2` is a shared asynchronous break/hold depth maintained by the seg008 install/remove path and consumed by transition/presentation code as a positive-count modal break condition.
### `0x6341` to `0x6828` relationship
- The missing seg126 function object at `000c:c63a` is now created and named `transition_preentry_setup_resources`; its body allocates the paired temporary text-renderer objects at `0x8c5c/0x8c60`, draws the preset `0x10` and `0x11` text variants, loads the file-backed buffer into `0x6301:0x6303`, and seeds the pre-entry state bytes before the main loop starts.
- The paired helper `transition_preentry_release_resources` (`000c:c890`) still handles teardown, but its late branch at `000c:c958` also constructs the transition-local animation object at `DS:0x6341` through `animation_ctor_variant_a`, then immediately sets `g_active_dispatch_entry_farptr[+0x40] = 1` at `000c:c963`.
- `active_dispatch_entry_create_default` (`000d:761c`) still owns the canonical `g_active_dispatch_entry_farptr` installation.
- `dispatch_entry_kind2_tick_hold_and_maybe_dispatch` (`000d:92eb`) now makes that paired readback explicit: when a kind-`0x0002` dispatch entry is pending and either entry byte `+0x40` is already non-zero or the shared break depth `0x31a2` is positive, it dispatches vtable slot `+0x08` and then decrements `+0x40`.
- `FUN_000d_938c` and `entity_cleanup_resources_and_dispatch` both clear `g_active_dispatch_entry_farptr[+0x40]` after their palette/presentation handoff work, which ties the active entry to the same transition-owned presentation lane rather than to an isolated constructor helper.
- `entity_cleanup_resources_and_dispatch` is now tighter on the caller side too. Its first `0x4588` callback emit at `000d:9d5e` is confirmed as the `entity +0x12d/+0x12f` payload-pair path, while the later emit at `000d:a3b7` uses the separate `entity +0x74f/+0x751` pair. Both still sit inside the same palette/watch-controller cleanup body rather than a separate callback-only helper.
- Caller-role alignment is now tighter across the remaining startup/display cleanup bodies. The mis-split seg126 window `000c:6176/619c -> 000c:6226` constructs only a temporary local animation payload, frees it, dispatches the seg049 watch/controller object at `0x2bd8` through vtable slot `+0x2c`, and then clears the shared owner byte `+0x40`; `FUN_000d_938c` similarly waits on two temporary palette/state entries, redraws, clears the same shared hold byte at `000d:958d`, and only then dispatches its caller object through vtable slot `+0x08`; `entity_cleanup_resources_and_dispatch` clears `g_active_dispatch_entry_farptr[+0x40]` at `000d:a1cf` only on the branch where entity byte `+0x737` is set and no temporary object remains, before falling into the same watch/controller dispatch at `000d:a1ed`. That is enough to align them as consumers of one shared presentation hold token around the seg049 lane, but still not enough to justify a single higher-level subsystem rename for `FUN_000d_938c` or the mis-split seg126 body.
### Follow-up: shared owner versus borrowed hold-token model
- `active_dispatch_entry_mark_enabled` (`000d:7600`) and `active_dispatch_entry_mark_disabled` (`000d:760e`) are now verified as tiny wrappers that only write the shared owner byte `g_active_dispatch_entry_farptr[+0x40] = 1/0`; they do not install or replace the owner object.
- `entity_dispatch_entry_init_runtime_state` (`000d:7e00`) now tightens that ownership split further. When it builds a runtime-state dispatch entry, it copies the current shared owner byte at `g_active_dispatch_entry_farptr[+0x40]` into the new entry's local byte `+0x40`; if the new entry stays inactive while a shared owner exists, it raises the shared owner's `+0x40` byte to `1` instead of replacing the owner pointer.
- The paired destructor `entity_dispatch_entry_release_runtime_state` (`000d:8078`) clears `g_active_dispatch_entry_farptr[+0x40]` when the runtime-state entry was marked as owner-propagating (`+0x41 != 0`) or when the entry's local hold byte was never asserted. This matches borrowed hold-state propagation, not separate owner creation.
- `startup_display_transition_driver` now provides the clearest caller-side proof in seg005: it raises `g_active_dispatch_entry_farptr[+0x40]` at `0004:2013` before the seg049 watch/camera path and the `0004:eece` transition call, and clears that same byte again at `0004:2128` before the seg080 display update pair, sprite redraw, and seg126 follow-up thunk `000c:82f9`.
- The still-mis-split seg126 window around `000c:6176/619c` also fits the same model. It makes one mode-dependent `animation_ctor_variant_a` call on a temporary local object, frees that temporary object, reloads the palette, dispatches the `0x2bd8` watch/camera controller through vtable slot `+0x2c`, and later clears `g_active_dispatch_entry_farptr[+0x40]` at `000c:6226`. No canonical owner installation is visible in that body.
- The thin seg005 wrappers at `0005:3c36` and `0005:3c5b` are now confirmed as pure `animation_ctor_variant_a` preset shims. Combined with the seg126 windows above, current best reading is: `DS:0x6341` and the other constructor callsites build transition-local or display-local animation payloads, while `0x6828` remains the shared active-dispatch owner installed elsewhere.
Current best model: `g_active_dispatch_entry_farptr` is a shared owner installed by `active_dispatch_entry_create_default`, and the startup/display transition lane mostly borrows and propagates the owner's byte `+0x40` as a hold/busy token while palette/runtime-state helpers run. The remaining open problem is the exact state/object label behind the seg049 watch/camera path, the seg108 sprite/object lane, and the cleanup branches that consume the same token.
### Current batch: presentation handoff family versus single-owner hypothesis
- The exact late sequencing now supports one stricter neutral read: these bodies behave like a shared startup/display presentation handoff family, but not like one single owner-object family. In `startup_display_transition_prepare`, the validated caller object (vtable `+0x0c`), the seg108 `0x4f38` lane, and the seg049 `0x2bd8` watch/controller lane stay separated by direct instruction windows rather than collapsing into one reused object path.
- `transition_preentry_setup_resources` also tightened the paired renderer role one step further. Window `000c:c659..c6ab` allocates renderer presets `0x10` and `0x11` through seg099 `000a:9748`, stores them at `0x8c5c:0x8c5e` and `0x8c60:0x8c62`, and immediately draws the same seed text buffer `DS:0x631a` at `(0x0a,0x0a)` through both objects. That makes the pair look like two preset text-render variants inside the same temporary presentation lane, not two separate owner objects.
- The shared hold-token consumers now line up more exactly than before. `startup_display_transition_driver` raises `g_active_dispatch_entry_farptr[+0x40]` before the seg049 `+0x2c` dispatch and clears it again before the redraw/follow-up path; `transition_preentry_release_resources` tears down the paired renderers and script buffer, and only on its late completion branch builds local `DS:0x6341` then raises the same shared byte; the mis-split `000c:6176/619c -> 000c:6226` body frees its temporary local animation object, reloads the palette, dispatches `0x2bd8`, and only then clears the shared byte; `FUN_000d_938c` waits on two temporary palette/state entries, redraws, clears the shared byte, and only then dispatches caller vtable `+0x08`; `entity_cleanup_resources_and_dispatch` clears the shared byte only on the late `+0x737` cleanup branch immediately before the same `0x2bd8` dispatch.
- The seg049 controller lane is also slightly tighter locally. `watch_entity_controller_create_global` (`0007:ba00`) delegates to `watch_entity_controller_create` (`0007:ba45`), which stamps type `0x2c2b`, stores the global object at `0x2bd8`, and seeds static row `DS:0x2be4` into `0x39ca[obj+2]`; the common `watch_entity_controller_dispatch_if_present` wrapper (`0007:ba13`) then runs both vtable slots `+0x2c` and `+0x30`. That still supports a real controller object, but not a strong enough state label to rename the wider family.
- The seg108 lane is one step tighter in the same pass. `sprite_redraw_if_needed` (`000b:2492`) remains the redraw-facing helper, while the newly named `sprite_object_push_state_word` / `sprite_object_pop_state_word` pair show that prepare-time use of `0x4f38` is bracketed by a bounded per-object state-word stack at `+0x186/+0x196` rather than by reuse of the validated caller object or the seg049 controller.
Current safest naming conclusion from this batch: keep the existing concrete function names, treat `FUN_000d_938c` and the related seg126/seg138 callers as one shared startup/display presentation handoff family, and defer any stronger single-state or single-owner rename until a caller-side state discriminator appears.
This is enough to treat seg136 as a shared active-dispatch owner/hold-state controller and seg138 as a real cleanup/presentation caller family, even though the final subsystem name is still open.
### Current batch: exact edge waits, interleaved handoff, and broader sprite-stack reuse
- The remaining pure `0x31a2` edge waits are now exact rather than inferred. `0004:c24d` is a two-phase wait that first spins while the shared break/hold depth is non-zero and then spins until it becomes positive again before continuing, while `000c:e4d8` is the simpler positive-edge gate that only waits for `0x31a2 > 0` and immediately returns into the local presentation path.
- `FUN_000d_938c` is now slightly tighter on sequencing even though it still does not justify a rename. The first scratch-palette runtime-state entry (`kind 0x3c`) is only built on the branch where global `0x68e6` is not already in the `0x13:0x0008` mode or entity byte `+0x33` is clear; after that wait completes, the helper clears the seg049 controller bit and dispatches `0x2bd8` through vtable slot `+0x2c`, performs one rectangle/display sync, and only then conditionally builds the current-palette runtime-state entry (`kind 0x14`) before the final redraw, shared hold-byte clear, and caller-object vtable `+0x08` dispatch. That keeps the body inside the same presentation-handoff family while making it less plausible as a single-owner constructor.
- Additional seg108 push/pop callsites now show that the `0x4f38` lane is reused outside the startup prepare shell. Windows `000b:9bb8/9bda` and `000b:9c3c/9c6e` bracket transient seg101 presentation helpers with the same `sprite_object_push_state_word` / `sprite_object_pop_state_word` pair on global sprite object `0x5e82:0x5e84`, while `000c:831f`, `000c:8845`, `000c:8909`, and `000c:a05f` pop that same state stack during later UI or object-cleanup flows. This strengthens the neutral reading that `0x4f38` is a generic sprite-object state stack, not the validated prepare-time caller object and not the `0x2bd8` watch/controller object.
### Current batch: shared hold-token ownership closure
- The highest-value remaining ownership question in the startup/display lane is now narrow enough to close without a speculative rename. `active_dispatch_entry_create_default` remains the canonical installer for `g_active_dispatch_entry_farptr`, while the later seg005/seg126/seg138 bodies only borrow or propagate the shared owner byte `+0x40` as a transition/presentation hold token.
- The set/clear sites now line up as one borrowed-hold family instead of competing owner installs. `startup_display_transition_driver` raises the shared byte at `0004:2013` before the seg049 controller dispatch and clears it at `0004:2128`; `transition_preentry_release_resources` raises it only on the late completion branch at `000c:c963` after building temporary `DS:0x6341`; `FUN_000d_938c` clears it at `000d:958d` after both temporary palette/state waits and redraw; `entity_cleanup_resources_and_dispatch` clears it at `000d:a1cf` only on the late cleanup branch immediately before the same seg049 controller dispatch.
- The seg049 and seg108 lanes stay separate in the exact places that matter for ownership. `watch_entity_controller_dispatch_if_present` (`0007:ba13`) confirms `0x2bd8` is a real controller object dispatched through vtable slots `+0x2c/+0x30`, while `sprite_object_clear_flag40_if_present` / `sprite_object_set_flag40_if_present` (`000b:2b08` / `000b:2b20`) only toggle bit `0x40` in the separate global sprite/object at `0x4f38 + 0x32`.
- The owner/borrow split also remains visible inside the dispatch-entry helpers. `entity_dispatch_entry_init_runtime_state` copies the shared owner byte into new runtime-state entries and re-raises the owner's `+0x40` byte when needed, which matches propagation of borrowed hold state rather than transfer of owner identity.
Current best neutral conclusion from this pass: the shared `g_active_dispatch_entry_farptr[+0x40]` byte is a startup/display presentation hold token borrowed across the seg049 controller lane and later cleanup/handoff bodies; the seg108 `0x4f38` lane is a separate local sprite-object state stack with its own bit-`0x40` contract, not the owner of the shared active-dispatch token.
### Current batch: seg126 control-stream producer tightening and completed `0x31a2` read classes
- The higher-level seg126 control-byte producer is now tighter without breaking the conservative file-backed model. `transition_preentry_run_until_complete_or_abort` (`000c:c9f4`) still has no data/bytecode arguments and is only reached from the local wrappers at `000c:7427` and `000c:0d0d`; both wrappers only stage the surrounding presentation lane before entering the seg126 loop, and neither injects script bytes or a script pointer.
- `transition_preentry_setup_resources` (`000c:c63a`) remains the only verified source of the consumed bytes. It copies the shared mutable base path from `0x6aa:0x6ac`, composes local filenames through the slash-aware seg072 helper `0009:3600` using local buffers `0x631c`, `0x6323`, `0x632c`, and `0x6335`, opens the resulting file through the seg070 file-handle lane, allocates a buffer of the returned size, and reads the full payload into `0x6301:0x6303` before seeding `0x62fa/0x62fc/0x62ff/0x6305/0x630a/0x6318`.
- Neighboring seg126 code now supports the same selector-path reading. Window `000c:b018..b03d` also reloads the same shared base path from `0x6aa:0x6ac` and composes a sibling local filename through `0009:3600` using `0x621c/0x6223`, which makes the startup/display lane look more like a family of file-selected transition assets than a local script-byte emitter.
- The upstream `0x6aa:0x6ac` question is now narrow enough to close as an earlier inherited path lane rather than an in-scope seg126 producer. Literal-address instruction search still finds no store into `0x6aa` or `0x6ac`; the seg004 parser window only mutates the first byte of the pointed buffer at `0004:0ccd` / `0004:0cd8`, while the same parser explicitly installs the sibling root `0x6ae:0x6b0` from parsed input at `0004:0d28..0d2c`. Current best read is therefore: `0x6aa:0x6ac` already points at a mutable external/default base-path buffer before the seg126 startup/display family begins composing filenames on top of it.
- The neighboring seg126 helper family sharpens that close further. Window `000c:afa5..b152` keys object field `+0x49` through local values `0`, `1`, and `4`, composes three sibling filenames from the shared stem buffer `0x621c` plus suffix buffers `0x6223`, `0x622d`, and `0x6237`, loads the selected file into object `+0x520` through seg002 `0004:0098`, then runs the same display/update chain; wrapper `000c:b153..b25f` increments or decrements `+0x49` on selected event codes and re-enters that same loader. This makes the nearby seg126 lane look like a local three-way transition-asset family selector layered on top of the inherited shared base path.
- The overlap check does not force repair yet. `analyze_function_boundaries` still reports `000c:ca1d` as a plausible function entry and `000c:cd53` as the next clean function, while the oversized overlap rooted at `000c:db68` still pollutes the namespace. That overlap no longer blocks byte-behavior or read-site classification, but it still blocks clean function recovery for `transition_preentry_step_script`.
- The in-scope `0x31a2` readers are now classed cleanly by role. `0004:c24d` and `000c:e4d8` are edge waits; `000c:ca11` is the seg126 modal-break exit; `000c:e546`, `000c:e5c6`, and `000d:c0ee` are cleanup-abort exits; `000d:9304` and `000d:b6b1` are deferred dispatch/state-advance gates.
- Two remaining `0x31a2` reads stay outside that presentation classification set. `0005:453d` is only a plain getter wrapper for the shared depth word, and `0008:5149` is a seg008 internal/accounting-side read that adds the current depth to another local count before tripping a `>= 0x10` capacity flag.
---
## Follow-up: `0x4588` Object-Role Evidence
@ -282,7 +374,9 @@ The next ScummVM-guided validation step now confirms that the sampled owner-load
- Scanning with the previously noted ScummVM-style `(base_offset + 19) / 6` interpretation overruns into inline payload/name bytes on these owner-loaded records, so the local sample set does not support that exact event-count formula as written.
- The best current arithmetic fit is now tighter: ScummVM's decremented `base_offset` is also used as the live code-stream base in `uc_machine.cpp`, so the local owner-loaded records fit best if bytes `8..11` are the first code-byte offset and event-count derivation is `(base_offset - 19) / 6`, which is exactly equivalent here to `(raw_u32_at_8_11 - 20) / 6`.
- Current `000d` loader evidence does not point to a header rewrite before VM consumption. `entity_vm_runtime_init_from_path_if_configured` (`000d:44df`) only builds the external path and creates the runtime, `entity_vm_runtime_create` (`000d:4c99`) only installs the helper returned by `000d:7000`, `entity_vm_runtime_owner_resource_create` (`000d:7000`) only allocates the child owner table and fills it through helper vtable `+0x0c`, and `entity_vm_context_create_from_slot_index` (`000d:46ec`) directly reads slot-backed source data from that owner table. No local step is yet verified as rewriting the sampled class headers.
- `entity_vm_runtime_owner_resource_create` (`000d:7000`) still does not expose a direct binary-side class-name lookup or explicit `classid + 2` arithmetic. What it does expose is an indexed file-set loader contract: helper-owned count at `+0x14`, far-pointer table at `+0x10`, paired per-entry word table at `+0x18`, vtable `+0x04` size query, and vtable `+0x0c` materialization of the `0x0d`-stride owner records later consumed by `entity_vm_context_create_from_slot_index`.
- `entity_vm_runtime_owner_resource_create` (`000d:7000`) still does not expose a direct binary-side class-name lookup or explicit `classid + 2` arithmetic. What it does expose is an indexed file-set loader contract: helper-owned count at `+0x14`, far-pointer table at `+0x10`, paired per-entry word table at `+0x18`, vtable `+0x04` size query, and vtable `+0x0c` materialization of the `0x0d`-stride owner records later consumed by `entity_vm_context_create_from_slot_index`. The current pass also makes the helper shape slightly more concrete: the two raw seg070 windows at `0009:67b6` and `0009:6916` are twin per-entry path/read loops with distinct format strings (`DS:3f2d` and `DS:3f40`) but the same `+0x10/+0x18` indexing and file open/read/close lane, which is better evidence for a multi-table or multi-phase external loader than for direct in-memory descriptor iteration.
- The signed slot-offset lane used by the still-xref-dark wrappers `0005:2c35` / `0005:2c68` is also no longer confined to `entity_vm_context_create_from_slot_index` (`000d:46ec`). Inside `entity_vm_runtime_create`, the pre-entry body at `000d:4c25..4c90` reloads object fields `+0x32/+0x34` through `entity_vm_slot_load_value_plus_offset` (`000d:5572`), stores that returned pair into object fields `+0x10c/+0x10e`, and also caches the owner-source far pointer at `+0x117/+0x119`. The paired save path at `000d:49ec` then serializes `+0x10c` through seg070 `0009:2034`, which makes the slot-plus-offset pair a persisted runtime/dispatch state lane rather than a transient wrapper-only argument.
- Additional `0x39ca` consumers are now classified more cleanly. Beyond the already-known static seeds at `000d:7299 -> DS:67f2` and `000d:761c -> DS:6872`, the constructor-like windows at `000d:929a` and `000d:963c` seed rows `DS:68ec` and `DS:68f5` respectively before enabling local timer/dispatch behavior. Those writes behave like dispatch-entry-local static seed rows, not owner-table mirrors. Separately, `FUN_000d_938c` reads temporary dispatch-entry fields `+0x32/+0x34` at `000d:9449..9468` and `000d:9547..9566` only as a wait/poll condition on the scratch-palette (`kind 0x3c`) and current-palette (`kind 0x14`) entries it creates, which further separates active dispatch-entry state from the owner-backed `0x39ca[slot] = {source_off, source_seg}` rows written by `000d:46ec`.
- Safe event-label correlation remains intentionally narrow after this pass. The sampled low slot ids are now concrete, but none of them yet have a verified binary-side behavior match strong enough to promote a ScummVM label like `look`, `use`, or `cachein`.
### Conservative parser rule from this batch

View file

@ -42,7 +42,8 @@ A small helper cluster in the raw `000e:` area implements a fixed-size CRLF reco
- `table_end = 0x6090`, which matches the first non-zero payload offset
- `403` non-zero entries in the current file
- `tools/extract_eusecode_flx.py` now parses the full validated table and emits all `403` non-zero entries under `USECODE/EUSECODE_extracted/`, including `entry_index.tsv`, `descriptor_index.tsv`, `descriptor_neighborhoods.tsv`, `summary.json`, per-chunk `.bin`, and `.strings.txt` sidecars.
- The extractor now also carries the conservative owner-loaded class rule directly into machine-readable outputs: `class_layout_index.tsv` records `object_index`, `class_id`, the raw bytes-`8..11` field, derived `code_base_minus_one`, and `conservative_event_count`, while `class_event_index.tsv` expands parsed classes into raw 6-byte event rows with slot numbers, ScummVM event-name hints for `0x00..0x1f`, unresolved leading words, and raw code-offset dwords.
- The extractor now also carries the conservative owner-loaded class rule directly into machine-readable outputs: `class_layout_index.tsv` records `object_index`, `class_id`, the raw bytes-`8..11` field, derived `code_base_minus_one`, and `conservative_event_count`, while `class_event_index.tsv` expands parsed classes into raw 6-byte event rows with slot numbers, ScummVM event-name hints for `0x00..0x1f`, unresolved leading words, raw code-offset dwords, derived body-window columns, and conservative repeated-template status tags for the verified repeated families.
- The extractor now emits one concrete generated per-class decompile artifact for the cleanest repeated lane too: `boot_family_decompile.md` / `.tsv` render the five `_BOOT` classes slot-by-slot with raw row bytes, derived body windows, repeated-template status, and stable body digests.
- The generated reports now expose lightweight descriptor summaries (`primary_label`, `field_names`, `field_tags`) so the object lane can be searched by field grammar instead of only by raw names.
- The extracted data now separates into at least two lanes:
- text-heavy records that fit the `000e:` CRLF parser model, such as `DATALINK` mission/objective text and `TEXTFIL1` message banks
@ -129,6 +130,19 @@ A small helper cluster in the raw `000e:` area implements a fixed-size CRLF reco
- The environmental hazard lane is now similarly constrained. `environmental_family_compare.tsv` shows that `FLAMEBOX` and `STEAMBOX` are close structural siblings with the same active-event backbone (`referent,event,<hazard>,<hazard2>,direction,count`) and matching `24:0A02 / 24:FC02 / 24:FE02` object-link pattern, while `NOSTRIL` is a smaller fire-specific variant that keeps the active `event` plus dual fire references and count fields but drops the direction/newType side.
- Their neighborhoods are different enough to matter: `environmental_event_graph.md` shows `FLAMEBOX` embedded among vent/door/bridge/copy records, `NOSTRIL` among flame/pad/desk/blaster/keypad records, and `STEAMBOX` among bounce/hover/fade/steam/flame box records. So this looks like one hazard-event descriptor family reused across distinct local object islands rather than one single environmental mega-cluster.
- The callback lane is tighter too. `callback_trigger_compare.tsv` confirms that `SURCAMNS` and `SURCAMEW` are effectively the same callback-trigger template: identical field set (`referent,textFile,monit,valueBox,passcode,link,code,screen,cameraEgg,trueRef,therma,eventTrigger,foundGun`) and identical tag grammar except for the `therma` slot offset (`24:F102` vs `24:F602`). That keeps the `eventTrigger` split credible as a true callback/attachment lane rather than only a spelling variation on active `event` carriers.
- Mining the new `class_layout_index.tsv` / `class_event_index.tsv` outputs now gives a first small safe set of repeated non-zero slot patterns:
- `JELYHACK` and `JELYH2` are exact referent-anchor twins at the event-table level too: both have only slot `0x01` non-zero, with the same row `0x002A / 0x00000001`.
- The five `_BOOT` event cores (`AND_BOOT`, `BRO_BOOT`, `COR_BOOT`, `REE_BOOT`, `VAR_BOOT`) all share the same three-slot pattern `0x0A / 0x0F / 0x10`. The clearest exact repeated row is slot `0x10`, where all five use `raw_event_entry_word = 0x003B` with class-specific code offsets.
- `SURCAMNS` and `SURCAMEW` share one exact five-slot callback pattern `0x01 / 0x0A / 0x20 / 0x21 / 0x22`, including the same `0x0A = 0x00D1 / 0x00000001` anchor row and the same `0x22` event-table word `0x01A3`.
- `FLAMEBOX`, `NOSTRIL`, and `STEAMBOX` share one environmental-event pattern `0x0A / 0x20 / 0x21`, which is enough to treat the higher slots as real repeated structure even though the exact row values differ by class.
- `EVENT` and `SFXTRIG` both participate in the wide `0x0A` lane, but that lane is broad enough that the slot number is currently more trustworthy than the ScummVM label attached to it.
- The next body-window pass now confirms that repeated slot rows are usually near-templates rather than clones. Using `body_start = code_base_minus_one + raw_code_offset` and the next non-zero slot offset or chunk EOF as the body end:
- `JELYHACK` and `JELYH2` slot `0x01` are both `42` bytes long with a shared `10`-byte prefix and `17`-byte suffix, but are not byte-identical.
- `_BOOT` slot `0x10` is a clean short-template lane: all five bodies are exactly `59` bytes long, share the same first `5` bytes and last `17` bytes, but each has a distinct digest.
- `_BOOT` slots `0x0A` and `0x0F` are larger variants of the same pattern: shared suffix-heavy structure, class-local middles, no exact clones.
- `SURCAMNS` and `SURCAMEW` slots `0x20` and `0x22` are same-length near-templates (`698` and `419` bytes respectively), while slot `0x21` diverges more strongly (`1801` vs `1621` bytes) even though it still keeps a common tail.
- That makes the current best human-readable script model more precise: preserve repeated-family status and exact row bytes, but record byte-identity as a separate property so “same slot template” does not get mistaken for “same compiled body.”
- That pattern pass materially improves what a decompiled USECODE script can look like right now. The honest current form is not a pretty source language; it is a reversible descriptor-plus-event-table rendering with raw slot ids, raw event-entry words, raw code offsets, and optional ScummVM labels marked as hints only. The concrete examples now live in `docs/usecode-roundtrip-ir.md` and are grounded in `readable_script_ir.md`, `readable_descriptor_templates.md`, and `runtime_descriptor_family_rankings.md`.
- The first runtime-side follow-through on those descriptor gains is now a little tighter too. Instruction search around `000d:ebe3` confirms one fixed sequenced VM/opcode driver body, not just a vague constructor helper: it calls `000d:177c`, `000d:1acb`, `000d:0988`, the internal `000d:22bc` link-matrix block, then `000d:1d4a` and `000d:2104` in order. The key negative result is just as useful: `000d:ec31` is only the internal `CALL 000d:22bc` site inside that body, not a standalone function entry.
- Ghidra now carries that as a conservative disassembly comment at `000d:ebe3`. That is still short of a safe rename, but it does promote the lane from “suspected constructor chain” to “verified ordered opcode/handler sequence,” which is the clearest current bridge from the descriptor-side event families back into the `000d` VM/object runtime.
@ -191,17 +205,17 @@ All three constructor variants (`000e:2777`, `000e:2860`, `000e:2969`) follow th
1. Call `FUN_000e_e935` (allocator — produces garbled 11KB decompile, not renamed)
2. Set fields `+0xb4` through `+0xc2` on the result
3. Call `000d:ebe3` directly (confirmed CALL sites at `000e:283e`, `000e:2931`, `000e:29e4`; multi-step chain initializer: calls `177c`, `1acb`, `0988`, `22bc`, `1d4a`, `2104` in sequence)
3. Call near target `000e:ebe3` directly (confirmed CALL sites at `000e:283e`, `000e:2931`, `000e:29e4`; this is a separate mis-split `000e` region, not `FUN_000d_ebe3`)
4. Call `assert_alive_sentinel` (assertion: checks `+0xd4 != -1`)
5. Call `func_0x000eec83`
The chain at `000d:ebe3` steps through VM opcode handlers (`000d:177c`, `000d:1acb`, `000d:0988`) that operate on a bytecode VM object with stack pointer at `+0xcc` (decremented by 2 per push) and segment base at `+0xce`.
The old assumption that these constructor calls fed the `000d` VM sequencer is now retired. Raw instruction search shows the direct near calls land on `000e:ebe3`, whose current body is still mis-split/garbled and cannot yet be tied to the `000d:177c` / `000d:1acb` / `000d:0988` / `000d:22bc` / `000d:2104` chain.
The constructor-side field setup before that sequencer is now slightly tighter too:
- variants A and B both set `+0xc0 = 1` before the direct `000d:ebe3` call and derive `+0xc2` from `DS:0x604e`
- variant C instead sets `+0xc0 = 0`, `+0xc2 = 1`, and `+0x4c = 0x000d` before the same sequencer call
- these direct xrefs make `000d:ebe3` a constructor-side animation sequencer rather than a globally xref-dark dispatcher, but they still do not expose any new wrapper-level opcode number beyond the internal `0x19/0x1a/0x1b` family already proven inside `000d:0988`
- variants A and B both set `+0xc0 = 1` before the direct `000e:ebe3` call and derive `+0xc2` from `DS:0x604e`
- variant C instead sets `+0xc0 = 0`, `+0xc2 = 1`, and `+0x4c = 0x000d` before the same near-call lane
- this remains useful for the animation subsystem, but it no longer counts as upstream xref evidence for `FUN_000d_ebe3`; the true selector/write path into the `000d` dispatcher is still unresolved
### Constructor variant renames

View file

@ -234,6 +234,315 @@ For near-term local RE and tooling:
- Treat `JELYHACK` and `JELYH2` as referent-anchor classes, not standalone event records.
- Treat `SURCAMNS` and `SURCAMEW` as callback/eventTrigger holders, not proven active-event cores.
## Repeated Slot Patterns Safe To Reuse Now
The latest pass over `class_layout_index.tsv` and `class_event_index.tsv` adds a small set of repeatable slot patterns that are safe enough to carry into decompiler output.
What is authoritative here:
- whether a class has a non-zero slot entry at a given slot id
- the raw `u16` event word for that slot
- the raw `u32` code offset for that slot
- repeated slot-set structure across several classes
What is still hint-level only:
- the ScummVM event-name labels for slots `0x00..0x1f`
- any mapping from one repeated slot directly to one recovered `000d` opcode family
- any claim that one repeated slot family is already tied to one exact gameplay subsystem in the DOS binary
Current small safe candidate sets:
| Family | Classes | Non-zero slots | Safe implication |
|---|---|---|---|
| referent-anchor twin | `JELYHACK`, `JELYH2` | `0x01` only | these are structurally anchor-only classes, not active event hubs |
| boot-event-core | `AND_BOOT`, `BRO_BOOT`, `COR_BOOT`, `REE_BOOT`, `VAR_BOOT` | `0x0A`, `0x0F`, `0x10` | one reusable three-slot active-event core template |
| callback-eventtrigger | `SURCAMNS`, `SURCAMEW` | `0x01`, `0x0A`, `0x20`, `0x21`, `0x22` | one shared callback-oriented multi-slot template |
| environmental-event | `FLAMEBOX`, `NOSTRIL`, `STEAMBOX` | `0x0A`, `0x20`, `0x21` | one shared hazard/event template with two extra high slots |
| broad active-event lane | `EVENT`, `SFXTRIG`, and several non-island classes | `0x0A` only | slot `0x0A` is widespread enough to treat as a real repeated event slot, but too broad to over-specialize |
Concrete repeated evidence worth preserving in IR:
- `JELYHACK` and `JELYH2` both carry only slot `0x01` with the exact same row: `raw_event_entry_word = 0x002A`, `raw_code_offset = 0x00000001`.
- The five `_BOOT` cores all share slot `0x10` with the exact same `raw_event_entry_word = 0x003B`, while the `raw_code_offset` varies by class (`0x0000045c` on `COR_BOOT`, `0x0000048b` on `AND_BOOT`, `0x00000522` on `BRO_BOOT`, `0x000004df` on `VAR_BOOT`, `0x000005a8` on `REE_BOOT`). That is a good example of repeated structure without identical bodies.
- `SURCAMNS` and `SURCAMEW` share the same five-slot layout and the same low/high anchor rows (`0x0A = 0x00D1/0x00000001`, `0x22 = 0x01A3/...`), but differ in the middle high-slot offsets. That looks like one shared callback template with instance-specific bodies, not two unrelated classes.
- `FLAMEBOX`, `NOSTRIL`, and `STEAMBOX` all share one `0x0A` low slot plus two extra high slots `0x20` and `0x21`. Their exact words differ, so the safe reading is shared layout, not identical compiled behavior.
- `EVENT` and `SFXTRIG` both participate in the wide `0x0A` lane, but that family is broad enough that the slot number is more trustworthy than the ScummVM name hint.
## Byte-Level Body Comparison Rules And Results
The next step after repeated row mining is to derive the chunk-local body window for each non-zero slot and compare the actual bytes instead of only the 6-byte event-table row.
Current conservative body-window rule:
- `body_start = code_base_minus_one + raw_code_offset`
- `body_end = code_base_minus_one + next_non_zero_raw_code_offset` in the same class, or chunk EOF when there is no later non-zero slot
- this keeps the representation reversible because it is computed only from preserved header and event-table fields plus the raw chunk bytes
This rule is now carried directly by the extractor outputs instead of living only in notes:
- `USECODE/EUSECODE_extracted/class_event_index.tsv` now emits `derived_body_start`, `derived_body_end`, `derived_body_length`, and conservative `repeated_template_status` columns per slot row.
- `USECODE/EUSECODE_extracted/boot_family_decompile.md` / `.tsv`, `callback_family_decompile.md` / `.tsv`, and `environmental_family_decompile.md` / `.tsv` now provide concrete generated per-class decompile artifacts for the `_BOOT`, `SURCAM*`, and environmental repeated-family lanes, each grounded in emitted output rather than prose-only examples.
- `USECODE/EUSECODE_extracted/repeated_family_regressions.tsv` now records and enforces the current repeated-family slot sets plus the verified raw-row and derived body-window fields for `JELYHACK/JELYH2`, `_BOOT`, `SURCAMNS/SURCAMEW`, and `FLAMEBOX/NOSTRIL/STEAMBOX` so extractor changes fail fast if those verified baselines drift.
What this confirms on the current repeated families:
- `JELYHACK` and `JELYH2` slot `0x01` are exact row twins but not exact body twins. Both bodies are `42` bytes long, both start at `0x00d4`, both keep `raw_event_entry_word = 0x002A`, and both share a `10`-byte prefix plus a `17`-byte suffix. The first differences are at body offsets `10,11,12,24`, which is consistent with one reused mini-template carrying class-local literals rather than one identical compiled body.
- `_BOOT` slot `0x10` is the cleanest repeated-body example. All five classes have a `59`-byte body, all share the same row word `0x003B`, all share the same first `5` bytes and the same last `17` bytes, and none are byte-identical across the family. This is strong evidence for one shared short-template tail with class-local identifiers or immediates in the middle.
- `_BOOT` slots `0x0A` and `0x0F` show the same pattern at larger sizes. Slot `0x0A` bodies range from `551` to `843` bytes and share only a `3`-byte prefix but a `39`-byte suffix; slot `0x0F` bodies range from `564` to `604` bytes and share a `3`-byte prefix plus a `38`-byte suffix. These are repeated family bodies, but not clones.
- `SURCAMNS` and `SURCAMEW` high slots `0x20` and `0x22` also behave like near-templates, not clones. Slot `0x20` is `698` bytes in both classes with an `11`-byte common prefix and an `84`-byte common suffix. Slot `0x22` is `419` bytes in both classes with an `11`-byte common prefix and a `53`-byte common suffix.
- `SURCAM` slot `0x21` is the strongest within-family divergence in this batch. `SURCAMNS` uses row word `0x0709` and a body length of `1801`, while `SURCAMEW` uses row word `0x0655` and a body length of `1621`. They still share a `20`-byte suffix, so this is best read as one callback-family slot with materially different instance bodies rather than a parsing mistake.
The practical IR consequence is important: repeated-family status should be recorded separately from byte-identity status. A human-readable decompile should be able to say “same family slot template” without falsely implying “same body bytes.”
## What A Decompiled Script Looks Like Today
The most honest present-day decompilation is not a polished source language. It is a reversible descriptor-plus-event-table rendering with optional VM-op vocabulary attached where the `000d` lane is already verified.
### Level 0: Raw event row plus derived body window
This is the minimal human-usable row form. It preserves the original six-byte event entry, explains how the body window is derived, and records whether the slot looks like an exact twin, a near-template, or a unique body.
```yaml
class_name: REE_BOOT
slot: 0x10
event_name_hint_scummvm: leaveFastArea
raw_event_entry_word: 0x003b
raw_code_offset: 0x000005a8
code_base_minus_one: 0x00d3
derived_body_start: 0x067b
derived_body_end: 0x06b6
derived_body_length: 59
repeated_template_status: boot-event-core/shared-slot-0x10
body_identity_status: non-identical; shared 5-byte prefix and 17-byte suffix across all five _BOOT bodies
body_sha1: 577c61e9c4c6...
```
Field meaning, using only what is currently verified:
- `class_name`: authoritative class label from object `1` in the owner-loaded class table
- `slot`: authoritative numeric slot id from the event table; this is safer than any guessed semantic name
- `event_name_hint_scummvm`: external label for slots `0x00..0x1f`; useful for orientation, not yet verified as the local class-specific meaning
- `raw_event_entry_word`: the unresolved leading `u16` from the 6-byte event record; authoritative bytes, unresolved semantics
- `raw_code_offset`: the authoritative row `u32`; currently best read as a 1-based offset relative to `code_base_minus_one`
- `code_base_minus_one`: derived from bytes `8..11` in the class header using the current conservative rule
- `derived_body_start` and `derived_body_end`: computed chunk-local byte window for the slot body; useful for diffing and future recompilation, and now emitted directly in the extractor outputs
- `repeated_template_status`: whether the row participates in a repeated family pattern such as `JELY` anchor twin, `_BOOT` event core, or `SURCAM` callback template
- `body_identity_status`: whether the extracted body bytes are exact twins, near-templates, or materially different within that family
- `body_sha1`: stable digest for exact identity checks without pretending the digest itself has semantic meaning
### Level 1: Lossless event-table IR
This is the form that is closest to a future round-trip compiler.
```yaml
class:
entry_index: 0x0115
class_id: 0x04d3
class_name: JELYHACK
class_object_index: 0x04d5
raw_code_base_u32: 0x00d4
code_base_minus_one: 0x00d3
conservative_event_count: 32
descriptor_fields:
- referent
events:
- slot: 0x01
event_name_hint_scummvm: use
raw_event_entry_word: 0x002a
raw_code_offset: 0x00000001
derived_body_start: 0x00d4
derived_body_end: 0x00fe
derived_body_length: 42
repeated_template_status: referent-anchor-twin/shared-slot-0x01
body_identity_status: near-template-with-JELYH2
confidence: authoritative-bytes, hinted-label
```
That is already a real decompilation output. It keeps the exact slot id, the exact six-byte row contents, and the exact class-header facts, while refusing to pretend that `use` is already a proven semantic name for this class.
Here is the same style for one active event-bearing attachment class in the same island:
```yaml
class:
entry_index: 0x011b
class_id: 0x04db
class_name: REE_BOOT
class_object_index: 0x04dd
raw_code_base_u32: 0x00d4
code_base_minus_one: 0x00d3
conservative_event_count: 32
descriptor_fields:
- referent
- event
- counter
- item
events:
- slot: 0x0a
event_name_hint_scummvm: equip
raw_event_entry_word: 0x034b
raw_code_offset: 0x00000001
derived_body_start: 0x00d4
derived_body_end: 0x041f
derived_body_length: 843
repeated_template_status: boot-event-core/shared-slot-0x0a
body_identity_status: same-family-body-not-identical
confidence: authoritative-bytes, hinted-label
- slot: 0x0f
event_name_hint_scummvm: enterFastArea
raw_event_entry_word: 0x025c
raw_code_offset: 0x0000034c
derived_body_start: 0x041f
derived_body_end: 0x067b
derived_body_length: 604
repeated_template_status: boot-event-core/shared-slot-0x0f
body_identity_status: same-family-body-not-identical
confidence: authoritative-bytes, hinted-label
- slot: 0x10
event_name_hint_scummvm: leaveFastArea
raw_event_entry_word: 0x003b
raw_code_offset: 0x000005a8
derived_body_start: 0x067b
derived_body_end: 0x06b6
derived_body_length: 59
repeated_template_status: boot-event-core/shared-slot-0x10
body_identity_status: same-family-body-not-identical
confidence: authoritative-bytes, hinted-label
```
And here is one callback-style multi-slot class, which shows why the high slots should stay numeric for now:
```yaml
class:
entry_index: 0x011c
class_id: 0x04de
class_name: SURCAMEW
class_object_index: 0x04e0
raw_code_base_u32: 0x00e6
code_base_minus_one: 0x00e5
conservative_event_count: 35
descriptor_fields:
- referent
- textFile
- monit
- valueBox
- passcode
- link
- code
- screen
- cameraEgg
- trueRef
- therma
- eventTrigger
- foundGun
events:
- slot: 0x01
event_name_hint_scummvm: use
raw_event_entry_word: 0x00f7
raw_code_offset: 0x000000d2
- slot: 0x0a
event_name_hint_scummvm: equip
raw_event_entry_word: 0x00d1
raw_code_offset: 0x00000001
- slot: 0x20
event_name_hint_scummvm: null
raw_event_entry_word: 0x02ba
raw_code_offset: 0x000001c9
derived_body_start: 0x02ae
derived_body_end: 0x0568
derived_body_length: 698
repeated_template_status: callback-eventtrigger/shared-slot-0x20
body_identity_status: same-family-body-not-identical
- slot: 0x21
event_name_hint_scummvm: null
raw_event_entry_word: 0x0655
raw_code_offset: 0x00000483
derived_body_start: 0x0568
derived_body_end: 0x0bbd
derived_body_length: 1621
repeated_template_status: callback-eventtrigger/shared-slot-0x21
body_identity_status: same-family-body-not-identical
- slot: 0x22
event_name_hint_scummvm: null
raw_event_entry_word: 0x01a3
raw_code_offset: 0x00000ad8
derived_body_start: 0x0bbd
derived_body_end: 0x0d60
derived_body_length: 419
repeated_template_status: callback-eventtrigger/shared-slot-0x22
body_identity_status: same-family-body-not-identical
```
The extra derived fields are worth keeping because they answer the immediate human question that the bare event table does not: not only “which slots exist,” but also “how much body belongs to each slot” and “whether this body is a true clone or only a same-family variant.”
### Level 2: Friendly but still reversible hinted form
This is the highest-level script shape that is justified right now.
```text
anchor JELYHACK(referent)
# authoritative event rows for the anchor itself
slot 0x01 hint=use? raw_word=0x002A code_off=0x00000001 body=0x00D4..0x00FE family=JELY-anchor identity=near-template-with-JELYH2
# nearby attachment classes from the same local island
attach REE_BOOT(referent,event,counter,item)
slot 0x0A hint=equip? raw_word=0x034B code_off=0x00000001 body=0x00D4..0x041F family=_BOOT-core identity=shared-template-not-clone
slot 0x0F hint=enterFastArea? raw_word=0x025C code_off=0x0000034C body=0x041F..0x067B family=_BOOT-core identity=shared-template-not-clone
slot 0x10 hint=leaveFastArea? raw_word=0x003B code_off=0x000005A8 body=0x067B..0x06B6 family=_BOOT-core identity=shared-template-not-clone
callback SURCAMEW(referent,textFile,monit,valueBox,passcode,link,code,screen,cameraEgg,trueRef,therma,eventTrigger,foundGun)
slot 0x01 hint=use? raw_word=0x00F7 code_off=0x000000D2 body=0x01B7..0x02AE
slot 0x0A hint=equip? raw_word=0x00D1 code_off=0x00000001 body=0x00E6..0x02AE
slot 0x20 raw_word=0x02BA code_off=0x000001C9 body=0x02AE..0x0568 family=SURCAM-callback identity=shared-template-not-clone
slot 0x21 raw_word=0x0655 code_off=0x00000483 body=0x0568..0x0BBD family=SURCAM-callback identity=shared-template-with-stronger-divergence
slot 0x22 raw_word=0x01A3 code_off=0x00000AD8 body=0x0BBD..0x0D60 family=SURCAM-callback identity=shared-template-not-clone
attach SFXTRIG(referent,event)
slot 0x0A hint=equip? raw_word=0x00B8 code_off=0x00000001
```
This is decompiled enough to read, diff, and later recompile because it preserves:
- the original class identity
- the exact non-zero event rows
- the derived chunk-local body window for each row
- which names are authoritative fields versus external hints
- which nearby descriptors appear to be anchors, active event attachments, or callback attachments
- whether a repeated family slot is an exact twin or only a structurally similar body
### Level 2.5: Human annotation layer
The last layer is prose, not syntax. It should explain the honest current reading of each field so a modder can see what is safe to edit and what still needs caution.
- Class name is authoritative at the container level: it comes from the owner-loaded class-name table and is not a guess.
- Slot id is authoritative at the event-table level: this is the safest event identifier currently available.
- Event-name hint is external: use it as orientation only when the slot is inside `0x00..0x1f` and the local behavior has not yet been reverified in binary.
- Raw event word is authoritative but semantically unresolved: it must survive round-trip intact.
- Raw code offset is authoritative and operational: combined with `code_base_minus_one`, it tells us where the slot body starts in the chunk.
- Body-window length is derived but useful: it tells a human whether a slot is a tiny stub-like record or a large body that deserves its own diff or annotation block.
- Repeated-template status is about family structure, not byte identity: a `_BOOT` slot can be “the same template role” without being byte-equal across classes.
- Body-identity status answers the concrete modding question “am I looking at a clone, a parameterized variant, or a different body that only occupies the same family slot?”
### Level 3: Where the current VM IR can be attached
For classes in the active-event ecosystems (`EVENT`, `_BOOT`, `NPCTRIG`, `SFXTRIG`, and the environmental family), the current `000d` work is strong enough to attach the known operator vocabulary without pretending one exact class-to-opcode decode already exists.
```text
vm_effect_possible:
APPEND_UNIQUE_INLINE
APPEND_UNIQUE_INDIRECT
REMOVE_MATCHING_INDIRECT
REMOVE_MATCHING_INLINE
MATERIALIZE_OR_FORWARD_VALUE
PREPEND_INLINE_PAYLOAD
BUILD_ENTITY_LINK_MATRIX
EMIT_OR_PUSHBACK_RESULT
FINALIZE_MIXED_VALUE_TO_OUTPTR
```
That operator block is authoritative as a recovered VM vocabulary, but only ecosystem-level when attached to one specific descriptor family.
## Conservative Parser Rule To Adopt Now
For the current owner-loaded EUSECODE and round-trip IR work, the safest reversible rule is:

View file

@ -2,427 +2,188 @@
## Purpose
This file is the workspace-facing mid-project tracker for the Crusader decompilation effort.
It is intended to answer four questions clearly:
This file is the live mid-project tracker for the Crusader decompilation effort.
1. How far along is the project?
2. What is already solid?
3. What still blocks broader decompilation?
4. What should be implemented next?
Keep it focused on:
The estimates below are intentionally conservative. They measure verified behavioral understanding, not just renamed symbols.
1. current verified state,
2. active blockers,
3. next resume work,
4. and the remaining path to a reasonably complete decompilation.
Detailed completed analysis belongs in the files under `docs/`, not in this plan.
## Progress Snapshot
## Working Progress
### Last Confirmed State
- Priority 0 has started: `crusader_segment_coverage_ledger.csv` exists and contains a first-pass 145-row ledger.
- The currently seeded ledger rows are conservative and strongest around seg001, seg004, seg021, seg043, seg080, seg082/083/085, seg091, seg094, and seg095.
- Priority 1 has started on the cache/backend cluster: the seg082 allocator mechanics are now materially recovered (`allocator_head_try_alloc_block`, `allocator_head_free_block`, `allocator_free_block_by_ptr`, `allocator_try_alloc_from_head_table`, `allocator_phase_finalize_pass`), and the `0x4588` path now has named lifecycle helpers (`runtime_callback_object_init_once`, `runtime_callback_object_teardown_once`).
- The `0x4588` blocker is tighter than before: `000a:b988` boundary repair now includes both callback sync callsites (`000a:b9e5` / `000a:ba66`) inside one real function body, `000d:9d5e` / `000d:a3b7` are confirmed inside `entity_cleanup_resources_and_dispatch`, and adjacent helpers are now clarified as `allocator_head_finalize_sweep` (`0009:a961`), `video_bios_state_snapshot` (`000a:4a1f`), and `video_mode_set_and_record_state` (`000a:4972`). Concrete subsystem identity is still unresolved.
- A larger MCP rename batch completed for cleanup callees: `palette_buffer_alloc_and_init_256` (`0009:7853`), `file_handle_alloc_init_and_open` (`0009:1c3a`), `file_handle_open_with_mode` (`0009:1d6a`), `surface_release_internal` (`0009:8d7b`), `surface_release_and_maybe_free` (`0009:8e0a`), and `sprite_redraw_global_if_active` (`000d:9231`). This reduces `entity_cleanup_resources_and_dispatch` ambiguity on file/surface/palette teardown paths.
- The previously missing `000d:7e00` function object is now recovered and named `entity_dispatch_entry_init_runtime_state`, with paired destructor `entity_dispatch_entry_release_runtime_state` at `000d:8078`. Adjacent missing helpers `0003:a880` and `0003:b8e2` were also recovered, with `0003:b8e2` promoted to `far_buffer_alloc_with_mode_flags`.
- Additional helper stabilization now covers seg061/064/076: `vga_palette_read` (`0009:6ec7`) is confirmed alongside existing palette write/free paths, `timer_entity_enable_wrapper` (`0008:d3ba`) is named, and seg064 one-shot gate helpers around `0x3b72/0x3b73` are documented with conservative comments while keeping speculative naming deferred.
- Constructor-lane semantics tightened further: `entity_set_update_period_and_reschedule` (`0008:d27e`) and `palette_buffer_alloc_copy_from_source` (`0009:7905`) are now named, and both `0x4588` callback emit callsites (`000d:9d5e`, `000d:a3b7`) now have explicit payload-pair annotations in disassembly.
- The seg082 allocator table structure is now pinned down as the allocator head table at `0x8724` and active head count at `0x879c`, and the old structural helpers at `0009:b06b` / `0009:b1c3` are now promoted to `allocator_try_alloc_from_head_table` and `allocator_phase_finalize_pass`.
- New caller-side seg138 evidence now exists at `FUN_000d_938c` (`000d:938c-000d:9583`): it builds one scratch-palette dispatch entry (`kind 0x3c`) and one current-palette dispatch entry (`kind 0x14`) through `entity_dispatch_entry_init_runtime_state`, waits for each entry's active flag to clear, then redraws the global sprite path and dispatches through the input object's vtable slot `+0x08`. This narrows the open lane to presentation/dispatch semantics without yet justifying a concrete subsystem rename.
- seg137 is now promoted from `Foothold` to `Partial`: direct MCP recovery stabilized a coherent palette/dispatch-entry helper family with safe renames for all-black, all-white, arbitrary-RGB, grayscale, black-state, and solid-color state builders around the same `entity_dispatch_entry_init_runtime_state` lane. The remaining gap is the higher-level event/script meaning of those helpers, not the local mechanics.
- seg005 and seg136 now have new high-value footholds: `FUN_0004_60c0` is recovered as a startup/display orchestration handoff that drives the seg137 palette helper family, validates an object through vtable `+0x0c`, creates the default active dispatch entry, programs mouse state, and then hands off into `0004:1e00`; nearby seg136 helpers are now stabilized as `active_dispatch_entry_mark_enabled`, `active_dispatch_entry_mark_disabled`, and `active_dispatch_entry_create_default`.
- The downstream seg005 handoff body is now also classified further: `FUN_0004_1e00` (`0004:1e00-0004:2420`) is a non-return startup/display transition driver with confirmed use of `vga_palette_set_all_black`, `animation_ctor_variant_b`, `sprite_node_get_or_traverse`, seg064 gate helpers, the `0x2bd8` vtable lane, and the `0x4aa/0x7e22` resource/object lane. The remaining work is naming the exact state label, not repairing the structure.
- seg126 is now promoted from `Foothold` to `Partial`: `FUN_000c_7412`, `transition_preentry_setup_resources`, `transition_preentry_release_resources`, `transition_preentry_run_until_complete_or_abort`, `transition_preentry_step_script`, `thunk_callf_0000_ffff_000c_827d`, `thunk_callf_0000_ffff_000c_82f9`, and `FUN_000c_834a` now show a coherent pre-entry, guarded-entry, script/fade step, and post-transition control shell around the same `FUN_0004_1e00` startup/display state.
- seg127 is now promoted from `Foothold` to `Partial`: `palette_fade_begin_full_up`, `palette_fade_begin_full_down`, `transition_palette_fade_begin`, `transition_palette_fade_tick`, `transition_palette_fade_out_step`, and `transition_palette_fade_in_step` form a concrete local palette-fade controller with verified full-range wrappers and caller-side state gating immediately beside the same seg126/seg005 transition lane.
- seg049 is no longer blank: `watch_entity_controller_create_global`, `watch_entity_controller_create`, and `watch_entity_controller_dispatch_if_present` now show that `0x2bd8` is a real type-stamped watch/camera controller object lane rather than only a raw watched-entity pointer, and that same controller is exercised from `FUN_0004_1e00`.
- seg108 is no longer blank: `sprite_object_clear_flag40_if_present` and `sprite_object_set_flag40_if_present` now anchor the `0x4f38` global sprite/object lane as a real state-bit-controlled object path used beside the same `0x4588` callback sync and startup/display transition flow.
- Direct MCP follow-up on seg126 and seg127 now recovered the missing helper bodies after boundary repair: `transition_preentry_setup_resources` (`000c:c63a`), `transition_preentry_release_resources` (`000c:c890`), `transition_preentry_run_until_complete_or_abort` (`000c:c9f4`), `transition_preentry_step_script` (`000c:ca1d`), and the neighboring `transition_palette_fade_tick` / `transition_palette_fade_begin` / `transition_palette_fade_out_step` / `transition_palette_fade_in_step` chain are now named against verified behavior. The latest semantic pass also tightened the two main open globals: `0x8c5c` / `0x8c60` are now best understood as a paired temporary text-renderer lane, while `0x31a2` behaves like an external input/event break gate maintained by queue/interrupt-side code. The remaining structural cleanup is the separate oversized overlap rooted at `000c:db68`, not the seg126 helper family.
- Bonus cheat-lane cleanup is now visible in Ghidra too: `cheat_code_check` has recovered local names (`input_event_record`, `input_event_offset`, `new_cheat_enabled`, `cheat_status_display_root`) and a decompiler comment stating that it matches the five-byte event-code sequence `50 80 3e fd 27 00` before toggling the cheat-state bytes and taking one of two local notification paths.
- Point 8 cheat/input-lane pass is complete. `keyboard_input_cheat_dispatch` (`0007:04dc`) is renamed and has a full scan-code mapping decompiler comment. `cheat_entity_slot_cycle_and_update_sprite` (`000c:8072`) and `cheat_anim_type_cycle_and_refresh` (`000c:81c0`) are named. Three `DS:0x6050` gate helpers (`000c:8221/8227/822b`) are named. All seven cheat event case-handlers in the 000c dispatch function now have labels and disassembly comments (`event_0x141/0x241/0x441_cheat_debug_overlay_toggle`, `event_0x7e_cheat_latch_runtime_toggle`, `event_0x142/0x143_cheat_fullscreen_mode1/0_refresh`, `event_0x410_cheat_flag_604f_toggle`). The cheat-related string table in seg014 is documented (including the dev Easter-egg `"FART ...TRY... -laurie"`). HACK MOVER / Immortality strings confirmed present with no static code xrefs — attributed to USECODE scripting layer. `0x844` (master cheat flag) vs `0x6045` (live cheat latch) separation remains solid.
- User-directed JELYHACK producer tracing is now tightened one layer upstream of `000d:208b` / `000d:21ed`: the immediate stream producer is the embedded mini-VM object created at context `+0x36`. `entity_vm_context_create_from_slot_index` (`000d:46ec`) feeds that object through `entity_vm_context_setup` (`000c:f844`), which uses `entity_vm_stack_init_with_data` (`000c:f6e8`) and `entity_vm_state_copy` (`000c:f772`) semantics to seed or clone `[+0xcc..+0xd2]`. The actual source payload comes from the runtime owner table at `0x6611 -> +0x1315/+0x1317 -> +0x10/+0x12`, addressed as `base + 0x0d*slot + 4`, and the resulting per-slot source is mirrored into `0x39ca`. This still does not expose a direct `JELYHACK`-named producer object, but it strengthens the current reading that `JELYHACK` / `JELYH2` contribute referent identity while neighboring `REE_BOOT` / `SURCAMEW` / `SFXTRIG` descriptors remain better candidates for event-bearing attachments.
- The next USECODE/JELYHACK pass now resolves the immediate owner-object writer too. `entity_vm_runtime_create` (`000d:4c99`) is the only writer of runtime `+0x1315/+0x1317`, via newly recovered `entity_vm_runtime_owner_resource_create` (`000d:7000`), and the companion `entity_vm_runtime_owner_resource_destroy` (`000d:70fd`) releases that helper. The `000d:7000` body does not copy a caller-supplied table directly: it constructs one embedded seg069/070 helper object, queries that helper for the required table size via vtable `+0x04`, allocates child `+0x10/+0x12`, then populates the `0x0d`-stride per-slot producer records through vtable `+0x0c`. Wrapper classification around `entity_vm_context_try_create_masked_for_entity` is tighter too: local wrapper `0004:f033` uses slot mask `0x8000:0007`, `FUN_0004_f05c` uses `0x2000:0015` and is reached from `0004:f2b3` after overlap/proximity and entity byte `+0x32` state checks, and `FUN_0005_27a4` uses `0x0001:0000` from the `000c:a09e` entity `+0x5b` bit-`0x0004` branch. This is enough for a conservative owner/resource classification, but not yet for a source-format-specific or descriptor-specific rename beyond that partial role name.
- The seg091 `000a:44fd/454d/45fe` cluster is now corrected too: these are reentrancy-guarded fatal-report helpers, not a wrapper selector or live event-dispatch lane. In particular, `entity_vm_slot_load_value` (`000d:51fd`) reaches `000a:44fd` via `PUSH 0x410; PUSH DS; PUSH 0x6616; CALLF 000a:44fd`, which now reads as an error/assert report path rather than a gameplay immortality-event producer.
- The `000d:7000` owner/resource helper is one step tighter as well: its embedded seg069/070 object is file-backed rather than abstract. Construction starts with `dos_file_handle_init` (`0009:1c00`), then uses helper vtable slot `+0x04` as the size query for the child `+0x10/+0x12` allocation and helper vtable slot `+0x0c` as the callback that populates the `0x0d`-stride slot table.
- The caller side for that owner/resource path is now anchored too. A new function object at `000d:44df` is recovered and named `entity_vm_runtime_init_from_path_if_configured`: it checks configured global/string `0x65a`, builds a path through seg072 helper `0009:3600` using globals `0x6d6:0x6d8` plus `0x65a`, validates it through `000a:500a`, then calls `entity_vm_runtime_create(0,0,path)` and seeds post-init runtime state at `0x6611`, `0x6615`, and `0x8c7c`.
- Follow-on correlation for the `000d:21ed/22bc` lane is now tighter: the two bytes consumed at `000d:22d2` and `000d:22ee` are confirmed as compact signed shape/count metadata for the link-matrix walk (not descriptor ids), while the streamed words consumed through `000d:2324..237b` and `entity_link` (`0008:7d27`) behave as runtime entity/link ids with `0x0400` list-flag filtering on writeback. The source stream provenance remains slot-indexed owner-table data (`(+0x10/+0x12) + 0x0d*slot + 4`) mirrored to `0x39ca`, so descriptor-family correlation is strengthened only at the ecosystem level (generic active-event neighborhoods) and not as a direct `SURCAM*` or class-id keyed lane.
- Pass 4 dispatcher/xref follow-up is now partially closed: `FUN_000d_ebe3` sequencing is re-verified (`177c -> 1acb -> 0988 -> 22bc -> optional 1d4a -> 2104`) and the opcode selector local is pinned to `[BP-0x32]` inside `000d:0988` with concrete compares against `0x19/0x1a/0x1b` (`000d:099b`, `000d:09a1`, `000d:0a07`, `000d:0a0d`). The direct upstream selector edge into entry `000d:ebe3` remains unresolved in MCP xref output, so no additional wrapper-level opcode number is promoted yet beyond that internal family identity.
- Readable EUSECODE targeting moved one concrete step forward again. Fresh decompile on `entity_vm_context_create_from_slot_index` re-verifies that the slot-backed VM source copied from the runtime owner table (`(+0x10/+0x12) + 0x0d*slot + 4`) is also written into the per-slot mirror row addressed through `0x39ca[context_slot]` during context creation, which tightens the current source model without over-claiming unique ownership of the mirror. Combined with the extracted `jelyhack_*` and `event_*` reports, this now supports a first verified pseudo-script rendering of two descriptor-side forms: `referent anchor + event-bearing attachment` (`JELYHACK` / `JELYH2` beside `REE_BOOT` / `SFXTRIG` / `SURCAMEW`) and `event hub + trigger-side neighbors` (`EVENT` / `COR_BOOT` / `NPCTRIG` inside the `ROLL_NS..VMAIL` island). The direct descriptor-id bridge and the upstream selector into `FUN_000d_ebe3` both remain unresolved.
- The next wrapper-expansion pass is now also complete. The `0005:2867/2918/2ae2/2d30` family and its adjacent `2c06/2c35/2c68/2c9b/2cd2/2d01` siblings now form one verified mask ladder around `entity_vm_context_try_create_masked_for_entity`: `0x0002:0001`, `0x0020:0005`, `0x0004:0002`, `0x0200:0009`, `0x0400:000a`, `0x0800:000b`, `0x0010:0004`, `0x1000:000c`, `0x4000:000e`, and `0x8000:000f`, alongside the earlier seeds `0x0001:0000`, `0x8000:0007`, and `0x2000:0015`. The strongest new caller-side evidence is gameplay-state-oriented rather than descriptor-id-oriented: `0005:2867` feeds `000c` state helpers that store the result into entity field `+0x39`, `0005:2918` is only reached from a `+0x3c == 0x20b` object lane and carries caller fields `+0x36/+0x38` as an extra dword, and `0005:2d30` gates on entity id range, class word bit `0x8000`, class-record `0x7e46` flag bits, class nibble values `4/7/8`, and a `0x0f16` / `0x20f` dispatch-entry emission path before attempting mask `0x8000:000f`. That makes the wrapper family correlate more naturally with active-event ecosystems (`EVENT` / `NPCTRIG` / `_BOOT`) than with a direct `JELYHACK` referent-anchor lookup, while still stopping short of a hard descriptor-class switch.
- First concrete follow-on pass for the adjacent wrapper entries is now in: direct caller anchors are confirmed for `0005:2c06` (`0005:0292`), `0005:2cd2` (`0005:0fee`), `0005:2c9b` (`0005:5946/59e9`), and `0005:2d01` (`0007:814e/822e`). The old `0005:2c68` indirect-dispatch evidence is now rejected: `0007:e521` and `0007:e73c` push caller-local data `DAT_0000_2c68` into the fatal-report helper `000a:44fd`, not into wrapper `0005:2c68`. That leaves both `0005:2c35` and `0005:2c68` genuinely xref-dark and shifts the remaining selector work back to the true upstream edge into `FUN_000d_ebe3`.
- The current VM owner/path lane is now tighter too. Seg072 helper `0009:3600` is not a raw buffer-advance helper; it is a rotating slash-aware path composer that uses five `0x50`-byte temp buffers and inserts `\` only when adjacent path parts need it. In `entity_vm_runtime_init_from_path_if_configured` (`000d:44df`), `0x65a` therefore reads as the configured relative runtime-owner filename/path component, while `0x6d6:0x6d8` reads as the mutable base/resource-root path buffer. The still-xref-dark wrappers `0005:2c35` / `0005:2c68` are narrower too: their signed extra word is forwarded through `entity_vm_context_create_from_slot_index` into `entity_vm_slot_load_value_plus_offset` and stored in context field `+0x34`, so they are offset-specialized mask wrappers rather than plain duplicates. Direct CALL xrefs into `FUN_000d_ebe3` are also now confirmed from `animation_ctor_variant_a/b/c`, but no new wrapper-level opcode number is proven yet beyond internal `0x19/0x1a/0x1b`.
- The owner/resource helper is now tighter at the file-loader level too. The embedded seg070 methods rooted at raw windows `0009:67b6` and `0009:6916` iterate helper-owned tables at object `+0x10/+0x18`, format per-entry paths with the seg001 string helpers, then open/read/close through `0009:1c3a` / `0009:2034` / `0009:1e61`. That pushes `entity_vm_runtime_owner_resource_create` (`000d:7000`) beyond a generic file-backed label: it now looks like an indexed external file-set loader whose vtable `+0x0c` callback materializes the `0x0d`-stride owner records consumed by the VM runtime.
- The `0x39ca` mirror follow-up is narrower now too. The newly checked `0008:709c/70cb`, `0008:7309/7338`, and `0008:85f9/8617` windows only save/restore or allocate the global `0x39ca:0x39cc` base pointer and zero the backing table. No new competing per-slot row writer is verified there; `entity_vm_context_create_from_slot_index` (`000d:46ec`) still remains the only confirmed writer of concrete `0x39ca[slot] = {source_off, source_seg}` mirror rows.
- The descriptor-side tooling is now one step closer to readable USECODE too. `tools/extract_eusecode_flx.py` widens the JELYHACK local window to include the nearby event-bearing `REE_BOOT` / `SURCAMEW` / `SFXTRIG` records and now emits `readable_descriptor_templates.md` / `.tsv`, which render conservative pseudo-script sketches for the current anchor, event-hub, environmental, and callback lanes rather than only raw descriptor indexes.
- The script-facing bridge artifact is now tighter too. `tools/extract_eusecode_flx.py` emits `runtime_descriptor_family_rankings.md` / `.tsv`, which rank descriptor families against the verified VM/runtime lanes: `EVENT` is the strongest active-event payload fit, `_BOOT` cores and `NPCTRIG` remain strong active-event satellites, `SFXTRIG` and the environmental classes stay moderate active-event fits, `JELYHACK` / `JELYH2` now occupy a dedicated referent-anchor/payload-owner lane, and `SURCAMNS` / `SURCAMEW` stay in a weaker callback/attachment lane until callback-specific opcode or mask evidence appears.
- External reference pass completed against ScummVM's Ultima 8 / Crusader engine. New note `docs/scummvm-crusader-reference.md` records concrete Crusader-specific evidence for `usecode/*.cpp`, `convert/crusader/*.h`, FLEX parsing, sound/speech/movie handling, map record layout, Crusader-only shape/typeflag handling, HUD gumps, and startup/world-state differences. Highest immediate payoff is on USECODE parsing: `usecode/usecode_flex.cpp` gives ScummVM's Crusader class-header/event-count interpretation, while `convert_usecode_crusader.h` provides named event ids `0x00..0x1f` and a large intrinsic-signature table that can be cross-checked against current VM/runtime work.
- The first ScummVM-guided USECODE cross-walk batch is now complete. `usecode/usecode_flex.cpp`, `usecode/usecode.cpp`, `usecode/uc_machine.cpp`, and the Crusader/Regret conversion tables now externally anchor the Crusader class/event model (`classid + 2`, names from object `1`, base offset from bytes `8..11` minus `1`, six-byte event records, event-number call translation through opcode `0x11`) and confirm that Remorse uses a `ByteSet(0x1000)` VM with the shared Crusader event-name table but version-specific intrinsic dispatch. New note `docs/usecode-roundtrip-ir.md` records the safe annotation policy plus a reversible IR v0 that preserves raw class/event bytes, intrinsic ordinals, inline-versus-indirect payload distinctions, and opaque-opcode fallback. Headline estimate is intentionally unchanged because this batch tightened interpretation more than direct binary coverage.
- The next binary-side validation step for that ScummVM cross-walk is now complete too. Sampled owner-loaded EUSECODE class records (`EVENT`, `NPCTRIG`, `SURCAMNS`, `JELYHACK`, `REE_BOOT`, `SURCAMEW`, `SFXTRIG`) now confirm the object-1 name-table and `classid + 2` body lookup locally: deriving `object_index = (table_offset - 0x80) / 8`, `class_id = object_index - 2`, and then reading object `1` at `4 + 13 * class_id` yields the expected class names. The same samples confirm a real header dword at bytes `8..11` and a 6-byte event-slot table at `+20` with `u16 unknown + u32 code/payload` structure.
- The current USECODE-header mismatch is now narrowed further and has a conservative working resolution. `uc_machine.cpp` uses ScummVM's decremented `get_class_base_offset()` as the live code-stream base, while the local owner-loaded records still fit bytes `8..11 = first code-byte offset` with 1-based event code offsets. Under that reading the local event-count rule is `(base_offset - 19) / 6`, equivalently `(raw_u32_at_8_11 - 20) / 6`, which matches the validated `32/33/35` slot tables from the `0x00d4/0x00da/0x00e6` headers. The `000d:44df -> 000d:4c99 -> 000d:7000 -> 000d:46ec` runtime path still shows indexed file loading and slot-table consumption but no verified per-class header rewrite, so the mismatch currently looks best explained by a ScummVM interpretation/detail issue rather than a proven owner-loader transform. No safe new event-label-to-runtime correlation was promoted from this pass, and the headline estimate remains unchanged.
- The conservative owner-loaded class rule is now implemented in `tools/extract_eusecode_flx.py` and refreshed on the current EUSECODE sample. New outputs `class_layout_index.tsv` and `class_event_index.tsv` now expose object index, class id, class-name hint, raw bytes `8..11`, derived `code_base_minus_one`, conservative event counts, and raw 6-byte event rows with ScummVM slot-name hints, giving the round-trip work a concrete parser baseline instead of only prose notes.
### Current Focus
1. User-directed USECODE/JELYHACK lane: use the updated parser outputs to recover a small safe set of non-zero slot semantics, then tighten the reversible script IR around the already verified `000d` VM families before returning to deeper producer/dispatcher mapping.
2. Finish Priority 0 refinement by promoting more exact segment rows where notes already support a verified foothold.
3. Continue the Priority 1 pass by tracing the higher-level startup/display callers, branch outcomes, pre-entry object lanes, palette-fade ownership, watch/camera controller ownership, and active sprite/object ownership that stitch the seg137 palette helper family into the wider `0x4588` / dispatch-entry object-role lane.
### Next Resume Point
1. Continue the user-directed USECODE/JELYHACK follow-on from the recovered producer chain, now with the ScummVM class/event cross-walk in place, especially by:
- validating the new conservative rule against any future main USECODE container sample that becomes available, to decide whether the current mismatch is EUSECODE-specific or whether ScummVM's `get_class_event_count()` arithmetic should be treated as the outlier for Crusader,
- mining the new `class_layout_index.tsv` / `class_event_index.tsv` outputs for repeat non-zero slot patterns before doing more ad hoc byte inspection,
- broadening the local owner-loaded spot-check beyond the first named cluster when convenient, especially across additional `_BOOT`, environmental-event, and callback-eventtrigger classes, while treating the present object-1 / `classid + 2` indexing as the current working model,
- determining whether the owner/resource helper behind `entity_vm_runtime_owner_resource_create` (`000d:7000`) exposes original object indices through its helper `+0x18` table or only slot-local file ids, now that the `+0x10/+0x14/+0x18` table contract is verified,
- re-tracing `0005:2c35` / `0005:2c68` through real caller-role recovery now that they are narrowed to signed slot-offset wrappers, while keeping the disproven `000a:44fd` selector hypothesis retired,
- mapping only the now-verified non-zero low slot ids from sampled classes (`JELYHACK` slot `1`; `EVENT`/`SFXTRIG` slot `10`; `NPCTRIG` slots `10/32`; `REE_BOOT` slots `10/15/16`; `SURCAMNS`/`SURCAMEW` slots `1/10/32/33/34`) onto ScummVM event labels where binary behavior actually matches, and otherwise keeping them as numeric slot annotations,
- separating safe Remorse annotations from Regret-only intrinsic numbering by treating ScummVM intrinsic names as ordinal/signature hints rather than rename authority,
- turning the new conservative parser rule into tooling/tests first, while preserving raw bytes `8..11`, raw 6-byte event rows, and the unresolved leading event word in emitted IR artifacts,
- resolving the remaining selector/opcode path inside `FUN_000d_ebe3` by lifting the write/read path for opcode-local `[BP-0x32]` and any hidden jump/call-table case entry from the now-confirmed `animation_ctor_variant_a/b/c` caller lane,
- hardening the first reversible script IR around preserved class headers, raw event-entry words, intrinsic ordinals, inline-versus-indirect payload forms, and opaque-opcode fallback,
- identifying the concrete seg069/070 helper class behind `entity_vm_runtime_owner_resource_create` (`000d:7000`) now that the helper is narrowed to an indexed external file-set loader around raw windows `0009:67b6` / `0009:6916`,
- tracing the remaining caller roles for the verified ladder entries `0005:2c06/2c35/2c68/2c9b/2cd2/2d01` and the larger `0005:2d30` gate so the slot-mask groups can be mapped to concrete gameplay object/state classes rather than only mask numbers,
- and checking whether any runtime-owner helper besides `entity_vm_context_create_from_slot_index` writes per-slot mirror rows through the far array rooted at `0x39ca`, now that the currently checked `0008:709c/70cb`, `0008:7309/7338`, and `0008:85f9/8617` sites are constrained to base-pointer save/restore or table allocation rather than row writes.
2. Keep classifying the seg126 pre-entry text-renderer lane around `transition_preentry_setup_resources`, `transition_preentry_step_script`, and `transition_preentry_release_resources`, especially by:
- comparing more preset `0x10` / `0x11` text-renderer callsites,
- tracing who owns the rendered buffer loaded into `0x6301:0x6303`,
- mapping the control bytes `0x21` / `0x23` / `0x24` / `0x26` / `0x2a` / `0x40` / `0x5e` to concrete display behavior,
- and deciding whether the paired `0x8c5c` / `0x8c60` lane is a title/body pair, normal/highlight pair, or another fixed UI pairing.
3. Finish the `0x31a2` gate pass as one batch:
- classify the read sites at `0004:c24d`, `000c:ca11`, `000c:e4d8`, `000c:e546`, `000c:e5c6`, `000d:9304`, `000d:b6b1`, and `000d:c0ee`,
- relate them back to interrupt-side updates at `0008:a283` / `0008:a314`,
- and decide whether `0x31a2` is best described as user-acknowledge, queued-input depth, or a broader event-break gate.
4. Tighten the `DS:0x6341` to `0x6828` relationship:
- compare the seg126 `animation_ctor_variant_a` call with the other raw callsites at `0005:3c4f`, `0005:3c74`, `000c:6176`, and `000c:619c`,
- map who owns `g_active_dispatch_entry_farptr[+0x40]`,
- and classify whether seg126 is constructing a transition-local animation payload for the shared active dispatch entry or only toggling an owner-side state bit after setup.
5. Identify which higher-level transition states own the seg127 fade-controller inputs at `0x630a-0x6316` and how that fade state is chosen from the seg005/seg126 startup path.
6. Repair the still-oversized overlap rooted at `000c:db68` only if it blocks follow-on analysis or decompiler visibility in the same transition lane.
7. Clarify the relationship between the seg049 watch/camera controller at `0x2bd8`, the seg108 sprite/object lane at `0x4f38`, and the object validated through `FUN_0004_60c0` vtable slot `+0x0c`.
8. Continue caller-role classification inside `entity_cleanup_resources_and_dispatch` (contains both `000d:9d5e` and `000d:a3b7`) and map how it relates to `FUN_000d_938c`, `FUN_0004_60c0`, `FUN_000c_7412`, `transition_preentry_release_resources`, and the seg136/seg137 active-dispatch helper family.
8. ~~Cheat/input side lane~~**COMPLETED** this pass. All point-8 sub-items are now resolved:
- `keyboard_input_cheat_dispatch` renamed; full scan-code table documented in decompiler comment.
- `cheat_entity_slot_cycle_and_update_sprite` and `cheat_anim_type_cycle_and_refresh` named.
- `DS:0x287b` / `DS:0x2892` success-path presentation: confirmed as opaque near-code discriminator values stored at `+0x49` in the display notification object; the cheat-on/off display is built via `display_null_check_dispatch` + `sprite_node_get_or_traverse`.
- All seven cheat event case-handlers in the 000c dispatch function labeled and commented.
- `0x844` (master) vs `0x6045` (live latch) separation confirmed solid; `0x604b` / `0x604f` / `0x6050` also documented.
- HACK MOVER: no static code xrefs; attributed to USECODE scripting layer. Cheat string table in 000e fully documented.
- Remaining open: exact user-facing identity of events `0x141/0x241/0x441` overlays (strings suggest targeting-reticle / CD-transfer-display), exact DS:0x6087/6091 notification objects, and any further depth on the `0x4f38` / `0x2bd8` vtable path taken by the overlay events.
- The Immortality cheat mechanics are now fully traced at the C level: event `0x410` toggles `DS:0x604f`; the sole read site is `player_receive_damage_and_dispatch_effects` (`0004:c055`) at `0004:c205`, which divides all incoming 32-bit damage by `0x40000` (262,144) when the flag is set, making HP loss negligible while the hit-stagger animation still plays. No static C keyboard dispatch generates event `0x410` — confirmed USECODE/ASYLUM scripting layer only. `DS:0x60d2` / `DS:0x60ee` are the "Immortality enabled." / "Immortality disabled." notification pointers. A parallel handler at `000b:b62c` sets the associated USECODE process state to `0xe` when the event arrives.
- `tools/extract_eusecode_flx.py` now parses the validated full EUSECODE table (`count @ 0x54`, table @ `0x80`) rather than the old heuristic header scan. Current run extracts all `403` non-zero entries and emits a searchable `entry_index.tsv` with `primary_label` and `field_names` summaries.
- The extractor now also emits `descriptor_index.tsv` and `descriptor_neighborhoods.tsv`, which summarize per-class field-tag patterns and the local neighborhoods around trigger/event-related classes.
- Current EUSECODE split is now clearer: the `000e` parser lane plausibly covers text-heavy records like `DATALINK` and `TEXTFIL1`, while the binary descriptor lane exposes object classes such as `EVENT`, `NPCTRIG`, `CRUZTRIG`, `TRIGPAD`, `SPECIAL`, `SURCAMNS`, `SURCAMEW`, `JELYHACK`, and `JELYH2`.
- The descriptor lane now has a real structural foothold too: field-name strings are preceded by short tagged metadata records (`69 xx 00 <name>`, `24 xx 02 <name>`, etc.) in multiple classes. This looks like compact field-definition encoding rather than arbitrary string spill.
- That tag grammar is now useful enough to search semantically: `69:0A00 -> event` is stable across `EVENT`, `NPCTRIG`, `SFXTRIG`, and several `*_BOOT` classes, while `24:0A02 -> eventTrigger` shows up in `SURCAMNS` / `SURCAMEW`.
- Immortality-specific follow-on is now narrowed but not closed: `JELYHACK` and `JELYH2` are confirmed as real referent-only EUSECODE descriptors; `NPCTRIG` is confirmed as an event-capable trigger descriptor; `CRUZTRIG` / `TRIGPAD` expose `referent,item,elev`; but no extracted record has yet been tied directly to binary event value `0x410`.
- The clustering pass tightened the local candidate set around `JELYHACK`: the immediate neighborhood now includes `SPECIAL`, `TRIGPAD`, `DATALINK`, `HOFFMAN`, `REE_BOOT`, `SURCAMEW`, and `SFXTRIG`, which is a plausible map/object island rather than random sparse table order.
- The strongest `record_table_parse_buffer` caller evidence (`000e:1b9f..1d49`) now appears to belong to the animation-object field lane, because the surrounding setup manipulates the already-mapped animation fields at `+0x117/+0x11b/+0x11f/+0x123` and `+0xeaf/+0xeb1`. That weakens the earlier assumption that `000e:3639` is the primary EUSECODE loader and shifts the likely binary-descriptor consumer search back toward the `000d` VM/object path.
- The first concrete `000c` to `000d` bridge in that direction is now visible at `entity_vm_set_value_from_slot_plus_offset` (`000c:f95f`): it calls `entity_vm_slot_load_value_plus_offset` (`000d:5572`) and stores the return pair into object fields `+0xd6/+0xd8`. Supporting slot helpers in the same lane are now named too (`entity_vm_slot_find_or_select`, `entity_vm_slot_decrement_use_count`, `entity_vm_slot_release_value`). The previously noted `000d:51fd` `PUSH 0x410` site is now reclassified as a fatal-report call into `000a:44fd` with `DS:6616`, so it no longer supports a direct compiled-code immortality-event bridge.
- The adjacent `000d:45xx..4exx` island is now promoted out of `FUN_*` placeholders as one coherent VM runtime/context family. Newly named helpers include `entity_vm_runtime_create` / `entity_vm_runtime_init_slots` / `entity_vm_runtime_release_slots` / `entity_vm_runtime_destroy`, `entity_vm_slot_index_from_entity`, `entity_vm_context_try_create_masked_for_entity`, `entity_vm_context_create_from_slot_index`, `entity_vm_context_sync_global_value_and_dispatch`, and the context save/load/destroy helpers. The runtime global at `0x6611` now reads as a real owner for this lane rather than an opaque far pointer.
- Two large caller bodies at `000d:208b` and `000d:21ed` now stand out as concrete context-construction sites: both feed per-object stream/data state from `+0xcc/+0xce` into `entity_vm_context_create_from_slot_index`, then continue by reading from the seeded `+0xd6/+0xd8` bytecode/value lane. This is the clearest current evidence that the `000d` interpreter/object family, not the `000e` text parser, is the near-runtime consumer to keep following for the immortality trigger.
- A second supporting lane is now named too: `entity_vm_referent_registry_init` / `destroy` / `alloc` / `release_by_id` / `free_node` show that `0x8c8c/0x8c8e/0x8c90/0x8c94` form a free-list-backed referent registry. `entity_vm_set_field_da_to_global` writes `0x8c94` from the context `+0xda` lane before entering the still-misaligned `000c:3350` body, which is the first concrete runtime mechanism explaining how referent-only descriptors such as `JELYHACK` can still participate in script state.
- That referent-registry lane is now better structured too: `entity_vm_referent_chain_copy`, `entity_vm_referent_chain_append_unique_from`, `entity_vm_referent_chain_remove_matching_from`, `entity_vm_referent_chain_contains_entry`, `entity_vm_referent_chain_get_entry_data_at`, `entity_vm_referent_chain_set_entry_data_at`, and `entity_vm_referent_chain_get_indirect_data` show that the runtime can build, subtract, and mutate payload chains hanging off one referent anchor. This is the first runtime shape that looks directly useful for a future human-readable / modifiable script IR.
- `entity_vm_opcode_finish` (`000d:3350`) is now identified as the shared opcode epilogue for this family rather than an opaque helper: it writes `0x8c94` from frame-local state, unwinds the temporary slot-array state at `0x659c/0x659e` when present, and returns the current opcode result.
- The runtime/context half of that lane is now named too. The `0x6611` global is managed by `entity_vm_runtime_create` / `entity_vm_runtime_init_slots` / `entity_vm_runtime_release_slots` / `entity_vm_runtime_destroy`, while `entity_vm_slot_index_from_entity`, `entity_vm_context_try_create_masked_for_entity`, and `entity_vm_context_create_from_slot_index` now show how gameplay entities are tested against one owner-side slot-mask table before a `0x6714` VM context is created.
- That context family is no longer anonymous either: `entity_vm_context_sync_global_value_and_dispatch`, `entity_vm_context_save`, `entity_vm_context_load`, `entity_vm_context_destroy`, and `entity_vm_context_free_buffer` now pin down the lifecycle around the same `+0xd6/+0xd8`, `+0x102`, `+0x10c/+0x10e`, and `+0x11b/+0x11d` fields.
- Current best near-runtime callsites for further immortality work are the large `000d:208b` and `000d:21ed` bodies, which both build one VM context from caller stream/data state and then continue by consuming bytes from the seeded context value lane.
- The first opcode family under that lane is also less anonymous now: `000d:0988` can either append unique payload entries or remove matching ones depending on the opcode id (`0x1a/0x1b` taking the removal path), and both branches return through `entity_vm_opcode_finish`.
- That opcode family is now classified one step further: `0x19` = append-unique indirect/string-like payloads, `0x1a` = remove-matching indirect/string-like payloads, `0x1b` = remove-matching inline payloads, and the same helper body strongly implies `0x18` as the missing append-unique inline sibling.
- The first stable `+0xd6/+0xd8` byte-lane semantics are now visible in the two large caller bodies too. The `000d:208b` block is a simple materialize-or-forward path after `entity_vm_context_create_from_slot_index`, while `000d:21ed` copies a caller-owned inline blob into the context `+0x102` buffer and then consumes two stream bytes as compact shape/count metadata before building an `entity_link` closure matrix from the following caller-stream words.
- EUSECODE readability moved one concrete step forward in this pass: decompile output now supports a first verified IR vocabulary for the same lane — `APPEND_UNIQUE_INLINE` (implied `0x18` sibling), `APPEND_UNIQUE_INDIRECT` (`0x19`), `REMOVE_MATCHING_INDIRECT` (`0x1a`), `REMOVE_MATCHING_INLINE` (`0x1b`), `MATERIALIZE_OR_FORWARD_VALUE` (`000d:208b`), `PREPEND_INLINE_PAYLOAD` (`000d:21ed`), and `BUILD_ENTITY_LINK_MATRIX` (`000d:22bc` with `entity_link` at `0008:7d27`). The `000d:22bc` tail also confirms a pushback filter where non-`0x0400` results are written back to the caller stream before `entity_vm_opcode_finish`.
- Current best JELYHACK reading is tighter than before: the extracted chunks still only expose `referent`, but the new referent-registry work means that does not relegate them to inert map labels. The most defensible present model is `JELYHACK/JELYH2 = referent anchors`, with the actual immortality/event behavior carried by neighboring event-capable descriptors in the same local island (`REE_BOOT`, `SURCAMEW`, `SFXTRIG`, or a nearby generic event/trigger record).
- That readability step now has a first concrete artifact: `tools/extract_eusecode_flx.py` emits `referent_anchor_event_graph.tsv` plus a focused `jelyhack_island_graph.md`, which turns the local table neighborhood into a first readable anchor-to-event view instead of only raw descriptor rows.
- The extractor now also emits `jelyhack_descriptor_compare.tsv`, and its first result is useful: `JELYHACK` and `JELYH2` have identical first 16 header words as referent-only sibling descriptors, while `REE_BOOT`, `SURCAMEW`, and `SFXTRIG` show materially richer header/state patterns consistent with the event-bearing side of the island.
- Latest opcode-side refinement: `entity_vm_opcode_finish` (`000d:3350`) is now the shared epilogue for the chain-mutating handlers, while `entity_vm_referent_chain_remove_matching_from` (`000d:6a9a`) and `entity_vm_referent_chain_set_entry_data_at` (`000d:6cf6`) show that the VM can subtract and rewrite payload chains in place, not just append/copy them.
- The `000d:21ed` follow-on is now better anchored semantically too: its nested callee `0008:7d27` is `entity_link`, so the `22bc..2433` block is building a bidirectional entity-link closure matrix from streamed entity ids rather than only emitting an opaque table. A conservative disassembly comment is now in place at `000d:22bc`; rename deferred until the bad outer function split is repaired.
- The extractor work now scales beyond the JELYHACK case: reusable focused-report helpers emit both `jelyhack_*` and `event_*` cluster artifacts, and the first new result is strong. The `EVENT` island (`ROLL_NS`, `COR_BOOT`, `EVENT`, `NPCTRIG`, `CRUZTRIG`, `NPC_ONLY`, `VMAIL`) contains a compact three-node event-bearing core (`COR_BOOT`, `EVENT`, `NPCTRIG`) surrounded by referent/link/text satellites.
- That second island materially improves the EUSECODE model: instead of one special-case `JELYHACK` anchor plus neighbors, we now have a broader pattern of `event-bearing core embedded in referent-neighbor island`, with `EVENT` acting as a large hub descriptor (`source/dest/door/link/time/counter/post1/post2/floor/flicMan`) and `ROLL_NS` / `CRUZTRIG` / `NPC_ONLY` / `VMAIL` reading as attached state or trigger-side records rather than peer event hubs.
- The descriptor-side taxonomy is now wider too: `event_family_index.tsv` / `event_family_summary.md` classify all current event-tagged descriptors into reusable families. The active `69:0A00 -> event` lane now breaks cleanly into one `EVENT` hub, five `_BOOT` event cores, one NPC trigger core, one minimal event core (`SFXTRIG`), and three environmental event classes (`FLAMEBOX`, `NOSTRIL`, `STEAMBOX`), while the surveillance pair `SURCAMNS` / `SURCAMEW` is now cleanly separated as `callback-eventtrigger` rather than generic event-bearing descriptors.
- The `_BOOT` family is now better constrained too. `boot_family_compare.tsv` shows that `AND_BOOT`, `BRO_BOOT`, `COR_BOOT`, `VAR_BOOT`, and `REE_BOOT` all share one common header/template shape, so the family now reads as repeated instantiations of the same event-core descriptor rather than structurally different boot subclasses.
- The best remaining `_BOOT` frontier is now explicit in extractor output as well: `boot_frontier_graph.md` shows `AND_BOOT` / `BRO_BOOT` embedded in a compact referent-heavy neighborhood (`OFFWORK`, `GUARD`, `GDOOR_*`, `BIGCAN`, `CRUMORPH`, `GUARDSQ`, `CARD_*`, wall variants), which is the cleanest unresolved object-side context for the boot-event template.
- The environmental event lane is now promoted out of a generic family label into a clearer structural pattern. `environmental_family_compare.tsv` shows `FLAMEBOX` and `STEAMBOX` as close hazard-event siblings with the same active-event backbone plus direction/count, while `NOSTRIL` is the smaller fire-specific variant that keeps the dual-hazard references and counters but drops the direction/newType side.
- The callback-trigger lane is also more defensible now: `callback_trigger_compare.tsv` confirms that `SURCAMNS` and `SURCAMEW` are effectively one shared callback template, differing only in one `therma` slot tag offset. That keeps the active `event` lane and callback `eventTrigger` lane separated by more than just naming convention.
- Runtime follow-through has resumed too: `000d:ebe3` is now backed by direct instruction evidence as one ordered VM/opcode driver body that calls `000d:177c`, `000d:1acb`, `000d:0988`, internal block `000d:22bc`, then `000d:1d4a` and `000d:2104` in sequence. `000d:ec31` is confirmed as only the internal `CALL 000d:22bc` site inside that body, so the inner block is still not a safe standalone rename target.
- Payload-shape reuse inside that same `FUN_000d_ebe3` sequencer is now partially classified: `000d:177c` behaves as a word-literal stream push, `000d:1acb` consumes one streamed dword pair and pushes a boolean word, `000d:21ed/22bc` remains the signed-byte metadata plus word-id matrix lane, `000d:1d4a` is still a boundary-suspect trap island, and `000d:2104` is a mixed scalar/handle out-pointer finalizer. This is now documented as a compact opcode-to-payload-shape matrix in docs.
- `entity_vm_context_try_create_masked_for_entity` (`000d:463a`) is now pinned down one step further: it first checks the runtime-disable byte at `0x6610`, computes the entity slot, tests the owner-side slot mask in the runtime owner table, and only then creates a context. On success it reports either an immediate result (success with cleared output word) or an object-backed result (success with the created object's low word), which is the clearest current typed boundary between gameplay entities and VM-backed object results.
- The immediate owner-object writer is now identified too. `entity_vm_runtime_create` (`000d:4c99`) stores the only verified runtime `+0x1315/+0x1317` value by calling the newly recovered `entity_vm_runtime_owner_resource_create` (`000d:7000`), whose helper-managed body allocates child `+0x10/+0x12` from a vtable `+0x04` size query and fills the `0x0d`-stride slot table through vtable `+0x0c`. The paired release path is `entity_vm_runtime_owner_resource_destroy` (`000d:70fd`).
- The first wrapper-side mask families are now anchored by direct instruction evidence as well: local wrapper `0004:f033` passes `0x8000:0007`, `FUN_0004_f05c` passes `0x2000:0015` from the `0004:f2b3` overlap/proximity branch with entity byte `+0x32` state toggling, and `FUN_0005_27a4` passes `0x0001:0000` from the `000c:a09e` entity `+0x5b` bit-`0x0004` branch. This is enough to distinguish at least three gameplay-side mask lanes without yet claiming descriptor-specific ownership such as `JELYHACK` versus `REE_BOOT`.
- One exact `0x410` collision that could have reopened the wrong lane is now ruled out: `000e:0953` pushes literal `0x410` into imported `ASYLUM.27` from the animation/audio path after setting the `+0xef1` audio-completion byte. Because `ASYLUM.DLL` is the `ASS_*` audio/media library, this is not evidence for a second gameplay or USECODE event source; the other previously suspected compiled-code bridge at `000d:51fd` is now ruled out too because that site calls the seg091 fatal-report helper `000a:44fd` with `DS:6616`, not gameplay dispatch.
9. Revisit `allocator_phase_finalize_pass` only where it intersects the same callback object semantics, rather than broad allocator mechanics that are already sufficiently constrained.
10. Continue `ASYLUM.24` only after the `0x4588` / dispatch-entry lane and `0004:1e00` transition path have no further cheap wins.
11. User-directed USECODE/JELYHACK side lane (next actionable IR step): map the new sequencer-local payload-shape matrix to concrete opcode numbers by recovering the upstream opcode dispatcher lane that selects `FUN_000d_ebe3`, then test whether those opcode numbers correlate better with active-event families (`EVENT`/`NPCTRIG`/`*_BOOT`/`SFXTRIG`) than with callback-trigger (`SURCAM*`) descriptors.
12. Use the new ScummVM reference note as a focused cross-check batch before deeper parser or VM work:
- compare local USECODE/EUSECODE container assumptions against ScummVM's Crusader `UsecodeFlex` class-header parsing (`classid + 2`, class-name table at object `1`, base offset from bytes `8..11`, event-count formula `(base + 19) / 6`),
- import the conservative Crusader event-name table from `convert_usecode_crusader.h` (`look/use/anim/cachein/hit/gotHit/hatch/schedule/release/equip/unequip/combine/calledFromAnim/enterFastArea/leaveFastArea/avatarStoleSomething/animGetHit/unhatch`) into the current USECODE annotation workflow where they match verified behavior,
- compare current weapon/ammo and item-family reads against ScummVM's `WeaponInfo`, `ShapeInfo`, and `ItemFactory` structures so quality/ammo/clip semantics are kept aligned with evidence,
- and prioritize local parsers or validators for the ScummVM-loaded Crusader data files that are still weakly covered here: `dtable.flx`, `damage.flx`, `glob.flx`, `wpnovlay.dat`, `sound.flx`, and per-shape speech FLEX archives.
### Headline Estimate
- Overall useful decompilation progress: about 37%
- Reasonable uncertainty band: about 31% to 40%
This is the best single-number estimate for the full game right now.
### Supporting Metrics
| Metric | Estimate | Meaning |
|---|---:|---|
| Top 100 far-call target coverage | about 80% | Roughly 80 of the top 100 most-called far-call targets have been named or materially classified |
| Whole-program behavioral coverage | about 37% | Verified subsystem and function understanding across the executable |
| Segment spread with meaningful analysis | about 20% to 26% | Segments with more than a trivial foothold or isolated note |
| Tooling maturity for continued work | about 75% | Core repair, lookup, and fallback automation needed for continued progress |
### Why These Numbers Differ
- The hot-target metric is much higher because the project has already focused on the most shared and most-called helpers.
- The whole-program metric is lower because most of the 145 NE segments still have not had systematic coverage passes.
- The segment-spread metric is lower still because only a subset of segments have coherent subsystem-level treatment.
## What Is Already In Place
### Workflow and Tooling
- Raw full-EXE Ghidra target is established and in active use.
- Verified raw-import mapping exists for seg001 and seg021.
- NE relocation parsing has been implemented.
- Internal literal far-call fixups have been applied to the raw import.
- PyGhidra fallback tooling exists for create/delete function work and batch scripted edits.
- Conservative boundary-repair workflow already exists and has been used successfully.
- Notes are detailed enough to support a formal executable-wide tracker.
### Objective Milestones Already Reached
- 145 NE segments identified from the internal NE header.
- 8851 internal literal CALLF sites patched to real targets in the raw import.
- 2841 non-CALLF far-pointer relocations identified and deferred.
- 119 import callsites annotated.
- Top 100 far-call target list processed through five tiers, with about 80 named or materially classified.
## Strongly Advanced Areas
### Core Gameplay and Entity Work
- seg001 gameplay, cursor, entity lifecycle, projectile, combat, and AI footholds are strong.
- A verified seg001 raw-port path is working and already used for multiple projectile helpers.
- Entity table, class-table, and several global gameplay fields are partially mapped.
- Overall useful decompilation progress: about 42%
- Reasonable uncertainty band: about 36% to 45%
- Top 100 far-call target coverage: about 80%
- Segment spread with meaningful analysis: about 22% to 28%
- Tooling maturity for continued work: about 75%
### Why The Estimate Stays Here
- Recent work materially improved semantic confidence inside the startup/display, cache/allocator, callback-object, and USECODE/VM lanes.
- The startup/display lane now has a verified owner split: `g_active_dispatch_entry_farptr[+0x40]` is a borrowed shared presentation hold token, while the seg108 `0x4f38` bit-`0x40` lane stays local to the sprite/object stack.
- The seg126 control stream is now tighter at the producer side too: the traced setup path still supports a shared-base-path file selector feeding a full external script/control buffer, the `0x6aa:0x6ac` base now reads as an inherited external/default path buffer rather than a stronger in-code producer, and the in-scope `0x31a2` transition/presentation readers are now classified by role.
- That work reduced ambiguity inside already-active clusters more than it expanded whole-program breadth, so the headline only moves modestly.
## Current Verified State
### Timer, Event, and State Systems
### 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/`.
- The raw full-EXE porting workflow is stable for the verified seg001 and seg021 mappings.
### Strong Or Stable Areas
- seg021 timer and event-dispatch work has meaningful coverage.
- 000c state-dispatch, cursor-nav, UI-listbox, palette-fade, and mini-VM clusters have footholds.
- 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 USECODE/VM owner/resource/runtime lane now has a workable partial model plus supporting extraction/reporting tooling.
### Rendering and Camera
### Recently Closed Or No Longer Live
- 0007 rendering, draw-list, tile-visibility, and camera work has strong structural coverage.
- `world_to_screen_coords` and adjacent geometric helpers are understood well enough to support further caller analysis.
- `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 now has named outer shells (`startup_display_transition_prepare`, `startup_display_transition_driver`) plus named seg126/127/136/137/138 helper families. The remaining work is higher-level ownership and state semantics, not basic structural recovery.
- The top startup/display ownership question is now narrowed: `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.
### Dispatch and Pair-Sync Helpers
## Live Blockers
- 0008 dispatch-entry helper families have multiple verified rename batches.
- Pair-sync and target-state helper clusters are no longer isolated unknowns.
1. The startup/display transition lane still lacks exact higher-level owner/state labels across seg005, seg049, seg108, seg126, seg127, seg136, seg137, and seg138, even though the shared `g_active_dispatch_entry_farptr[+0x40]` hold token is now separated from the seg108-local `0x4f38` bit-`0x40` lane.
2. The oversized overlap rooted at `000c:db68` still blocks safe recovery of the real `transition_preentry_step_script` body.
3. The `0x4588` callback object is better constrained but still not behaviorally classified enough for a confident subsystem rename.
4. The USECODE/VM sequencer still lacks the real upstream selector/caller path into `FUN_000d_ebe3`, and wrappers `0005:2c35` / `0005:2c68` remain caller-dark.
5. High-value missing or weak function objects still exist in hot ranges such as `000b:2e00`, `0007:5a00`, and `000e:ffb0`.
6. 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.
### Cache, Tracked Handles, and Bucket Logic
## Current Focus
- 000a cache manager layer is structurally mapped.
- 000a tracked-handle table is structurally mapped.
- 000d tracked bucket / proximity / visibility bucket logic has several meaningful behavioral names.
- The client/cache distinction is much clearer than before.
1. Finish the startup/display transition lane while it is still producing direct executable coverage.
2. Continue the USECODE/VM lane only where it yields concrete caller, selector, or loader 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.
### Parser and Animation Framework
## Next Resume Point
- 000e parser cluster has a stable set of verified names.
- 000e animation framework has a real foothold: chunk lookup, audio load, tick, frame advance, and constructor variants are partly mapped.
1. Continue the adjacent seg126 startup/display clarification from the local three-way file-family selector at `000c:afa5..b152` and nearby seg049 `0x2bd8` dispatch sites, but only where it sharpens the validated presentation-handoff model without speculative renames.
2. Repair the `000c:db68` overlap only if needed to split `transition_preentry_step_script` into its own clean function object and preserve the already-verified `000c:ca1d..cd4f` body in Ghidra.
3. Classify the exact UI role of the paired `0x8c5c` / `0x8c60` renderer presets, the `+0x49` selector states, and the neighboring seg127 fade inputs only where the caller evidence stays inside the same startup/display family.
4. Recover the real upstream caller/selector path into `FUN_000d_ebe3` from persisted context/save/load or shared-consumer paths instead of repeating exhausted direct xref hunts.
5. Recover real caller roles for `0005:2c35` and `0005:2c68`, now that both are narrowed to signed slot-offset wrappers feeding the VM context lane.
6. Clarify whether the seg070 twin loops at `0009:67b6` and `0009:6916` represent two file families, two table formats, or two loader phases of the same helper behind `entity_vm_runtime_owner_resource_create`.
7. Promote additional ledger rows where the current docs already justify `Foothold`, `Partial`, or `Deep`.
8. Revisit `000e:ffb0` and other high-value overlap targets only after the current startup/display and VM lanes stop yielding near-term wins.
### Local Repair Successes
## Remaining Work To Reach A Reasonably Complete Decompilation State
- seg043 overlap repair succeeded and recovered multiple valid function objects.
- seg091 boundary recovery succeeded and exposed RNG helpers plus local init/context helpers.
- Recent seg004 reset-path recovery and cache-reset follow-up added a new high-value analysis cluster.
### 1. Coverage And Tracker Completion
## What Still Blocks Broader Coverage
- 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.
### High-Value Classification Gaps
### 2. Startup/Display And Presentation Lane
- The object rooted at `0x4588` is still not classified well enough to safely rename the callback object itself beyond the current allocator-side glue names.
- `ASYLUM.24` is only known as an import site, not yet a confidently identified routine.
- Some structural names in the cache/backend/finalize cluster are waiting on object-role confirmation.
- Finish semantic ownership across seg005, seg049, seg108, seg126, seg127, seg136, seg137, and seg138.
- Resolve the remaining role of the shared active-dispatch hold token versus local per-entry hold bytes.
- Recover the higher-level meaning of the file-backed seg126 control stream without speculating beyond verified byte behaviors.
- Classify the exact UI role of the paired `0x8c5c` / `0x8c60` text-renderer lane if stronger caller evidence appears.
- Finish the fade-controller producer path so seg127 fade inputs are tied to higher-level transition states, not only local opcodes.
- Classify `FUN_000d_938c`, `transition_preentry_release_resources`, and `entity_cleanup_resources_and_dispatch` by role once their shared-hold semantics are fully separated.
- Remove the remaining overlap blockers in this lane, with `000c:db68` first.
### Boundary and Decompiler Gaps
### 3. VM / USECODE / Scripting Lane
- Some high-caller targets still require conservative boundary repair or follow-up validation.
- Certain functions still decompile poorly because of overlaps, thunk-heavy paths, or unresolved downstream targets.
- `000e:ffb0` remains a notable animation/video-side blocker because of overlapping instructions.
- Recover the upstream selector into `FUN_000d_ebe3` and map payload-shape handlers to real opcode dispatch.
- Recover real caller roles for the dark mask wrappers `0005:2c35` and `0005:2c68`.
- 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.
### Coverage Management Gap
### 4. Cache / Allocator / Callback-Object Lane
- A first-pass normalized segment-by-segment coverage ledger now exists for all 145 NE segments.
- The remaining gap is refinement rather than absence: most segments still need manual promotion from `None` to `Foothold` / `Partial` / `Deep` as coverage expands.
- 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.
### Deferred Data Work
### 5. Rendering, Palette, Animation, And UI Support Lanes
- Non-CALLF far-pointer relocations still exist and will matter for deeper object/table recovery.
- They are no longer the main blocker, but they remain a real second-pass problem.
- 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.
## Current Best Assessment Of Remaining Work
### 6. Boundary Repair And Function Hygiene
The project has solved most of the architectural uncertainty needed to keep going efficiently.
The remaining effort is mainly a scaling problem:
- 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.
- expand coverage across many more segments,
- remove the last high-value boundary blockers,
- convert structural names into subsystem names when evidence is strong enough,
- and normalize progress tracking so the whole program can be managed deliberately.
### 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.
In practical terms, this looks like a true mid-project state rather than an early exploratory state or a late polish state.
### 8. Completion Criteria
## Implementation Priorities
A reasonably complete decompilation state should mean:
### Priority 0: Coverage Ledger
- 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.
First pass completed: an executable-wide coverage ledger now exists for all 145 NE segments in `crusader_segment_coverage_ledger.csv`.
## Priority Order
Next work under Priority 0:
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. Promote additional segments from `None` where notes already support a verified foothold.
2. Normalize raw-address subsystem islands (notably the `000e:` parser/animation cluster) back onto exact NE segment rows.
3. Keep the ledger updated together with `crusader_decompilation_notes.md` after each verified batch.
## Evidence Anchors
Minimum columns:
| Column | Meaning |
|---|---|
| Segment | NE segment number |
| Type | Code or data |
| File offset | From the NE segment table |
| Length | Segment length |
| Coverage status | None, foothold, partial, deep |
| Known subsystem | Best current classification |
| Key named functions | Short summary only |
| Blockers | Boundary, import, thunk, overlap, unknown object, etc. |
| Notes source | Notes section or evidence anchor |
This is the most important missing artifact because it will make the percentage estimates maintainable.
### Priority 1: Finish The New Cache/Backend Cluster
Work the newest verified reset-path cluster to closure:
1. Trace more callers of `0009:b06b`.
2. Trace more callers of `FUN_0009_a961`.
3. Classify the object rooted at `0x4588`.
4. Revisit `allocator_phase_finalize_pass` once the object role is clearer.
This is currently the best next analysis target because it closes a live cluster that already has fresh verified work around it.
### Priority 2: `ASYLUM.24` Resolved
`ASYLUM.DLL` was imported as a separate NE program in Ghidra and its export table is now verified as an `ASS_*` audio DLL, not the immortality/USECODE interpreter lane.
Resolved result:
- `ASYLUM.24` = `_ASS_StopAllSFX` at `1018:0681`
- `runtime_cache_reset_sequence` therefore performs an audio stop before the cache/tracked-handle reset work
- this import is not evidence for the immortality cheat path; the `0x410` toggle remains attributed to the interpreted `EUSECODE.FLX` lane rather than `ASYLUM.DLL`
### Priority 3: Continue Small-Batch Boundary Repair
Use the existing conservative repair approach for remaining high-value blockers.
Good candidates include:
- unresolved high-caller function objects,
- ranges that still steal bytes from adjacent real bodies,
- and overlaps that block decompilation of already-active subsystems.
### Priority 4: Finish Partial Subsystem Islands Before Expanding Broadly
Recommended order:
1. seg043 plus connected seg004 reset and dispatch paths
2. 000e animation/video overlap at `000e:ffb0`
3. 000c UI-listbox, mini-VM, and cursor-nav families
4. Remaining structural 0007 and 0008 helper cohorts
The goal is to reduce the number of half-understood islands before starting broad segment sweeps.
### Priority 5: Broaden Coverage Across The Remaining Executable
Once the ledger exists and the current hot cluster is closed, broaden analysis segment by segment.
Preferred method:
1. Group segments by adjacency and call relationships.
2. Identify entry points and hot callees first.
3. Classify globals and tables next.
4. Promote helper names only when supported by strong evidence.
## Recommended Tracking Model
Use these status values for segment coverage:
| Status | Meaning |
|---|---|
| None | No meaningful verified analysis yet |
| Foothold | One or two verified entry points or helper names, but no subsystem picture |
| Partial | Several verified names plus some globals/tables or object fields |
| Deep | Coherent subsystem-level understanding with multiple verified related functions |
Use these status values for subsystem maturity:
| Status | Meaning |
|---|---|
| Unknown | Not enough evidence to classify |
| Structural | Behavior is partly mapped but still generic |
| Behavioral | Confident subsystem role is known |
| Stable | Multiple connected functions and data objects support the classification |
## Suggested Immediate Work Queue
### Queue A: Highest Leverage
1. Expand the first-pass segment coverage ledger beyond the currently seeded segments.
2. Trace `allocator_try_alloc_from_head_table`, `allocator_head_finalize_sweep`, and `allocator_phase_finalize_pass`.
3. Identify `ASYLUM.24`.
### Queue B: Repair And Stabilize
1. Review remaining high-caller gap functions.
2. Repair any still-blocking overlaps in small batches.
3. Re-decompile repaired ranges and keep only evidence-backed names.
### Queue C: Broaden Carefully
1. Expand into adjacent segments connected to already-understood clusters.
2. Avoid speculative naming.
3. Update the notes and the coverage ledger together after each verified batch.
## Concrete Progress Interpretation
If a single number is needed, use 25%.
If a more honest dashboard is acceptable, use all three:
- 80% of top-100 hot targets processed
- 25% overall behavioral decompilation progress
- 10% to 15% segment spread with meaningful analysis
That combination best reflects the actual state of the project.
## Source Anchors
Primary sources for this file:
Primary files backing this plan state:
- `crusader_segment_coverage_ledger.csv`
- `crusader_decompilation_notes.md`
- `crusader_ne_segments.csv`
- `tier4_output.txt`
- `tier5_output.txt`
- repo memory progress summary
- `docs/overview.md`
- `docs/raw-porting-progress.md`
- `docs/raw-0008-000c.md`
- `docs/raw-000a-000d.md`
- `docs/raw-000e.md`
- `docs/far-call-targets.md`
- `docs/usecode-roundtrip-ir.md`
- `docs/scummvm-crusader-reference.md`
## Next Update Rule
## Update Rule
Update this file when one of the following happens:
- the overall estimate changes materially,
- a new subsystem reaches behavioral or stable status,
- a major blocker such as `0x4588`, `allocator_phase_finalize_pass`, or `ASYLUM.24` is resolved,
- or the segment coverage ledger is created and becomes the new primary progress source.
- the headline estimate changes materially,
- a live blocker is resolved,
- a subsystem moves from structural to behavioral understanding,
- 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.

View file

@ -15,6 +15,7 @@ to support the next decoding pass.
from __future__ import annotations
import argparse
import hashlib
import json
import pathlib
import struct
@ -61,6 +62,21 @@ class ExtractedChunk:
class_parse_status: str | None = None
@dataclass(frozen=True)
class ClassEventRow:
entry_index: int
object_index: int
class_id: int
class_name_hint: str
slot: int
event_name_hint: str | None
raw_event_entry_word: int
raw_code_offset: int
derived_body_start: int | None
derived_body_end: int | None
derived_body_length: int | None
@dataclass(frozen=True)
class FlxTable:
entry_count: int
@ -69,6 +85,25 @@ class FlxTable:
entries: list[CandidateEntry]
@dataclass(frozen=True)
class FamilyArtifactSpec:
output_stem: str
title: str
labels: tuple[str, ...]
@dataclass(frozen=True)
class RepeatedFamilyRowExpectation:
class_name: str
slot: int
raw_event_entry_word: int
raw_code_offset: int
derived_body_start: int
derived_body_end: int
derived_body_length: int
repeated_template_status: str
def read_u32_le(data: bytes, offset: int) -> int:
return struct.unpack_from("<I", data, offset)[0]
@ -454,6 +489,73 @@ SCUMMVM_EVENT_NAME_HINTS: tuple[str, ...] = (
)
VERIFIED_REPEATED_TEMPLATE_FAMILIES: tuple[tuple[str, tuple[str, ...]], ...] = (
("referent-anchor-twin", ("JELYHACK", "JELYH2")),
("boot-event-core", ("AND_BOOT", "BRO_BOOT", "COR_BOOT", "REE_BOOT", "VAR_BOOT")),
("callback-eventtrigger", ("SURCAMNS", "SURCAMEW")),
("environmental-event", ("FLAMEBOX", "NOSTRIL", "STEAMBOX")),
)
FAMILY_ARTIFACT_SPECS: tuple[FamilyArtifactSpec, ...] = (
FamilyArtifactSpec(
output_stem="boot_family_decompile",
title="_BOOT Family Decompiled Event Sketches",
labels=("AND_BOOT", "BRO_BOOT", "COR_BOOT", "REE_BOOT", "VAR_BOOT"),
),
FamilyArtifactSpec(
output_stem="callback_family_decompile",
title="SURCAM Callback Family Decompiled Event Sketches",
labels=("SURCAMNS", "SURCAMEW"),
),
FamilyArtifactSpec(
output_stem="environmental_family_decompile",
title="Environmental Family Decompiled Event Sketches",
labels=("FLAMEBOX", "NOSTRIL", "STEAMBOX"),
),
)
VERIFIED_REPEATED_FAMILY_ROW_EXPECTATIONS: tuple[RepeatedFamilyRowExpectation, ...] = (
RepeatedFamilyRowExpectation("JELYHACK", 0x01, 0x002A, 0x00000001, 0x00D4, 0x00FE, 42, "referent-anchor-twin/shared-slot-0x01/same-length-template"),
RepeatedFamilyRowExpectation("JELYH2", 0x01, 0x002A, 0x00000001, 0x00D4, 0x00FE, 42, "referent-anchor-twin/shared-slot-0x01/same-length-template"),
RepeatedFamilyRowExpectation("AND_BOOT", 0x0A, 0x0253, 0x00000001, 0x00D4, 0x0327, 595, "boot-event-core/shared-slot-0x0A/shared-slot-template"),
RepeatedFamilyRowExpectation("AND_BOOT", 0x0F, 0x0237, 0x00000254, 0x0327, 0x055E, 567, "boot-event-core/shared-slot-0x0F/shared-slot-template"),
RepeatedFamilyRowExpectation("AND_BOOT", 0x10, 0x003B, 0x0000048B, 0x055E, 0x0599, 59, "boot-event-core/shared-slot-0x10/same-length-template"),
RepeatedFamilyRowExpectation("BRO_BOOT", 0x0A, 0x02D5, 0x00000001, 0x00D4, 0x03A9, 725, "boot-event-core/shared-slot-0x0A/shared-slot-template"),
RepeatedFamilyRowExpectation("BRO_BOOT", 0x0F, 0x024C, 0x000002D6, 0x03A9, 0x05F5, 588, "boot-event-core/shared-slot-0x0F/shared-slot-template"),
RepeatedFamilyRowExpectation("BRO_BOOT", 0x10, 0x003B, 0x00000522, 0x05F5, 0x0630, 59, "boot-event-core/shared-slot-0x10/same-length-template"),
RepeatedFamilyRowExpectation("COR_BOOT", 0x0A, 0x0227, 0x00000001, 0x00D4, 0x02FB, 551, "boot-event-core/shared-slot-0x0A/shared-slot-template"),
RepeatedFamilyRowExpectation("COR_BOOT", 0x0F, 0x0234, 0x00000228, 0x02FB, 0x052F, 564, "boot-event-core/shared-slot-0x0F/shared-slot-template"),
RepeatedFamilyRowExpectation("COR_BOOT", 0x10, 0x003B, 0x0000045C, 0x052F, 0x056A, 59, "boot-event-core/shared-slot-0x10/same-length-template"),
RepeatedFamilyRowExpectation("REE_BOOT", 0x0A, 0x034B, 0x00000001, 0x00D4, 0x041F, 843, "boot-event-core/shared-slot-0x0A/shared-slot-template"),
RepeatedFamilyRowExpectation("REE_BOOT", 0x0F, 0x025C, 0x0000034C, 0x041F, 0x067B, 604, "boot-event-core/shared-slot-0x0F/shared-slot-template"),
RepeatedFamilyRowExpectation("REE_BOOT", 0x10, 0x003B, 0x000005A8, 0x067B, 0x06B6, 59, "boot-event-core/shared-slot-0x10/same-length-template"),
RepeatedFamilyRowExpectation("VAR_BOOT", 0x0A, 0x029A, 0x00000001, 0x00D4, 0x036E, 666, "boot-event-core/shared-slot-0x0A/shared-slot-template"),
RepeatedFamilyRowExpectation("VAR_BOOT", 0x0F, 0x0244, 0x0000029B, 0x036E, 0x05B2, 580, "boot-event-core/shared-slot-0x0F/shared-slot-template"),
RepeatedFamilyRowExpectation("VAR_BOOT", 0x10, 0x003B, 0x000004DF, 0x05B2, 0x05ED, 59, "boot-event-core/shared-slot-0x10/same-length-template"),
RepeatedFamilyRowExpectation("SURCAMNS", 0x01, 0x0051, 0x000000D2, 0x01B7, 0x0208, 81, "callback-eventtrigger/shared-slot-0x01/shared-slot-template"),
RepeatedFamilyRowExpectation("SURCAMNS", 0x0A, 0x00D1, 0x00000001, 0x00E6, 0x01B7, 209, "callback-eventtrigger/shared-slot-0x0A/same-length-template"),
RepeatedFamilyRowExpectation("SURCAMNS", 0x20, 0x02BA, 0x00000123, 0x0208, 0x04C2, 698, "callback-eventtrigger/shared-slot-0x20/same-length-template"),
RepeatedFamilyRowExpectation("SURCAMNS", 0x21, 0x0709, 0x000003DD, 0x04C2, 0x0BCB, 1801, "callback-eventtrigger/shared-slot-0x21/shared-slot-template"),
RepeatedFamilyRowExpectation("SURCAMNS", 0x22, 0x01A3, 0x00000AE6, 0x0BCB, 0x0D6E, 419, "callback-eventtrigger/shared-slot-0x22/same-length-template"),
RepeatedFamilyRowExpectation("SURCAMEW", 0x01, 0x00F7, 0x000000D2, 0x01B7, 0x02AE, 247, "callback-eventtrigger/shared-slot-0x01/shared-slot-template"),
RepeatedFamilyRowExpectation("SURCAMEW", 0x0A, 0x00D1, 0x00000001, 0x00E6, 0x01B7, 209, "callback-eventtrigger/shared-slot-0x0A/same-length-template"),
RepeatedFamilyRowExpectation("SURCAMEW", 0x20, 0x02BA, 0x000001C9, 0x02AE, 0x0568, 698, "callback-eventtrigger/shared-slot-0x20/same-length-template"),
RepeatedFamilyRowExpectation("SURCAMEW", 0x21, 0x0655, 0x00000483, 0x0568, 0x0BBD, 1621, "callback-eventtrigger/shared-slot-0x21/shared-slot-template"),
RepeatedFamilyRowExpectation("SURCAMEW", 0x22, 0x01A3, 0x00000AD8, 0x0BBD, 0x0D60, 419, "callback-eventtrigger/shared-slot-0x22/same-length-template"),
RepeatedFamilyRowExpectation("FLAMEBOX", 0x0A, 0x026A, 0x00000001, 0x00E0, 0x034A, 618, "environmental-event/shared-slot-0x0A/shared-slot-template"),
RepeatedFamilyRowExpectation("FLAMEBOX", 0x20, 0x01AC, 0x0000026B, 0x034A, 0x04F6, 428, "environmental-event/shared-slot-0x20/shared-slot-template"),
RepeatedFamilyRowExpectation("FLAMEBOX", 0x21, 0x029A, 0x00000417, 0x04F6, 0x0790, 666, "environmental-event/shared-slot-0x21/shared-slot-template"),
RepeatedFamilyRowExpectation("NOSTRIL", 0x0A, 0x00C0, 0x00000001, 0x00E0, 0x01A0, 192, "environmental-event/shared-slot-0x0A/shared-slot-template"),
RepeatedFamilyRowExpectation("NOSTRIL", 0x20, 0x0129, 0x000000C1, 0x01A0, 0x02C9, 297, "environmental-event/shared-slot-0x20/shared-slot-template"),
RepeatedFamilyRowExpectation("NOSTRIL", 0x21, 0x01BE, 0x000001EA, 0x02C9, 0x0487, 446, "environmental-event/shared-slot-0x21/shared-slot-template"),
RepeatedFamilyRowExpectation("STEAMBOX", 0x0A, 0x0266, 0x00000001, 0x00E0, 0x0346, 614, "environmental-event/shared-slot-0x0A/shared-slot-template"),
RepeatedFamilyRowExpectation("STEAMBOX", 0x20, 0x01F6, 0x00000267, 0x0346, 0x053C, 502, "environmental-event/shared-slot-0x20/shared-slot-template"),
RepeatedFamilyRowExpectation("STEAMBOX", 0x21, 0x02A7, 0x0000045D, 0x053C, 0x07E3, 679, "environmental-event/shared-slot-0x21/shared-slot-template"),
)
def scummvm_event_name_hint(slot: int) -> str | None:
if 0 <= slot < len(SCUMMVM_EVENT_NAME_HINTS):
return SCUMMVM_EVENT_NAME_HINTS[slot]
@ -532,6 +634,368 @@ def annotate_class_layout(chunks: list[ExtractedChunk]) -> None:
chunk.class_parse_status = "parsed-class-layout"
def derive_class_event_rows(chunk: ExtractedChunk, raw_data: bytes) -> list[ClassEventRow]:
if chunk.class_parse_status != "parsed-class-layout":
return []
if chunk.object_index is None or chunk.class_id is None or chunk.conservative_event_count is None:
return []
provisional_rows: list[tuple[int, int, int]] = []
for slot in range(chunk.conservative_event_count):
entry_offset = 20 + 6 * slot
raw_word = read_u16_le(raw_data, entry_offset)
raw_code_offset = read_u32_le(raw_data, entry_offset + 2)
provisional_rows.append((slot, raw_word, raw_code_offset))
non_zero_offsets = sorted(
{
raw_code_offset
for _, _, raw_code_offset in provisional_rows
if raw_code_offset != 0
}
)
rows: list[ClassEventRow] = []
for slot, raw_word, raw_code_offset in provisional_rows:
derived_body_start: int | None = None
derived_body_end: int | None = None
derived_body_length: int | None = None
if raw_code_offset != 0 and chunk.code_base_minus_one is not None:
body_start = chunk.code_base_minus_one + raw_code_offset
next_offsets = [offset for offset in non_zero_offsets if offset > raw_code_offset]
body_end = chunk.code_base_minus_one + next_offsets[0] if next_offsets else len(raw_data)
if 0 <= body_start <= body_end <= len(raw_data):
derived_body_start = body_start
derived_body_end = body_end
derived_body_length = body_end - body_start
rows.append(
ClassEventRow(
entry_index=chunk.index,
object_index=chunk.object_index,
class_id=chunk.class_id,
class_name_hint=chunk.class_name_hint or "",
slot=slot,
event_name_hint=scummvm_event_name_hint(slot),
raw_event_entry_word=raw_word,
raw_code_offset=raw_code_offset,
derived_body_start=derived_body_start,
derived_body_end=derived_body_end,
derived_body_length=derived_body_length,
)
)
return rows
def build_class_event_rows(
parsed_class_chunks: list[ExtractedChunk],
) -> tuple[list[ClassEventRow], dict[int, list[ClassEventRow]], dict[int, bytes]]:
all_rows: list[ClassEventRow] = []
rows_by_entry: dict[int, list[ClassEventRow]] = {}
raw_data_by_entry: dict[int, bytes] = {}
for chunk in parsed_class_chunks:
raw_data = pathlib.Path(chunk.raw_path).read_bytes()
raw_data_by_entry[chunk.index] = raw_data
rows = derive_class_event_rows(chunk, raw_data)
rows_by_entry[chunk.index] = rows
all_rows.extend(rows)
return all_rows, rows_by_entry, raw_data_by_entry
def build_repeated_template_status_map(
parsed_class_chunks: list[ExtractedChunk],
rows_by_entry: dict[int, list[ClassEventRow]],
raw_data_by_entry: dict[int, bytes],
) -> dict[tuple[int, int], str]:
status_by_row: dict[tuple[int, int], str] = {}
chunk_by_label = {
chunk.primary_label: chunk
for chunk in parsed_class_chunks
if chunk.primary_label
}
for family_name, labels in VERIFIED_REPEATED_TEMPLATE_FAMILIES:
family_chunks = [chunk_by_label[label] for label in labels if label in chunk_by_label]
if len(family_chunks) < 2:
continue
rows_by_slot: dict[int, list[tuple[ExtractedChunk, ClassEventRow, bytes]]] = {}
for chunk in family_chunks:
raw_data = raw_data_by_entry.get(chunk.index)
if raw_data is None:
continue
for row in rows_by_entry.get(chunk.index, []):
if row.raw_code_offset == 0:
continue
if row.derived_body_start is None or row.derived_body_end is None:
continue
body = raw_data[row.derived_body_start:row.derived_body_end]
rows_by_slot.setdefault(row.slot, []).append((chunk, row, body))
for slot, slot_rows in rows_by_slot.items():
if len(slot_rows) < 2:
continue
lengths = {len(body) for _, _, body in slot_rows}
bodies = {body for _, _, body in slot_rows}
if len(bodies) == 1:
status_suffix = "exact-body-clone"
elif len(lengths) == 1:
status_suffix = "same-length-template"
else:
status_suffix = "shared-slot-template"
status = f"{family_name}/shared-slot-0x{slot:02X}/{status_suffix}"
for chunk, row, _ in slot_rows:
status_by_row[(chunk.index, row.slot)] = status
return status_by_row
def format_optional_hex(value: int | None, width: int = 0) -> str:
if value is None:
return ""
if width > 0:
return f"0x{value:0{width}X}"
return f"0x{value:X}"
def hex_edge(data: bytes, width: int = 8) -> str:
if not data:
return ""
return data[:width].hex()
def hex_tail(data: bytes, width: int = 8) -> str:
if not data:
return ""
return data[-width:].hex()
def write_family_decompile_artifact(
out_dir: pathlib.Path,
parsed_class_chunks: list[ExtractedChunk],
rows_by_entry: dict[int, list[ClassEventRow]],
raw_data_by_entry: dict[int, bytes],
repeated_status_by_row: dict[tuple[int, int], str],
spec: FamilyArtifactSpec,
) -> None:
family_labels = set(spec.labels)
family_chunks = [chunk for chunk in parsed_class_chunks if chunk.primary_label in family_labels]
if not family_chunks:
return
family_chunks.sort(key=lambda chunk: chunk.primary_label or "")
tsv_lines = [
"entry_index\tclass_id\tclass_name\tslot\tevent_name_hint\traw_event_entry_word\traw_code_offset\tderived_body_start\tderived_body_end\tderived_body_length\trepeated_template_status\tbody_sha1\tbody_prefix_hex\tbody_suffix_hex"
]
md_lines = [
f"# {spec.title}",
"",
"This is a reversible per-class rendering derived directly from `class_event_index.tsv` plus the raw extracted chunk bytes.",
"ScummVM event labels remain hints only; the authoritative data here is the slot id, raw row bytes, and derived body window.",
"",
]
for chunk in family_chunks:
rows = [row for row in rows_by_entry.get(chunk.index, []) if row.raw_code_offset != 0]
if not rows:
continue
raw_data = raw_data_by_entry[chunk.index]
md_lines.extend([
f"## {chunk.primary_label}",
"",
"```yaml",
"class:",
f" entry_index: 0x{chunk.index:03X}",
f" class_id: 0x{chunk.class_id:X}",
f" class_name: {chunk.primary_label}",
f" class_object_index: 0x{chunk.object_index:X}",
f" raw_code_base_u32: 0x{chunk.raw_code_base_u32:X}",
f" code_base_minus_one: 0x{chunk.code_base_minus_one:X}",
f" conservative_event_count: {chunk.conservative_event_count}",
" events:",
])
for row in rows:
body = b""
if row.derived_body_start is not None and row.derived_body_end is not None:
body = raw_data[row.derived_body_start:row.derived_body_end]
repeated_status = repeated_status_by_row.get((row.entry_index, row.slot), "")
body_sha1 = hashlib.sha1(body).hexdigest() if body else ""
md_lines.extend([
f" - slot: 0x{row.slot:02x}",
f" event_name_hint: {row.event_name_hint or ''}",
f" raw_event_entry_word: 0x{row.raw_event_entry_word:04x}",
f" raw_code_offset: 0x{row.raw_code_offset:08x}",
f" derived_body_start: {format_optional_hex(row.derived_body_start, 4).lower() or 'null'}",
f" derived_body_end: {format_optional_hex(row.derived_body_end, 4).lower() or 'null'}",
f" derived_body_length: {row.derived_body_length if row.derived_body_length is not None else 'null'}",
f" repeated_template_status: {repeated_status or 'unique-or-unclassified'}",
f" body_sha1: {body_sha1 or 'null'}",
f" body_prefix_hex: {hex_edge(body) or 'null'}",
f" body_suffix_hex: {hex_tail(body) or 'null'}",
])
tsv_lines.append(
"{entry_index}\t0x{class_id:X}\t{class_name}\t0x{slot:02X}\t{event_name_hint}\t0x{raw_event_entry_word:04X}\t0x{raw_code_offset:08X}\t{derived_body_start}\t{derived_body_end}\t{derived_body_length}\t{repeated_template_status}\t{body_sha1}\t{body_prefix_hex}\t{body_suffix_hex}".format(
entry_index=row.entry_index,
class_id=row.class_id,
class_name=chunk.primary_label or "",
slot=row.slot,
event_name_hint=row.event_name_hint or "",
raw_event_entry_word=row.raw_event_entry_word,
raw_code_offset=row.raw_code_offset,
derived_body_start=format_optional_hex(row.derived_body_start, 4),
derived_body_end=format_optional_hex(row.derived_body_end, 4),
derived_body_length=(row.derived_body_length if row.derived_body_length is not None else ""),
repeated_template_status=repeated_status,
body_sha1=body_sha1,
body_prefix_hex=hex_edge(body),
body_suffix_hex=hex_tail(body),
)
)
md_lines.extend([
"```",
"",
])
(out_dir / f"{spec.output_stem}.md").write_text("\n".join(md_lines), encoding="utf-8")
(out_dir / f"{spec.output_stem}.tsv").write_text("\n".join(tsv_lines) + "\n", encoding="utf-8")
def validate_verified_repeated_family_regressions(
parsed_class_chunks: list[ExtractedChunk],
rows_by_entry: dict[int, list[ClassEventRow]],
repeated_status_by_row: dict[tuple[int, int], str],
) -> list[str]:
chunk_by_label = {
chunk.primary_label: chunk
for chunk in parsed_class_chunks
if chunk.primary_label
}
expected_slots_by_class: dict[str, set[int]] = {}
for expectation in VERIFIED_REPEATED_FAMILY_ROW_EXPECTATIONS:
expected_slots_by_class.setdefault(expectation.class_name, set()).add(expectation.slot)
report_lines = [
"record_type\tclass_name\tslot\texpected\tactual\tstatus"
]
errors: list[str] = []
for class_name, expected_slots in sorted(expected_slots_by_class.items()):
chunk = chunk_by_label.get(class_name)
actual_slots: set[int] = set()
if chunk is not None:
actual_slots = {
row.slot
for row in rows_by_entry.get(chunk.index, [])
if row.raw_code_offset != 0
}
status = "ok" if actual_slots == expected_slots else "mismatch"
report_lines.append(
"slot-set\t{class_name}\t*\t{expected}\t{actual}\t{status}".format(
class_name=class_name,
expected=",".join(f"0x{slot:02X}" for slot in sorted(expected_slots)),
actual=",".join(f"0x{slot:02X}" for slot in sorted(actual_slots)),
status=status,
)
)
if status != "ok":
errors.append(
f"{class_name}: expected non-zero slots {sorted(expected_slots)}, found {sorted(actual_slots)}"
)
for expectation in VERIFIED_REPEATED_FAMILY_ROW_EXPECTATIONS:
chunk = chunk_by_label.get(expectation.class_name)
if chunk is None:
errors.append(f"missing repeated-family class {expectation.class_name}")
report_lines.append(
f"row\t{expectation.class_name}\t0x{expectation.slot:02X}\tpresent\tmissing-class\tmismatch"
)
continue
row = next(
(candidate for candidate in rows_by_entry.get(chunk.index, []) if candidate.slot == expectation.slot),
None,
)
if row is None:
errors.append(f"missing row {expectation.class_name} slot 0x{expectation.slot:02X}")
report_lines.append(
f"row\t{expectation.class_name}\t0x{expectation.slot:02X}\tpresent\tmissing-row\tmismatch"
)
continue
actual_values = (
row.raw_event_entry_word,
row.raw_code_offset,
row.derived_body_start,
row.derived_body_end,
row.derived_body_length,
repeated_status_by_row.get((row.entry_index, row.slot), ""),
)
expected_values = (
expectation.raw_event_entry_word,
expectation.raw_code_offset,
expectation.derived_body_start,
expectation.derived_body_end,
expectation.derived_body_length,
expectation.repeated_template_status,
)
status = "ok" if actual_values == expected_values else "mismatch"
report_lines.append(
"row\t{class_name}\t0x{slot:02X}\t{expected}\t{actual}\t{status}".format(
class_name=expectation.class_name,
slot=expectation.slot,
expected="|".join(
[
f"0x{expectation.raw_event_entry_word:04X}",
f"0x{expectation.raw_code_offset:08X}",
f"0x{expectation.derived_body_start:04X}",
f"0x{expectation.derived_body_end:04X}",
str(expectation.derived_body_length),
expectation.repeated_template_status,
]
),
actual="|".join(
[
f"0x{row.raw_event_entry_word:04X}",
f"0x{row.raw_code_offset:08X}",
format_optional_hex(row.derived_body_start, 4),
format_optional_hex(row.derived_body_end, 4),
str(row.derived_body_length if row.derived_body_length is not None else ""),
repeated_status_by_row.get((row.entry_index, row.slot), ""),
]
),
status=status,
)
)
if status != "ok":
errors.append(
"{class_name} slot 0x{slot:02X}: expected {expected}, found {actual}".format(
class_name=expectation.class_name,
slot=expectation.slot,
expected=expected_values,
actual=actual_values,
)
)
if errors:
raise ValueError(
"repeated-family regression mismatch:\n- " + "\n- ".join(errors)
)
return report_lines
def readable_neighbor_chunks(
center: ExtractedChunk,
chunk_by_index: dict[int, ExtractedChunk],
@ -1556,6 +2020,17 @@ def write_summary(out_dir: pathlib.Path, input_path: pathlib.Path, data: bytes,
"entry_index\tobject_index\tclass_id\tclass_name_hint\traw_code_base_u32\tcode_base_minus_one\tconservative_event_count\tevent_table_end\tclass_parse_status\tdata_offset\tdeclared_size\tprimary_label"
]
parsed_class_chunks = [chunk for chunk in chunks if chunk.class_parse_status == "parsed-class-layout"]
class_event_rows, rows_by_entry, raw_data_by_entry = build_class_event_rows(parsed_class_chunks)
repeated_status_by_row = build_repeated_template_status_map(
parsed_class_chunks,
rows_by_entry,
raw_data_by_entry,
)
repeated_family_regression_lines = validate_verified_repeated_family_regressions(
parsed_class_chunks,
rows_by_entry,
repeated_status_by_row,
)
for chunk in parsed_class_chunks:
class_layout_lines.append(
"{index}\t0x{object_index:X}\t0x{class_id:X}\t{class_name_hint}\t0x{raw_code_base_u32:X}\t0x{code_base_minus_one:X}\t{conservative_event_count}\t0x{event_table_end:X}\t{class_parse_status}\t0x{data_offset:X}\t0x{declared_size:X}\t{primary_label}".format(
@ -1576,28 +2051,39 @@ def write_summary(out_dir: pathlib.Path, input_path: pathlib.Path, data: bytes,
(out_dir / "class_layout_index.tsv").write_text("\n".join(class_layout_lines) + "\n", encoding="utf-8")
class_event_lines = [
"entry_index\tobject_index\tclass_id\tclass_name_hint\tslot\tevent_name_hint\traw_event_entry_word\traw_code_offset"
"entry_index\tobject_index\tclass_id\tclass_name_hint\tslot\tevent_name_hint\traw_event_entry_word\traw_code_offset\tderived_body_start\tderived_body_end\tderived_body_length\trepeated_template_status"
]
for chunk in parsed_class_chunks:
raw_data = pathlib.Path(chunk.raw_path).read_bytes()
assert chunk.conservative_event_count is not None
for slot in range(chunk.conservative_event_count):
entry_offset = 20 + 6 * slot
raw_word = read_u16_le(raw_data, entry_offset)
raw_code_offset = read_u32_le(raw_data, entry_offset + 2)
class_event_lines.append(
"{entry_index}\t0x{object_index:X}\t0x{class_id:X}\t{class_name_hint}\t0x{slot:02X}\t{event_name_hint}\t0x{raw_word:04X}\t0x{raw_code_offset:08X}".format(
entry_index=chunk.index,
object_index=chunk.object_index,
class_id=chunk.class_id,
class_name_hint=chunk.class_name_hint or "",
slot=slot,
event_name_hint=scummvm_event_name_hint(slot) or "",
raw_word=raw_word,
raw_code_offset=raw_code_offset,
)
for row in class_event_rows:
class_event_lines.append(
"{entry_index}\t0x{object_index:X}\t0x{class_id:X}\t{class_name_hint}\t0x{slot:02X}\t{event_name_hint}\t0x{raw_event_entry_word:04X}\t0x{raw_code_offset:08X}\t{derived_body_start}\t{derived_body_end}\t{derived_body_length}\t{repeated_template_status}".format(
entry_index=row.entry_index,
object_index=row.object_index,
class_id=row.class_id,
class_name_hint=row.class_name_hint,
slot=row.slot,
event_name_hint=row.event_name_hint or "",
raw_event_entry_word=row.raw_event_entry_word,
raw_code_offset=row.raw_code_offset,
derived_body_start=format_optional_hex(row.derived_body_start, 4),
derived_body_end=format_optional_hex(row.derived_body_end, 4),
derived_body_length=(row.derived_body_length if row.derived_body_length is not None else ""),
repeated_template_status=repeated_status_by_row.get((row.entry_index, row.slot), ""),
)
)
(out_dir / "class_event_index.tsv").write_text("\n".join(class_event_lines) + "\n", encoding="utf-8")
for family_artifact_spec in FAMILY_ARTIFACT_SPECS:
write_family_decompile_artifact(
out_dir,
parsed_class_chunks,
rows_by_entry,
raw_data_by_entry,
repeated_status_by_row,
family_artifact_spec,
)
(out_dir / "repeated_family_regressions.tsv").write_text(
"\n".join(repeated_family_regression_lines) + "\n",
encoding="utf-8",
)
neighborhood_lines = [
"center_index\tneighbor_index\tprimary_label\tfield_names\tfield_tags"
@ -1763,7 +2249,9 @@ def write_summary(out_dir: pathlib.Path, input_path: pathlib.Path, data: bytes,
lines.append("- `.strings.txt` files are the main human-readable output for now; `.txt` files are emitted only for chunks that look text-like.")
lines.append("- `descriptor_index.tsv` summarizes guessed class labels, field names, and compact tag patterns for descriptor-like chunks.")
lines.append("- `class_layout_index.tsv` records the conservative owner-loaded class parsing state: object index, class id, class-name hint, raw bytes-8..11 field, derived code-base-minus-one, and event-count/table-end values when the local divisibility and bounds checks succeed.")
lines.append("- `class_event_index.tsv` expands parsed owner-loaded classes into raw 6-byte event rows with slot numbers, ScummVM event-name hints for `0x00..0x1f`, unresolved leading words, and raw code-offset dwords for round-trip tooling work.")
lines.append("- `class_event_index.tsv` now also emits derived body-window columns (`derived_body_start`, `derived_body_end`, `derived_body_length`) plus conservative `repeated_template_status` tags for verified repeated families.")
lines.append("- `boot_family_decompile.md` / `.tsv`, `callback_family_decompile.md` / `.tsv`, and `environmental_family_decompile.md` / `.tsv` now provide reversible per-class decompile artifacts for the `_BOOT`, `SURCAM*`, and environmental repeated-family lanes.")
lines.append("- `repeated_family_regressions.tsv` enforces the current repeated-family slot sets plus the verified raw-row and derived body-window fields for `JELYHACK/JELYH2`, `_BOOT`, `SURCAM*`, and `FLAMEBOX/NOSTRIL/STEAMBOX`.")
lines.append("- `descriptor_neighborhoods.tsv` captures local table neighborhoods around trigger/event-related classes such as `JELYHACK`, `NPCTRIG`, `CRUZTRIG`, `TRIGPAD`, and `SPECIAL`.")
lines.append("- `referent_anchor_event_graph.tsv` groups referent-bearing descriptors with nearby event-bearing neighbors so the attachment model can be inspected without ad hoc grepping.")
lines.append("- `jelyhack_island_graph.md` now uses a wider local window so the `JELYHACK` / `JELYH2` anchors can be inspected alongside the nearby event-bearing `REE_BOOT`, `SURCAMEW`, and `SFXTRIG` descriptors rather than stopping at the referent-only neighbors.")

View file

@ -0,0 +1,165 @@
import csv
import glob
import hashlib
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
LAYOUT_PATH = ROOT / "USECODE" / "EUSECODE_extracted" / "class_layout_index.tsv"
EVENT_PATH = ROOT / "USECODE" / "EUSECODE_extracted" / "class_event_index.tsv"
CHUNKS_DIR = ROOT / "USECODE" / "EUSECODE_extracted" / "chunks"
FAMILIES = {
"BOOT": {
"classes": ["AND_BOOT", "BRO_BOOT", "COR_BOOT", "REE_BOOT", "VAR_BOOT"],
"slots": [0x0A, 0x0F, 0x10],
},
"SURCAM": {
"classes": ["SURCAMNS", "SURCAMEW"],
"slots": [0x20, 0x21, 0x22],
},
"JELY": {
"classes": ["JELYHACK", "JELYH2"],
"slots": [0x01],
},
}
def parse_hex(value: str) -> int:
return int(value, 16)
def common_prefix_length(blobs: list[bytes]) -> int:
if not blobs:
return 0
limit = min(len(blob) for blob in blobs)
for index in range(limit):
current = blobs[0][index]
if any(blob[index] != current for blob in blobs[1:]):
return index
return limit
def common_suffix_length(blobs: list[bytes]) -> int:
return common_prefix_length([blob[::-1] for blob in blobs])
def first_diff_positions(blobs: list[bytes], limit: int = 8) -> list[int]:
positions: list[int] = []
max_len = max(len(blob) for blob in blobs)
for index in range(max_len):
values = {blob[index] if index < len(blob) else None for blob in blobs}
if len(values) > 1:
positions.append(index)
if len(positions) >= limit:
break
return positions
def load_layouts(targets: set[str]) -> dict[str, dict[str, str]]:
layouts: dict[str, dict[str, str]] = {}
with LAYOUT_PATH.open("r", encoding="utf-8", newline="") as handle:
reader = csv.DictReader(handle, delimiter="\t")
for row in reader:
if row["class_name_hint"] in targets:
layouts[row["class_name_hint"]] = row
return layouts
def load_events(targets: set[str]) -> dict[str, list[dict[str, str]]]:
events: dict[str, list[dict[str, str]]] = {}
with EVENT_PATH.open("r", encoding="utf-8", newline="") as handle:
reader = csv.DictReader(handle, delimiter="\t")
for row in reader:
if row["class_name_hint"] in targets:
events.setdefault(row["class_name_hint"], []).append(row)
for rows in events.values():
rows.sort(key=lambda row: parse_hex(row["slot"]))
return events
def resolve_chunk(data_offset: int) -> Path:
matches = glob.glob(str(CHUNKS_DIR / f"chunk_*_off_{data_offset:06X}_len_*.bin"))
if len(matches) != 1:
raise RuntimeError(f"chunk lookup failed for 0x{data_offset:06X}: {matches}")
return Path(matches[0])
def build_rows() -> dict[tuple[str, int], dict[str, object]]:
targets = {name for family in FAMILIES.values() for name in family["classes"]}
layouts = load_layouts(targets)
events = load_events(targets)
rows_by_key: dict[tuple[str, int], dict[str, object]] = {}
for class_name, layout in layouts.items():
chunk_path = resolve_chunk(parse_hex(layout["data_offset"]))
blob = chunk_path.read_bytes()
code_base_minus_one = parse_hex(layout["code_base_minus_one"])
nonzero_rows = [row for row in events[class_name] if parse_hex(row["raw_code_offset"]) != 0]
offsets = sorted({parse_hex(row["raw_code_offset"]) for row in nonzero_rows})
for row in nonzero_rows:
slot = parse_hex(row["slot"])
code_offset = parse_hex(row["raw_code_offset"])
start = code_base_minus_one + code_offset
next_offsets = [offset for offset in offsets if offset > code_offset]
end = code_base_minus_one + next_offsets[0] if next_offsets else len(blob)
body = blob[start:end]
rows_by_key[(class_name, slot)] = {
"class_name": class_name,
"slot": slot,
"event_name_hint": row["event_name_hint"],
"raw_event_entry_word": row["raw_event_entry_word"],
"raw_code_offset": row["raw_code_offset"],
"start": start,
"end": end,
"length": len(body),
"sha1": hashlib.sha1(body).hexdigest(),
"preview": body[:16].hex(" "),
"chunk_path": str(chunk_path).replace("\\", "/"),
"body": body,
}
return rows_by_key
def main() -> None:
rows_by_key = build_rows()
for family_name, family in FAMILIES.items():
print(f"## {family_name}")
for slot in family["slots"]:
print(f"SLOT 0x{slot:02X}")
subset = [rows_by_key[(class_name, slot)] for class_name in family["classes"]]
for row in subset:
print(
"\t".join(
[
str(row["class_name"]),
str(row["event_name_hint"]),
str(row["raw_event_entry_word"]),
str(row["raw_code_offset"]),
f"{row['start']:04X}-{row['end']:04X}",
str(row["length"]),
str(row["sha1"])[:12],
str(row["preview"]),
str(row["chunk_path"]),
]
)
)
groups: dict[str, list[str]] = {}
for row in subset:
groups.setdefault(str(row["sha1"]), []).append(str(row["class_name"]))
blobs = [row["body"] for row in subset]
print("identical_groups=" + json.dumps(list(groups.values())))
print(
f"common_prefix_len={common_prefix_length(blobs)} "
f"common_suffix_len={common_suffix_length(blobs)}"
)
print("first_diff_positions=" + json.dumps(first_diff_positions(blobs)))
print()
if __name__ == "__main__":
main()