PSX Research

This commit is contained in:
Marco 2026-04-07 17:16:44 +02:00
commit 94c49ac5bd
16 changed files with 2052 additions and 16 deletions

View file

@ -11,7 +11,808 @@
</SAVE_STATE>
</PROJECT_DATA_XML_NAME>
<TOOL_MANAGER ACTIVE_WORKSPACE="Workspace">
<WORKSPACE NAME="Workspace" ACTIVE="true" />
<WORKSPACE NAME="Workspace" ACTIVE="true">
<RUNNING_TOOL TOOL_NAME="CodeBrowser">
<ROOT_NODE X_POS="-2" Y_POS="15" WIDTH="1381" HEIGHT="875" EX_STATE="0">
<SPLIT_NODE WIDTH="100" HEIGHT="100" DIVIDER_LOCATION="0" ORIENTATION="VERTICAL">
<SPLIT_NODE WIDTH="1866" HEIGHT="1014" DIVIDER_LOCATION="774" ORIENTATION="HORIZONTAL">
<SPLIT_NODE WIDTH="100" HEIGHT="100" DIVIDER_LOCATION="0" ORIENTATION="VERTICAL">
<SPLIT_NODE WIDTH="1866" HEIGHT="1014" DIVIDER_LOCATION="880" ORIENTATION="VERTICAL">
<SPLIT_NODE WIDTH="1866" 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="1367" HEIGHT="779" DIVIDER_LOCATION="174" ORIENTATION="HORIZONTAL">
<SPLIT_NODE WIDTH="237" HEIGHT="779" DIVIDER_LOCATION="640" ORIENTATION="VERTICAL">
<SPLIT_NODE WIDTH="237" HEIGHT="496" 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="3723604406647099771" />
</COMPONENT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Symbol Tree" OWNER="SymbolTreePlugin" TITLE="Symbol Tree" ACTIVE="true" GROUP="Default" INSTANCE_ID="3723604406647099766" />
</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="3723604490275230076" />
</COMPONENT_NODE>
</SPLIT_NODE>
<SPLIT_NODE WIDTH="1126" HEIGHT="779" DIVIDER_LOCATION="785" ORIENTATION="VERTICAL">
<SPLIT_NODE WIDTH="1386" HEIGHT="638" DIVIDER_LOCATION="705" ORIENTATION="VERTICAL">
<SPLIT_NODE WIDTH="1126" HEIGHT="779" DIVIDER_LOCATION="490" ORIENTATION="HORIZONTAL">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Listing" OWNER="CodeBrowserPlugin" TITLE="Listing: SLUS_002.68" ACTIVE="true" GROUP="Core" INSTANCE_ID="3723604488515719525" />
</COMPONENT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Decompiler" OWNER="DecompilePlugin" TITLE="Decompile: psx_object_create_simple_record" ACTIVE="true" GROUP="Default" INSTANCE_ID="3723604406647099772" />
<COMPONENT_INFO NAME="Bytes" OWNER="ByteViewerPlugin" TITLE="Bytes: SLUS_002.68" ACTIVE="false" GROUP="Default" INSTANCE_ID="3723604406647099774" />
<COMPONENT_INFO NAME="Data Window" OWNER="DataWindowPlugin" TITLE="Defined Data" ACTIVE="false" GROUP="Default" INSTANCE_ID="3723604490583511403" />
<COMPONENT_INFO NAME="Defined Strings" OWNER="ViewStringsPlugin" TITLE="Defined Strings" ACTIVE="false" GROUP="Default" INSTANCE_ID="3723604490583511407" />
<COMPONENT_INFO NAME="Equates Table" OWNER="EquateTablePlugin" TITLE="Equates Table" ACTIVE="false" GROUP="Default" INSTANCE_ID="3723604488515719523" />
<COMPONENT_INFO NAME="External Programs" OWNER="ReferencesPlugin" TITLE="External Programs" ACTIVE="false" GROUP="Default" INSTANCE_ID="3723604488515719526" />
<COMPONENT_INFO NAME="Functions Window" OWNER="FunctionWindowPlugin" TITLE="Functions" ACTIVE="false" GROUP="Default" INSTANCE_ID="3723604488515719529" />
<COMPONENT_INFO NAME="Relocation Table" OWNER="RelocationTablePlugin" TITLE="Relocation Table" ACTIVE="false" GROUP="Default" INSTANCE_ID="3723604490583511406" />
</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="3723604490008891766" />
</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="3723604488515719522" />
</COMPONENT_NODE>
</SPLIT_NODE>
</SPLIT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Console" OWNER="ConsolePlugin" TITLE="Console" ACTIVE="false" GROUP="Default" INSTANCE_ID="3723604406647099773" />
<COMPONENT_INFO NAME="Bookmarks" OWNER="BookmarkPlugin" TITLE="Bookmarks" ACTIVE="false" GROUP="Core.Bookmarks" INSTANCE_ID="3723604406647099770" />
</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="3723604406647099767" />
</COMPONENT_NODE>
</SPLIT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Domain Events" OWNER="DomainEventDisplayPlugin" TITLE="Domain Object Event Display" ACTIVE="false" GROUP="Default" INSTANCE_ID="3722070732148365067" />
</COMPONENT_NODE>
</SPLIT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Plugin Event Display" OWNER="EventDisplayPlugin" TITLE="Plugin Event Display" ACTIVE="false" GROUP="Default" INSTANCE_ID="3722070732148365064" />
</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="3722070734440552208" />
</COMPONENT_NODE>
</SPLIT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Decompiler" OWNER="DecompilePlugin" TITLE="[Decompile: Weapon_GetDisplayFrameForShape]" ACTIVE="false" GROUP="disconnected" INSTANCE_ID="3721996248960424124" />
</COMPONENT_NODE>
</SPLIT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Diff Location Details" OWNER="ProgramDiffPlugin" TITLE="Diff Details" ACTIVE="false" GROUP="Default" INSTANCE_ID="3721161170088475892" />
</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="3723604406647099768" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="423" Y_POS="144" WIDTH="927" HEIGHT="695">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Memory Map" OWNER="MemoryMapPlugin" TITLE="Memory Map" ACTIVE="false" GROUP="Default" INSTANCE_ID="3723604406647099749" />
</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="3723604490583511408" />
</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="3723604488515719528" />
</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="3723604490583511404" />
</COMPONENT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Symbol References" OWNER="SymbolTablePlugin" TITLE="Symbol References" ACTIVE="false" GROUP="symbolTable" INSTANCE_ID="3723604490583511405" />
</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="3723604488515719524" />
</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="3723604490275230077" />
</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="3723604490583511402" />
</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="3723604490275230078" />
</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="3723604406647099769" />
</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="3723604490583511401" />
</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="3723604488515719527" />
</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="3723604490008891767" />
</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="3722070719626270492" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="890" Y_POS="456" WIDTH="729" HEIGHT="566">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Location References Provider" OWNER="LocationReferencesPlugin" TITLE="References to g_jassica16Offset - 7 locations" ACTIVE="false" GROUP="Default" INSTANCE_ID="3721161260863700211" />
</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="9" />
<STATE NAME="LOCATION_COUNT" TYPE="int" VALUE="10" />
<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" />
<STATE NAME="MEMENTO_CLASS7" TYPE="string" VALUE="ghidra.app.plugin.core.gotoquery.DefaultNavigatableLocationMemento" />
<STATE NAME="MEMENTO_CLASS8" TYPE="string" VALUE="ghidra.app.plugin.core.gotoquery.DefaultNavigatableLocationMemento" />
<STATE NAME="MEMENTO_CLASS9" 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="3723604488515719525" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="1f800000" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="1f800000" />
<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="// CACHE" />
<A VALUE="// ram:1f800000-ram:1f8003ff" />
<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="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="8002b3d4" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="8002b3d4" />
<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="8002b3d4" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined main()" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO_DATA1">
<SAVE_STATE>
<XML NAME="MEMENTO0">
<SAVE_STATE>
<STATE NAME="CURSOR_OFFSET" TYPE="int" VALUE="400" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.codebrowser.CodeViewerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3723604488515719525" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="8002b3d4" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="8002b3d4" />
<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="8002b3d4" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined main()" />
</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="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="1f800000" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="1f800000" />
<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="// CACHE" />
<A VALUE="// ram:1f800000-ram:1f8003ff" />
<A VALUE="//" />
</ARRAY>
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_TYPE" TYPE="int" VALUE="-1" />
</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="3723604406647099772" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="X_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="Y_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80045ffc" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80045ffc" />
<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="80045ffc" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined psx_cache_type_art_descriptor()" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO1">
<SAVE_STATE>
<STATE NAME="CURSOR_OFFSET" TYPE="int" VALUE="400" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.codebrowser.CodeViewerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3723604488515719525" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80045ffc" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80045ffc" />
<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="80045ffc" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined psx_cache_type_art_descriptor()" />
</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="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80045ffc" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80045ffc" />
<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="80045ffc" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined psx_cache_type_art_descriptor()" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO_DATA3">
<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="3723604406647099772" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="X_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="Y_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80011ef8" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80011ef8" />
<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="80011ef8" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined FUN_80011ef8()" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO1">
<SAVE_STATE>
<STATE NAME="CURSOR_OFFSET" TYPE="int" VALUE="369" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.codebrowser.CodeViewerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3723604488515719525" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80011ef8" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80011ef8" />
<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="80011ef8" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined FUN_80011ef8()" />
</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="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80011ef8" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80011ef8" />
<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="80011ef8" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined FUN_80011ef8()" />
</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="3723604406647099772" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="X_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="Y_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80012138" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80012138" />
<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="80012138" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined FUN_80012138()" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO1">
<SAVE_STATE>
<STATE NAME="CURSOR_OFFSET" TYPE="int" VALUE="369" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.codebrowser.CodeViewerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3723604488515719525" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80012138" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80012138" />
<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="80012138" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined FUN_80012138()" />
</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="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80012138" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80012138" />
<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="80012138" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined FUN_80012138()" />
</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="3723604406647099772" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="X_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="Y_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80012138" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80012138" />
<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="80012138" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined FUN_80012138()" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO1">
<SAVE_STATE>
<STATE NAME="CURSOR_OFFSET" TYPE="int" VALUE="369" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.codebrowser.CodeViewerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3723604488515719525" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80012538" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80012538" />
<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="80012538" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined FUN_80012538()" />
</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="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80012538" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80012538" />
<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="80012538" />
<STATE NAME="_RETURN_TYPE" TYPE="string" VALUE="undefined" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_SIGNATURE" TYPE="string" VALUE="undefined FUN_80012538()" />
</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="3723604406647099772" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="X_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="Y_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80012538" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80012538" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CHAR_POS" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.app.decompiler.location.DefaultDecompilerLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNCTION_ENTRY" TYPE="string" VALUE="80012538" />
<STATE NAME="_LINE_NUM" TYPE="int" VALUE="0" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_TOKEN_TEXT" TYPE="string" VALUE="" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO1">
<SAVE_STATE>
<STATE NAME="CURSOR_OFFSET" TYPE="int" VALUE="639" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.codebrowser.CodeViewerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3723604488515719525" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80012538" />
<STATE NAME="_ADDR_REP" TYPE="string" VALUE="80012538" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80012538" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.AddressFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
</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="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80012538" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80012538" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CHAR_POS" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.app.decompiler.location.DefaultDecompilerLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNCTION_ENTRY" TYPE="string" VALUE="80012538" />
<STATE NAME="_LINE_NUM" TYPE="int" VALUE="0" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_TOKEN_TEXT" TYPE="string" VALUE="" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO_DATA7">
<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="3723604406647099772" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="X_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="Y_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80012538" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80012538" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CHAR_POS" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.app.decompiler.location.DefaultDecompilerLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNCTION_ENTRY" TYPE="string" VALUE="80012538" />
<STATE NAME="_LINE_NUM" TYPE="int" VALUE="0" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_TOKEN_TEXT" TYPE="string" VALUE="" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO1">
<SAVE_STATE>
<STATE NAME="CURSOR_OFFSET" TYPE="int" VALUE="369" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.codebrowser.CodeViewerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3723604488515719525" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="800249f4" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="800249f4" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.CommentFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<ARRAY NAME="_COMMENT" TYPE="string">
<A VALUE="Section-0 object constructor for the simpler record layout. Resolves DAT_800758d8 and DAT_800758d0 by type id, copies authored x/y/z into obj+0x3c/+0x40/+0x44 as 16.16 fixed-point, then seeds the object state/render path." />
</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="2" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="800249f4" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="800249f4" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="69" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.CommentFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<ARRAY NAME="_COMMENT" TYPE="string">
<A VALUE="Section-0 object constructor for the simpler record layout. Resolves DAT_800758d8 and DAT_800758d0 by type id, copies authored x/y/z into obj+0x3c/+0x40/+0x44 as 16.16 fixed-point, then seeds the object state/render path." />
</ARRAY>
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_TYPE" TYPE="int" VALUE="1" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO_DATA8">
<SAVE_STATE>
<STATE NAME="FOCUSED_NAV" TYPE="long" VALUE="3723604406647099772" />
<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="3723604406647099772" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="X_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="Y_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="800249f4" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="800249f4" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CHAR_POS" TYPE="int" VALUE="14" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.app.decompiler.location.DefaultDecompilerLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNCTION_ENTRY" TYPE="string" VALUE="800249f4" />
<STATE NAME="_LINE_NUM" TYPE="int" VALUE="20" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_TOKEN_TEXT" TYPE="string" VALUE="DAT_80067814" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO1">
<SAVE_STATE>
<STATE NAME="CURSOR_OFFSET" TYPE="int" VALUE="45" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.codebrowser.CodeViewerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3723604488515719525" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="800249f4" />
<STATE NAME="_ADDR_REP" TYPE="string" VALUE="800249f4" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="800249f4" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.AddressFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
</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="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="800249f4" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="800249f4" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CHAR_POS" TYPE="int" VALUE="14" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.app.decompiler.location.DefaultDecompilerLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNCTION_ENTRY" TYPE="string" VALUE="800249f4" />
<STATE NAME="_LINE_NUM" TYPE="int" VALUE="20" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_TOKEN_TEXT" TYPE="string" VALUE="DAT_80067814" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO_DATA9">
<SAVE_STATE>
<STATE NAME="FOCUSED_NAV" TYPE="long" VALUE="3723604406647099772" />
<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="3723604406647099772" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="X_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="Y_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="800249f4" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="800249f4" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CHAR_POS" TYPE="int" VALUE="14" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.app.decompiler.location.DefaultDecompilerLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNCTION_ENTRY" TYPE="string" VALUE="800249f4" />
<STATE NAME="_LINE_NUM" TYPE="int" VALUE="20" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_TOKEN_TEXT" TYPE="string" VALUE="DAT_80067814" />
</SAVE_STATE>
</XML>
<XML NAME="MEMENTO1">
<SAVE_STATE>
<STATE NAME="CURSOR_OFFSET" TYPE="int" VALUE="369" />
<STATE NAME="MEMENTO_CLASS" TYPE="string" VALUE="ghidra.app.plugin.core.codebrowser.CodeViewerLocationMemento" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3723604488515719525" />
<STATE NAME="PROGRAM_ID" TYPE="long" VALUE="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80067814" />
<STATE NAME="_ADDR_REP" TYPE="string" VALUE="80067814" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80067814" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.AddressFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<ARRAY NAME="_COMP_PATH" TYPE="int" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
</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="3722017627685836549" />
<STATE NAME="PROGRAM_PATH_" TYPE="string" VALUE="Crusader:/psx/remorse/SLUS_002.68" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80067814" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80067814" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.ProgramLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
</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="SLUS_002.68" />
</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="3723604406647099772" />
<STATE NAME="Num Disconnected" TYPE="int" VALUE="0" />
<STATE NAME="Y_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="800249f4" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="800249f4" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CHAR_POS" TYPE="int" VALUE="14" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.app.decompiler.location.DefaultDecompilerLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="0" />
<STATE NAME="_FUNCTION_ENTRY" TYPE="string" VALUE="800249f4" />
<STATE NAME="_LINE_NUM" TYPE="int" VALUE="20" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
<STATE NAME="_TOKEN_TEXT" TYPE="string" VALUE="DAT_80067814" />
</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="3723604406647099774" />
<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="SLUS_002.68" />
<STATE NAME="LOCATION_0" TYPE="string" VALUE="/D:/Ghidra/Crusader/" />
<STATE NAME="NUM_PROGRAMS" TYPE="int" VALUE="1" />
<STATE NAME="PATHNAME_0" TYPE="string" VALUE="/psx/remorse/SLUS_002.68" />
<STATE NAME="PROJECT_NAME_0" TYPE="string" VALUE="Crusader" />
<STATE NAME="VERSION_0" TYPE="int" VALUE="-1" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80067814" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80067814" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.XRefFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="6" />
<ARRAY NAME="_COMP_PATH" TYPE="int" />
<STATE NAME="_REF_ADDRESS" TYPE="string" VALUE="800255b8" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
</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="3723604490583511408" />
</PLUGIN>
<PLUGIN NAME="CodeBrowserPlugin">
<STATE NAME="INDEX" TYPE="int" VALUE="429346" />
<STATE NAME="NAV_ID" TYPE="long" VALUE="3723604488515719525" />
<STATE NAME="Num Disconnected" TYPE="int" VALUE="0" />
<STATE NAME="Y_OFFSET" TYPE="int" VALUE="-225" />
<STATE NAME="_ADDRESS" TYPE="string" VALUE="80067814" />
<STATE NAME="_BYTE_ADDR" TYPE="string" VALUE="80067814" />
<STATE NAME="_CHAR_OFFSET" TYPE="int" VALUE="0" />
<STATE NAME="_CLASSNAME" TYPE="string" VALUE="ghidra.program.util.XRefFieldLocation" />
<STATE NAME="_COLUMN" TYPE="int" VALUE="6" />
<ARRAY NAME="_COMP_PATH" TYPE="int" />
<STATE NAME="_REF_ADDRESS" TYPE="string" VALUE="800255b8" />
<STATE NAME="_ROW" TYPE="int" VALUE="0" />
</PLUGIN>
</DATA_STATE>
</RUNNING_TOOL>
</WORKSPACE>
</TOOL_MANAGER>
</PROJECT>

View file

@ -4,7 +4,7 @@ This file is an index. Detailed notes have been split into the `docs/` folder by
Active live analysis target is now `CRUSADER.EXE`. Existing `CRUSADER-RAW.EXE` notes remain in scope as cross-reference evidence and should be cited alongside live NE addresses when they support a rename, variable role, or behavior claim.
Recent verified PSX map-viewer batch: [docs/psx/psx.md](docs/psx/psx.md) and [docs/psx/map-viewer-plan.md](docs/psx/map-viewer-plan.md) now record the latest executable-backed correction to the PSX renderer model. Current best read is that the cache builder still exports executable-named section-0 visible families (`section0_dispatch_roots`, `section0_constructor_placements`), runtime/state layers for `DAT_800758d8`, `DAT_800758d0`, `DAT_800758cc`, `DAT_800758d4`, and one offline `FUN_8003b00c` decode candidate for `DAT_8006b5d8 -> DAT_8006769c`, and packs shared PSX art into `1925` shared atlases instead of the earlier one-atlas-per-shape spread. But the same batch also shows the current fallback art path is wrong at the root: early `section0_dispatch_roots` types such as `0x0042` and `0x0049` currently bind to portrait/talk-animation bundles, so the real remaining blocker is the multi-stage per-type template/state/variant selection path, not just a missing one-step bundle lookup.
Recent verified PSX map-viewer batch: [docs/psx/psx.md](docs/psx/psx.md), [docs/psx/map-rendering.md](docs/psx/map-rendering.md), [docs/psx/map-viewer-plan.md](docs/psx/map-viewer-plan.md), and the new [docs/psx/art-binding-recovery.md](docs/psx/art-binding-recovery.md) now record both the latest executable-backed renderer findings and the first measured recovery pass against placeholder-heavy PSX scenes. Current best read is that the cache builder still exports executable-named section-0 visible families (`section0_dispatch_roots`, `section0_constructor_placements`), runtime/state layers for `DAT_800758d8`, `DAT_800758d0`, `DAT_800758cc`, `DAT_800758d4`, and one offline `FUN_8003b00c` decode candidate for `DAT_8006b5d8 -> DAT_8006769c`, but it now also treats large zero-block `DAT_800758d8` constructor-placement bands as inherited-art candidates before falling back to placeholders. That donor-based recovery path moved the built cache from `58,262` fallback items / `1,714` bundle-mapped items down to `25,038` fallback items / `34,938` bundle-mapped items, making maps such as `0`, `9`, and `43` mostly real-art while leaving `map 104` as the clearest remaining outlier. The newest pass does not change the earlier `DAT_800758d4` conclusion: `psx_object_advance_state_script` still sign-extends those three bytes into `obj+0x30/+0x34/+0x38` for overlap/contact-style consumers rather than the draw path, and the unresolved blocker still sits later in the live state-to-resource/frame bridge for the remaining constructor-placement families.
Recent verified combat-data batch: [docs/combat-dat.md](docs/combat-dat.md) now documents the shipped `COMBAT.DAT` archive end to end. Current best read is that all local Remorse/Regret variants ship the same `14`-record combat-tactic archive, each record contains a `16`-byte name plus four block offsets and bytecode, and the tactic VM is now grounded both in the live `CRUSADER.EXE` helpers (`Attack_SetupForTacticNo`, `Attack_SetupForBlockNo`, `NPC_Get/SetNPCTacticNo`) and in ScummVM's readable Crusader attack-process implementation. The new note also promotes the per-tactic human-readable catalog, including the midpoint-pressure, marker-shuttle, step-out-shoot, and stationary-chaos families.
@ -38,6 +38,8 @@ Recent map/editor visibility batch: [docs/editor-object-visibility.md](docs/edit
Recent map-viewer trigger batch: [docs/map_renderer/trigger-usecode-links.md](docs/map_renderer/trigger-usecode-links.md) now records the evidence-backed editor/controller-object -> USECODE mapping used by the viewer when opening readable pseudocode from pinned tooltips. Current best read is that the stable viewer targets are `BOX_EW`, `PANELNS`, `CARD_NS`, and `SPANEL` -> `use`; `FASTSKIL` -> `enterFastArea`; `SKILLBOX`, `EVENT`, `ALARMHAT`, and `ALRMTRIG` -> `equip`; `TRIGPAD` and `NPC_ONLY` -> `gotHit`; and the `0x04B1` cmd helper itself -> `TRIGGER.slot_20`, the shared high-slot fan-out lane that nearby controller families keep spawning.
Recent usecode-event vocabulary note: [docs/usecode-event-slots-combine-cast-fast-area.md](docs/usecode-event-slots-combine-cast-fast-area.md) now records a focused evidence pass on the suspicious inherited `combine` / `cast` slot names and the current best meaning of `fast area`. Current best read is that shipped Crusader usecode exposes no recovered `slot_0C` handler bodies at all, while `slot_11 cast` is real but acts as a scripted activation/dispatch lane rather than a classic spell system. The same note also tightens `enterFastArea` / `leaveFastArea` to `authored proximity/activation region` callbacks instead of the older speed-zone-style shorthand.
Recent startup/map-selection batch: [docs/first-mission-map-selection.md](docs/first-mission-map-selection.md) now records the live proof that fresh-game map choice is code-selected rather than read from `CRUSADER.CFG` or another external mission-mapping file. For `CRUSADER.EXE`, the normal fresh-game path still hardcodes map `1`, egg `0x1e` inside `Game_Start`. For `REGRET.EXE`, the same values are hardcoded twice: once in an early `Game_Start` selector and again in the later `FUN_1030_032d` mission-start path that actually controls a real new game. The same note also captures the separate debug `-warp mission` path: it indexes a small executable-embedded mission-to-map word table at `1478:0488` (`0,1,3,5,...,0x1d,0x28`) and then applies `-mapoff`, while the actual map contents remain external in `FIXED.DAT`.
New REGRET startup-flow batch: [docs/regret-game-start.md](docs/regret-game-start.md) now documents the live `REGRET.EXE` `Game_Start` neighborhood more thoroughly. That note promotes `HandleCommandlineArgs`, `Game_RunNewGameFlow`, `Game_DrawCenteredStartupSplash`, `Game_EnterFrontendMenuViewport`, and `Game_RestoreGameplayViewport` in the live database, records the startup-state globals used by the new-game and `-warp` lanes, and explains the current best reason map `1` is set twice in No Regret: two separate live startup entry paths still own their own teleporter literals instead of sharing one final startup-map source.
@ -114,10 +116,13 @@ Latest F7 overlay follow-up: new note [docs/f7-overlays.md](docs/f7-overlays.md)
| [docs/remorse-rebuild-abi-notes.md](docs/remorse-rebuild-abi-notes.md) | Working note for rebuild constraints: segmented-memory model, far-call provenance, runtime/toolchain evidence, ABI guardrails, and the split between original-style executable reconstruction and a behaviorally equivalent port |
| [docs/command-line-parameters.md](docs/command-line-parameters.md) | Consolidated startup/debug argument reference for the retail Crusader executables: live retail `-u` usecode override, the current `-setver` caution, `-debug`, `-asylum`, `-warp`, `-skill`, `-mapoff`, `-egg`, `-demo`, the `-laurie` cross-reference, and the evidence-backed direct-coordinate warp syntax/limits |
| [docs/psx/psx.md](docs/psx/psx.md) | PlayStation `SLUS_002.68` and disc-resource note: boot/load layout, `LSET`/menu WDL structure, executable-backed map inventory, passcode alphabet/display path, recovered PSX ammo/item/weapon tables, and current unresolved enemy/password-compare gaps |
| [docs/psx/map-rendering.md](docs/psx/map-rendering.md) | Detailed PlayStation map-rendering architecture note: `LSET*.WDL` storage model, constructor record layouts, runtime banks, state/variant/art bridge, stage-1 versus stage-2 render lanes, visible-list sorting, final primitive submission, and the current best recipe for reconstructing PSX maps in the viewer |
| [docs/psx/art-binding-recovery.md](docs/psx/art-binding-recovery.md) | Focused PSX viewer recovery note: extraction-versus-render diagnosis, zero-block `DAT_800758d8` constructor-family donor heuristics, measured fallback reduction, representative map stats, and the remaining outlier families/maps |
| [docs/psx/prealpha.md](docs/psx/prealpha.md) | PlayStation pre-pre alpha `/psx/prealpha/SLUS_002.68` comparison note: reduced disc inventory, retained retail-style `LSET` loader, surviving No Remorse branding, stale `TALK1.XA` and `LoadExec` leftovers, and the current read that this build is closer to an unfinished No Remorse PSX branch than to a visibly rebranded sequel executable |
| [docs/sprite-node-class-layout.md](docs/sprite-node-class-layout.md) | Focused working note for the `SpriteNode` family: current core layout, destructor and event-dispatch evidence, candidate virtual slots, and a conservative Ghidra modeling plan |
| [docs/usecode-startup-override.md](docs/usecode-startup-override.md) | Focused retail `-u` deep dive: startup call order, why the override looks like full live-root replacement rather than addition, which event/process/interpreter consumers use that root, and what that implies for future custom usecode experiments |
| [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 |
| [docs/usecode-event-slots-combine-cast-fast-area.md](docs/usecode-event-slots-combine-cast-fast-area.md) | Focused note on whether inherited event labels `combine` and `cast` are actually live in shipped Crusader usecode, plus the current evidence-backed explanation of `enterFastArea` / `leaveFastArea` as proximity-trigger callbacks |
| [docs/usecode-pentagram-ghidra-path.md](docs/usecode-pentagram-ghidra-path.md) | Pentagram-derived Crusader USECODE parser plan, proof-of-concept workflow, canonical IR v1 goals, and the Ghidra-side annotation import path |
| [docs/usecode-tooling-comparison.md](docs/usecode-tooling-comparison.md) | Comparison of Pentagram's converter/disassembler, the local `crusader-disasm` corpus/scripts, and the current workspace parser/pseudocode exporter, with emphasis on assumptions, strengths, and repo-specific differences |
| [docs/usecode-tool-improvement-plan.md](docs/usecode-tool-improvement-plan.md) | Concrete next-step plan for the local USECODE parser/decompiler, distilled from the Pentagram and `crusader-disasm` comparison into prioritized parser, loop-decoding, intrinsic, trailer, corpus, and runtime-bridge upgrades |

View file

@ -0,0 +1,84 @@
# PSX Art-Binding Recovery
## Scope
- Active target: retail PlayStation `SLUS_002.68` feeding the renderer-local cache pipeline in `Crusader-Map-Viewer/map_renderer`.
- Goal of this pass: stop treating the current unreadable PSX output as a renderer-only problem and measure whether placeholders are mainly caused by extraction-time art binding failures.
- This note records the first pragmatic recovery pass that replaces large placeholder bands with real bundle-backed art while keeping the result auditable in exported scene metadata.
## Root Cause Summary
- The current unreadable output was primarily an extraction-side problem, not a front-end draw bug.
- Before this pass, the built `.cache/scene-cache/psx-remorse` set carried `58,262` fallback placeholders versus only `1,714` bundle-mapped items.
- The fallback concentration was overwhelmingly in `section0_constructor_placements` (`94.5%` of fallback items), not in the smaller `section0_dispatch_roots` family.
- The hottest unresolved types were `0x0042`, `0x0041`, `0x0047`, `0x0045`, `0x003f`, `0x004b`, `0x0046`, `0x0044`, `0x0048`, and `0x004a`.
- Those types already appeared in the exported `DAT_800758d8` art-template layer in many maps, but almost all of their rows were `blockSize = 0` and therefore had no direct payload dwords to match against bundle offsets.
- The neighboring state banks were still populated: `DAT_800758cc` carried valid script tables for the same types, while `DAT_800758d0` stayed empty for this family and `DAT_800758d4` remained on the runtime-bounds side.
- That combination strongly suggests inherited or aliased presentation for these constructor-placement families rather than a literal absence of art.
## Implemented Recovery Rule
The cache builder now resolves PSX art in three stages instead of dropping directly from "no direct `DAT_800758d8` payload" to a placeholder:
1. Use the direct non-zero `DAT_800758d8` row when a type has a real local template payload and bundle match.
2. If the type lives in the unresolved zero-block constructor-placement family, look for a same-map donor type whose `DAT_800758cc` script blob is identical and whose `DAT_800758d8` row resolves to a real bundle.
3. If no exact script-signature donor exists, fall back to the nearest resolved same-map donor inside the current constructor-placement family band (`0x003e..0x0064`).
The exporter keeps this explicit instead of pretending the rule is fully solved:
- `mappingSource` now records whether the art came from a direct template, a `cc-signature-donor:xxxx`, or a `generic-family-donor:xxxx` path.
- `templateTypeId` and `donorTypeId` are preserved in exported `mapSource` rows.
- The unresolved type still uses its own `DAT_800758cc` selector-to-frame map; only the bundle source is borrowed.
## Measured Effect
Two measured rebuilds mattered in this pass.
### First donor pass
- Added donor reuse for the original generic family band.
- Totals moved from `58,262` fallback / `1,714` bundle-mapped to `37,054` fallback / `22,922` bundle-mapped.
Representative maps:
- `map 0`: `1078 / 111` -> `340 / 849`
- `map 9`: `664 / 111` -> `197 / 578`
- `map 43`: `707 / 97` -> `196 / 608`
- `map 64`: `1194 / 161` -> `736 / 619`
- `map 104`: `909 / 93` -> `894 / 108`
### Extended donor band
- Extended the same heuristic through the next zero-block constructor-placement band up to `0x0064`.
- Final measured totals after rebuild: `25,038` fallback / `34,938` bundle-mapped.
Representative maps after the extended pass:
- `map 0`: `66` fallback / `1123` bundle-mapped
- `map 9`: `42` fallback / `733` bundle-mapped
- `map 43`: `9` fallback / `795` bundle-mapped
- `map 64`: `160` fallback / `1195` bundle-mapped
- `map 104`: `866` fallback / `136` bundle-mapped
So the first practical conclusion is straightforward: the exporter was the main blocker for most maps, and a constrained donor heuristic can replace a large fraction of placeholders with real graphics without touching the viewer runtime.
## Remaining Unresolved Mass
- The cache is still not fully closed. `25,038` fallback items remain across `62` maps.
- The remaining fallback distribution is still concentrated in `section0_constructor_placements`.
- Current top fallback-heavy types after the donor pass are `0x0042`, `0x005a`, `0x005b`, `0x005c`, `0x0047`, `0x0059`, `0x005d`, `0x005e`, `0x0062`, and `0x0063`.
- `0x0042` remains the single largest unresolved type even after the heuristic pass.
- `map 104` is the most obvious current outlier: it still sits at `866` fallback items versus only `136` bundle-mapped items, so the next recovery pass should treat it as the best stress case rather than relying only on the now-mostly-readable early maps.
## Practical Interpretation
- The donor heuristic is good enough to prove that many placeholders were caused by missing extraction-time inheritance logic.
- It is not strong enough to count as the final executable-backed rule for the unresolved families.
- The remaining gap still sits where the earlier Ghidra work already pointed: somewhere between the constructor-side art/resource creation lane and the live post-spawn state/resource/frame reselection path.
## Next Steps
1. Trace the remaining high-volume band `0x0055..0x0063` in Ghidra with the same question used for `0x0042`: why does `DAT_800758d8` stay zero-sized while visible art still exists at runtime?
2. Use `map 104` as the primary regression target and dump its remaining fallback type/state/lane distribution before doing any broader heuristic expansion.
3. Compare unresolved zero-block types against nearby resolved donor types at the constructor/resource level, not only at the script-signature level, so borrowed bundles can be replaced with an executable-backed alias rule.
4. Keep the `DAT_800758d4` work on the bounds side unless a family-specific caller proves otherwise; this pass did not reopen that conclusion.

622
docs/psx/map-rendering.md Normal file
View file

@ -0,0 +1,622 @@
# PSX Map Rendering Architecture
## Scope
This document explains the current evidence-backed model for how the PlayStation build stores map data, how the executable turns that data into live objects and visible primitives, and how a viewer can reassemble the same data into a coherent scene.
It is not a replacement for the running note in `docs/psx/psx.md`. Instead, it consolidates the map-rendering findings from that note and the related Ghidra work into one detailed technical reference.
Primary target:
- retail PlayStation `SLUS_002.68`
Primary use:
- guide viewer/exporter work in `Crusader-Map-Viewer`
- preserve the executable-backed reasoning behind the current PSX scene format
- make the remaining unresolved gap explicit instead of leaving it spread across many small notes
## Executive Summary
The current strongest model is:
1. A PSX level is not stored as one flat placement table.
2. `LSET*.WDL` loads a multi-section bundle with at least three map-relevant classes of data:
- authored root/dispatch records
- authored constructor-placement records
- per-type runtime banks for art, state scripts, variants, and simple-record payloads
3. Constructors turn those authored rows into live objects with fixed-point world coordinates, a cached drawable resource pointer, a state-script bank pointer, a variant bank pointer, and a preserved pointer back to the original authored record.
4. The runtime does not draw directly from the authored selector byte. It advances and sometimes reseats the live state script after spawn, then uses the current script word to drive frame choice and variant lookup.
5. There are two separate world-facing render lanes:
- stage 1: main visible-object list
- stage 2: queued special-visible list
6. The viewer can already reconstruct placement, projection, most resource loading, and much of the draw path from executable evidence.
7. The main remaining blocker is the last live state-to-art rule for unresolved families such as `0x0042` and `0x0049`. The map is still unreadable in practical terms because those families still fall back to placeholders.
So the problem is no longer "how do PSX coordinates work" or "where do draw rectangles come from". The problem is now much narrower: the viewer still does not fully reproduce the executable's final runtime art-state resolution.
## Evidence Base
This model is grounded by a combination of static and runtime-adjacent evidence:
- Ghidra decompilation of retail `SLUS_002.68`
- the renderer-local `.cache` scene/export pipeline
- WDL section parsing and bank extraction work
- live comparison between exported scene fields and executable object layouts
- repeated correction of earlier bad hypotheses, especially the disproven "small top-level record stream == whole map" assumption
Most important named functions and data anchors:
- `wdl_resource_bundle_load_by_index`
- `psx_object_create_simple_record`
- `psx_object_create_compound_record`
- `psx_object_select_state_script`
- `psx_object_advance_state_script`
- `psx_object_lookup_variant_entry`
- `psx_object_integrate_motion_and_route_visible`
- `psx_project_object_main_visible`
- `psx_project_object_special_visible_queue`
- `psx_draw_world_visible_passes`
- `psx_draw_main_visible_object`
- `psx_draw_special_visible_queue`
- `psx_level_root_record_stream` (`DAT_800678f4`)
- `psx_section0_dispatch_root_records` (`DAT_80067720`)
- `psx_section0_constructor_placement_records` (`DAT_800678f0`)
- `psx_type_art_template_bank` (`DAT_800758d8`)
- `psx_type_simple_component_bank` (`DAT_800758d0`)
- `psx_type_state_script_bank` (`DAT_800758cc`)
- `psx_type_companion_extents_bank` (`DAT_800758d4`)
- `psx_level_detached_blob` (`DAT_8006767c`)
- `psx_level_decompressed_state_buffer` (`DAT_8006769c`)
- `psx_level_runtime_header_block` (`DAT_80067794`)
## How Map Data Is Stored
### 1. `LSET*.WDL` is a multi-section bundle
The executable-backed loader model is no longer speculative:
- `wdl_resource_bundle_load_by_index` opens `SPEC_A.WDL` and the selected `LSET*.WDL`
- it reads a `0x38` header whose first nine dwords act as section sizes
- it then lays out multiple runtime pointers rather than one monolithic map blob
Current map-relevant runtime destinations:
- `psx_level_root_record_stream` (`DAT_800678f4`): top-level root record stream
- `psx_section0_dispatch_root_records` (`DAT_80067720`): secondary `0x18`-stride authored record family
- `psx_section0_constructor_placement_records` (`DAT_800678f0`): `0x0c`-stride constructor-placement family
- `psx_type_art_template_bank` (`DAT_800758d8`): per-type art/template descriptor bank
- `psx_type_simple_component_bank` (`DAT_800758d0`): per-type simple-record payload bank
- `psx_type_state_script_bank` (`DAT_800758cc`): per-type state-script bank
- `psx_type_companion_extents_bank` (`DAT_800758d4`): per-type variant/companion bank
- `DAT_800675f8`: per-type flags table
- `psx_level_detached_blob` (`DAT_8006767c`): additional detached level blob
- `DAT_8006b5d8 -> psx_level_decompressed_state_buffer` (`DAT_8006769c`): optional decompressed `0x3e00` runtime/state substrate
- `psx_level_runtime_header_block` (`DAT_80067794`): separate `0x50` level runtime-header block
### 2. The map is split across authored families, not one row type
The viewer's old `region00/region01` labels were a useful stepping stone, but the current executable-backed names are better:
- `section0_dispatch_roots`: closest to the `DAT_80067720` / root-dispatch family
- `section0_constructor_placements`: closest to the `DAT_800678f0` constructor-input family
These runtime anchors are now named the same way in the live Ghidra database:
- `section0_dispatch_roots` aligns with `psx_section0_dispatch_root_records`
- `section0_constructor_placements` aligns with `psx_section0_constructor_placement_records`
These families do different jobs.
`section0_dispatch_roots`:
- generic runtime-object descriptors
- fed into per-type dispatch handlers
- not yet directly final render primitives
- include families that still need downstream runtime state/variant logic before visible art is known
`section0_constructor_placements`:
- tighter constructor-facing rows
- much closer to direct object spawn inputs
- already usable for a large part of the viewer export
### 3. The late template bank matters
The per-type art bank in `DAT_800758d8` is not taken from the earlier small-section heuristic.
Current best read:
- the useful late `DAT_800758d8` candidate sits in a late large section
- it decodes only when that section is treated as a bank with an embedded `+0x38` parse start
- on retail map `9`, that correction lifts resolved bundle-mapped items from `0` to `111`
This is one of the strongest pieces of evidence that the viewer must respect executable loader structure rather than broad file-wide scans or first-match heuristics.
## Constructor Record Layouts
Two constructor families are now strong enough to describe directly.
### Compound-record constructor
`psx_object_create_compound_record` reads:
- `type` from `record + 0x00`
- `x` from `u16` at `+0x02`
- `y` from `u16` at `+0x04`
- `z` from byte `+0x06`
- initial state selector from byte `+0x08`
- flags from `+0x0a`
### Simple-record constructor
`psx_object_create_simple_record` reads:
- `type` from `record + 0x04`
- `x` from `u16` at `+0x08`
- `y` from `u16` at `+0x0a`
- `z` from byte `+0x0c`
- initial state selector from byte `+0x0e`
### Common constructor outputs
Both constructors:
- write authored coordinates into object fields `+0x3c/+0x40/+0x44` as `16.16` fixed-point
- preserve the original authored source-record pointer at `obj + 0xa0`
- resolve the per-type art bank and seed a drawable resource pointer at `obj + 0x10`
- store the per-type variant bank at `obj + 0x84`
- store the per-type state-script bank at `obj + 0x88`
- call `psx_object_select_state_script` to seed the initial live state
That preserved source-record pointer at `obj + 0xa0` is especially important because it closes the palette-override provenance: later draw code really is reading authored bytes directly.
## Runtime Banks And Object Fields
The current best object-centric map/render model revolves around a small cluster of object fields.
### Per-type banks
- `psx_type_art_template_bank` (`DAT_800758d8`): art/template descriptor bank
- `psx_type_simple_component_bank` (`DAT_800758d0`): simple-record local payload bank
- `psx_type_state_script_bank` (`DAT_800758cc`): state-script bank
- `psx_type_companion_extents_bank` (`DAT_800758d4`): variant/companion bank
### Important object fields
- `obj + 0x10`: current drawable resource pointer
- `obj + 0x20..0x2e`: projected on-screen rectangle
- `obj + 0x3c/+0x40/+0x44`: fixed-point world `x/y/z`
- `obj + 0x54/+0x58/+0x5c`: next/target world position cluster used by motion/integration helpers
- `obj + 0x60/+0x64/+0x68`: motion vector used by heading/state reselection helpers
- `obj + 0x78/+0x7c`: intermediate projected screen anchor in fixed-point
- `obj + 0x84`: current variant bank pointer
- `obj + 0x88`: current state-script table pointer
- `obj + 0x8c/+0x90`: active script base and current script cursor
- `obj + 0x94`: current script word, which is already the live frame/state index used by later draw helpers
- `obj + 0x9e`: original authored selector stored by `psx_object_select_state_script`
- `obj + 0xa0`: original authored source-record pointer
The crucial distinction is:
- `obj + 0x9e` is the authored input selector
- `obj + 0x94` is the current live script word after advancement/reselection
The executable's later art-facing logic follows `obj + 0x94`, not `obj + 0x9e`.
## State Scripts, Variants, And Why The Map Is Still Unreadable
### 1. Initial state selection is not final state selection
`psx_object_select_state_script`:
- stores the authored selector at `obj + 0x9e`
- chooses an initial script base from `DAT_800758cc`
- seeds `obj + 0x8c/+0x90`
But that is only the starting point.
### 2. The runtime advances and sometimes reseats the live script
`psx_object_advance_state_script`:
- interprets sentinel-driven script records
- updates `obj + 0x94` from the current script word
- reruns `psx_object_lookup_variant_entry`
Verified sentinel/control behavior now includes:
- `0xfffe`: non-visible audio/effect helper dispatch
- `0xfffd`: direct in-family jump
- `0xfffc`: immediate switch to subsidiary script-table entry
- `0xfffb`: scan-forward variant that consumes the next in-band `0xfffd` selector before switching
### 3. Variant lookup is indexed by live state, not by raw placement selector
`psx_object_lookup_variant_entry`:
- reads `DAT_800758d4`
- indexes it by `obj + 0x94`
- does not index directly by `obj + 0x9e`
That is the key split between authored placement metadata and runtime-visible state.
### 4. The current `DAT_800758d4` evidence points to companion extents, not final art
The newly traced consumer path is narrower and more concrete than the earlier placeholder-art theory.
`psx_object_advance_state_script`:
- reruns `psx_object_lookup_variant_entry`
- sign-extends the returned three bytes into `obj + 0x30/+0x34/+0x38`
- does not update `obj + 0x10`
- does not replace the live frame index stored at `obj + 0x94`
Downstream consumers of `obj + 0x30/+0x34/+0x38` are now verified in the interaction lane:
- `psx_object_test_overlap_3d` uses those fields as the object's overlap extents against `obj + 0x54/+0x58/+0x5c`
- `psx_object_update_contact_block_flags` uses the same extents while setting directional block/contact bits
- `psx_object_reselect_state_from_target_vector` and `psx_type4_reselect_motion_state` use target-object `+0x30/+0x34/+0x38` as target bounds while reseating heading-based state
By contrast, the visible projectors and draw helpers still take visible art only from:
- the drawable resource pointer at `obj + 0x10`
- the live frame/state word at `obj + 0x94`
So `DAT_800758d4` is currently better described as a per-state companion-extents bank than as the last missing direct art table.
### 5. Interaction and heading state can rewrite the live script
The runtime does not only advance scripts linearly.
Verified reselection path:
- `psx_object_reselect_state_from_target_vector`
- `psx_object_quantize_motion_heading16`
- `psx_quantize_vector_heading16`
Verified interaction/reselection cluster:
- `psx_type4_update_delayed_interaction`
- `psx_type4_reselect_motion_state`
- `psx_object_update_nearby_interactions`
- `psx_object_test_overlap_3d`
- `psx_object_update_contact_block_flags`
- `psx_object_register_contact_pair`
These helpers prove that post-spawn interaction and motion state can reseat the active script from runtime heading, not only from the authored row.
That is why exported `0x0042` selectors `3` and `4` do not contradict an earlier three-script file read: some of those live selectors are runtime outcomes.
### 6. This is the current blocker
The map is still unreadable because the viewer still does not fully reproduce that last runtime bridge:
- authored row
- initial state bank entry
- post-spawn script advancement/reselection
- live companion-extents lookup
- final visible resource/frame choice
Projection, placement decoding, list routing, and primitive submission are no longer the main unknowns.
## Per-Frame World And Render Pipeline
### 1. Outer lifecycle
- `psx_level_session_loop`: outer level-session loop
- `wdl_resource_bundle_load_by_index`: level load and runtime-bank setup
- `psx_world_frame_tick`: normal per-frame world loop
- `psx_draw_world_visible_passes`: top-level draw submission
### 2. Authored record dispatch before live-object draw
The executable still operates on authored families each frame before or alongside live objects:
- `psx_dispatch_section0_dispatch_roots`: dispatches the `DAT_80067720` family plus nearby fixed-size entries
- `psx_dispatch_section0_constructor_placements`: dispatches the `DAT_800678f0` constructor-placement family
These are the closest executable matches for the viewer's exported authored record families.
### 3. Live-object update lane
- `psx_run_live_object_type_updates`: per-type live-object update callback pass
- `psx_run_live_object_behavior_callbacks`: later per-object behavior callback pass
- `psx_object_integrate_motion_and_route_visible`: integrates motion, updates visibility flags, advances script state, and routes the object to the appropriate render lane
### 4. Stage 1 versus stage 2 is a real runtime split
Stage 1:
- projector: `psx_project_object_main_visible`
- draw helper: `psx_draw_main_visible_object`
- object membership stored in `psx_main_visible_list` (`DAT_8006ad5c`)
Stage 2:
- projector: `psx_project_object_special_visible_queue`
- draw helper: `psx_draw_special_visible_queue`
- queue stored in `psx_special_visible_queue` (`DAT_80078b70`) with count `psx_special_visible_queue_count` (`DAT_80067472`)
- reset each frame by `psx_reset_special_visible_queue`
This is not a HUD-only split. Stage 2 is a real world-facing lane.
### 5. Small wrapper helpers prove the split is intentional
Recovered wrappers:
- `psx_spawn_compound_record_advance_state_once`
- `psx_spawn_simple_record_set_active_flag`
- `psx_object_refresh_main_visible_and_cleanup`
- `psx_object_advance_state_and_queue_special_visible`
These wrappers show that the executable contains dedicated short-form helpers for:
- spawn then immediately advance state
- spawn then mark active
- refresh and project into stage 1
- advance state and immediately queue into stage 2
That is strong evidence that route selection is a first-class runtime decision, not an incidental byproduct of the draw code.
## Projection Model
The PSX executable-backed coordinate model is now stable enough for viewer use.
### World coordinates
Authored world values are stored in object fields:
- `obj + 0x3c`: `x` as `16.16`
- `obj + 0x40`: `y` as `16.16`
- `obj + 0x44`: `z` as `16.16`
### Projection
`psx_project_object_main_visible` and `psx_project_object_special_visible_queue` use:
- `screen_anchor_x = y - x`
- `screen_anchor_y = 2*z - (x + y)/2`
They write the fixed-point intermediate anchor to:
- `obj + 0x78`
- `obj + 0x7c`
Then they subtract camera origin and per-frame origin metrics to produce the final on-screen rectangle at:
- `obj + 0x20..0x2e`
This matters for the viewer because PSX sprites should be positioned from those authored screen rectangles, not from a DOS-style reconstructed wireframe.
## Render Submission Path
### 1. Top-level draw pass
`psx_draw_world_visible_passes`:
1. asks `psx_main_visible_list_get_sorted_slice` for the current sorted stage-1 slice
2. iterates it through `psx_draw_main_visible_object`
3. then draws stage 2 through `psx_draw_special_visible_queue`
4. then executes the HUD/overlay pass
### 2. Stage-1 visible-list management
Named helpers:
- `psx_main_visible_list_add`
- `psx_main_visible_list_remove`
- `psx_main_visible_list_rebucket_object`
- `psx_main_visible_list_refresh_from_live_chain`
- `psx_main_visible_list_sort_range`
- `psx_main_visible_list_get_sorted_slice`
The sorter is important because it shows that draw order is not just screen-y or type-only ordering. It uses dependency and tie-break logic tied to the `DAT_800915xx` ordering data plus depth/position rules.
### 3. Frame metrics and final resource-specific submitters
Frame metric accessors:
- `psx_resource_frame_origin_x`
- `psx_resource_frame_origin_y`
- `psx_resource_frame_width`
- `psx_resource_frame_height`
Resource-specific submitters:
- `psx_sprite_resource_submit_frame`
- `psx_image_table_submit_frame`
The critical point is that both paths take the live frame index from `obj + 0x94`.
### 4. Resource creation path
`psx_create_image_resource_from_descriptor`:
- type-4 descriptors bind a single image resource through `image_resource_bind_vram_slot`
- type-5 descriptors allocate and upload multi-frame bundles through `image_bundle_load_to_vram`
This is why the constructors can seed `obj + 0x10` early and then let later code only vary frame index and state.
## Palette Selection
Palette handling is partly closed and partly still open.
Closed:
- `psx_draw_main_visible_object` reads palette overrides from the original source-record pointer at `obj + 0xa0`
- for types `0x003e..0x00ab`, it uses the high byte of source word `+0x06`
- for types `>= 0x00ac`, it uses the high byte of source word `+0x0c`
Open:
- the full rule for all resource classes and all placement families is not yet completely closed
- the viewer still needs broader CLUT-selection recovery so common cases render with runtime-correct colors without heuristics
So palette work is real, but it is no longer confused with the deeper unresolved art-state bridge.
## How To Reassemble A PSX Map In A Viewer
This is the current best practical recipe.
### Step 1: parse `LSET*.WDL` as a multi-section bundle
Do not treat the first small record stream as the whole map.
Required outputs from the loader stage:
- root/dispatch family payloads
- constructor-placement family payloads
- per-type banks for art/state/variant/simple payloads
- detached extra blob(s)
- optional decoded `DAT_8006769c` substrate/state buffer
- runtime-header block metadata
### Step 2: preserve authored rows as authored rows
Keep the exported record families as reversible scene metadata.
Useful naming already in use:
- `section0_dispatch_roots`
- `section0_constructor_placements`
Do not flatten them into one inferred placement family too early.
### Step 3: reconstruct constructor inputs faithfully
For each candidate placement row, reconstruct at least:
- `type`
- authored selector byte
- authored `x/y/z`
- original source bytes needed for later palette and state analysis
Store the original row pointer or offset in exported metadata where practical.
### Step 4: resolve per-type runtime banks
For each type, export or reconstruct:
- art/template descriptor
- state-script bank
- companion-extents/variant bank
- simple-record payload bank when present
This is already partly supported by the current cache/export path through `stateLayers` and related scene metadata.
### Step 5: seed a viewer-side live object model
Minimum object fields for a faithful viewer simulation:
- resource pointer or resource descriptor reference
- world `x/y/z`
- current script word
- original selector
- current companion extents from `obj + 0x30/+0x34/+0x38`
- type flags
- source-record pointer or reconstructed source bytes
- visible lane classification if known
### Step 6: project with the executable's isometric transform
Use:
- `screen_x = y - x`
- `screen_y = 2*z - (x + y)/2`
Then apply per-frame origin/size offsets and camera subtraction, just as the executable projectors do.
### Step 7: separate stage 1 and stage 2
Do not assume one visible-object list.
Viewer-side representation should support:
- stage-1 main visible list behavior
- stage-2 queued special-visible path
Even if the first viewer implementation collapses them visually, the underlying scene model should keep them distinct.
### Step 8: honor draw-order rules explicitly
The main visible list is sorted, not appended blindly.
A viewer that only sorts by simple `y` or `z` will eventually diverge. The current best approximation should be based on the executable-backed visible-list sort behavior, and exported metadata should preserve enough object/dependency information to improve that later.
### Step 9: use authored palette bytes where proven
The viewer/exporter should keep both:
- default resource palette assumptions
- authored placement override bytes from the preserved source record
That keeps palette work reversible and lets the viewer use stronger executable-backed overrides without hard-wiring them into flattened output.
### Step 10: keep the unresolved state-to-art rule explicit
For unresolved families, do not pretend a flat `type -> frame` table is solved.
Current best practice:
- use verified executable-backed frame maps only where they are genuinely closed
- mark unresolved families as provisional
- preserve the exported state metadata and the `DAT_800758d4` companion-extents metadata so later passes can replace placeholders without redoing the whole loader
- do not treat `DAT_800758d4` as a direct fallback art selector unless a family-specific consumer proves otherwise
## What A Viewer Can Already Do Reliably
Already defensible from evidence:
- load PSX maps from the correct `LSET*.WDL` families
- separate authored record families instead of flattening them
- reconstruct multi-level `z` values for the constructor-placement lane
- use executable-backed projection math
- separate stage-1 and stage-2 world lanes in the scene model
- resolve a first subset of per-type real art from the corrected late `DAT_800758d8` bank
- preserve state banks, companion-extents banks, and decoded runtime blobs as research metadata
## What Still Prevents A Fully Readable Map
The remaining blocker is now narrow and concrete:
- the exact last rule that turns live script/variant state into final visible art for unresolved families
More specifically:
- the constructors, routing, projection, draw passes, and resource submission path are now substantially understood
- `DAT_800758d4` now looks like per-state companion extents used by overlap/contact logic, not the missing final art table
- but for unresolved families the viewer still does not fully reproduce how `DAT_800758cc`, runtime reselection, and family-specific drawable resource/frame presentation interact after the live script changes
That is why the current viewer output is still unreadable as a practical map even though so much of the storage and render machinery is now mapped.
## Recommended Viewer Strategy From Here
Short-term:
- keep the current executable-backed export model
- preserve all state metadata in exported scene JSON
- export the `DAT_800758d4`-backed signed companion extents as explicit runtime-bounds metadata instead of treating them as likely art selectors
- avoid broad fallback art heuristics that overwrite evidence
- use narrow verified family-specific rules only where backed by executable behavior
Current status of that first export step:
- the PSX cache builder now decodes `DAT_800758d4` as a packed per-state signed extents table
- exported `stateLayers` preserve those decoded extents for each type
- each exported scene item and `mapSource` row now carries the resolved `companionExtents` tuple for its chosen live state when available
Medium-term:
- use the recovered companion extents to improve viewer-side inspection and future occlusion/contact overlays
- recover the remaining family-specific state-to-art bridge for unresolved root-dispatch families without assuming `DAT_800758d4` is the art source
- close palette selection more broadly once the art-state path is stable enough
Long-term:
- replace placeholder-heavy families with executable-backed final art selection
- keep scene export reversible so future corrections do not require a fresh reverse-engineering pass over the raw files
## Current Best One-Line Model
The PSX map is stored as a multi-section level bundle plus per-type runtime banks; the executable turns authored rows into live objects, advances and sometimes reseats their state scripts, updates companion extents from `DAT_800758d4`, projects them into one of two world-facing render lanes, and finally draws resource/frame pairs driven by the live script word. The viewer can now reconstruct most of that chain, and the remaining unreadable output is concentrated in the last unresolved live state-to-art bridge for a few still-placeholder-heavy families.

View file

@ -10,6 +10,7 @@
## Current State
- The detailed architectural write-up now lives in `docs/psx/map-rendering.md`; keep that file as the long-form reference for storage format, runtime banks, render lanes, and viewer reassembly, and keep this file focused on the active plan and remaining blockers.
- `docs/psx/psx.md` already closes the boot executable, the broad `LSET*.WDL` layout, and the likely split between map-like regions and graphics-like regions.
- The earlier `region00-first` viewer export is now known to be based on a bad assumption: the `~45..59` records it exposes per map are only the small top-level WDL descriptor stream, not the full level content.
- The stronger current model is a multi-section bundle layout: a top-level `0x18`-byte dispatch-record table, typed subordinate resource tables rooted at `DAT_800758cc/d0/d4/d8`, and at least one separate compressed level-state blob that is inflated into `DAT_8006769c` by `FUN_8003b00c(..., 0x3e00, 0x3e00)`.
@ -19,7 +20,9 @@
- The current verified processed build exposes `62` PSX maps in the live renderer catalog under the runtime-record scene format (`4032` atlas-backed shapes, `1925` packed shared atlases after the latest atlas pass).
- The exporter root cause is now clearer: the old five-region post-audio carve was still masking the real visible payload. Loader-sized `post_audio_section_00` contains both the small `0x18` root descriptor rows and the dense 24-byte bulk placement rows, so the cache builder now recovers both visible families from that first real section instead of from the guessed `region00/region01` split.
- A verified full rebuild now carries `region00 + region01` across all `62` maps. `LSET1/L0.WDL` now emits `1189` items, `LSET1/L1.WDL` emits `754`, and every rebuilt map now reports `uniqueZCount > 1` instead of the earlier mostly-flat `z = 0` export.
- The next subordinate layers are now structurally split too: `DAT_800758d8` is the per-type art/template bank, `DAT_800758d0` feeds the simple constructor's local component payload, and `DAT_800758cc/d4` feed the compound constructor's state/variant tables. The executable model is solid, but the generic raw-file export for `DAT_800758cc/d0/d4` is not currently landing in the live scene cache, so that serialization path stays open work.
- The next subordinate layers are now structurally split too: `DAT_800758d8` is the per-type art/template bank, `DAT_800758d0` feeds the simple constructor's local component payload, and `DAT_800758cc/d4` feed the compound constructor's state/variant tables. The renderer-side serialization gap is now closed too: the current `psx-runtime-record-probe-v6` path exports those banks into `stateLayers`, and the scene writer preserves them in both scene metadata and `mapSource`.
- The live viewer now trims the heavyweight `stateLayers` and `decodedRuntimeLayers` blobs back out of the interactive runtime scene after load and only reattaches them for scene-JSON export. That keeps the executable-backed research payload reversible without forcing normal pan/zoom interaction to carry the full PSX bank dumps.
- The compound-bank finder is broader now too. When a typed-section-16 bank is not found at a parsed section boundary, the cache builder falls back to an absolute-file scan, which is how the late `DAT_800758cc/d0/d4` source candidates now land in the exported scene state.
- The late LSET template bank is now less speculative too. The currently working map-local `DAT_800758d8` candidate is not the old "small typed section" guess; on retail `LSET1/L9.WDL` it decodes cleanly only when the parser treats the late large section as a bank with an embedded `+0x38` start, which is now enough to recover real bundle-backed mappings for a first subset of map types.
- The main visible bulk layer is no longer flat. The accepted `region01` placements now use the constructor-backed `+0x06` byte as provisional `z`, and `LSET1/L0.WDL` currently exports `11` distinct structured elevation levels instead of one forced `z = 0` plane.
- One renderer-side mismatch is now closed: PSX sprites use authored `item.screen` rectangles, and the bounding/highlight overlay path now uses those same authored rectangles instead of recomputing a DOS-style wireframe from provisional `world` coordinates.
@ -29,12 +32,43 @@
- The offline `FUN_8003b00c` path now exists in the renderer-local exporter and serializes one candidate on-disk compressed source plus the decoded `0x3e00` state buffer into the cache for each map.
- The type-to-art pass is still open. The exporter now scans parsed per-type template-bank payloads for bundle references, and it no longer promotes the disproven scan-order bundle fallback into visible map art. Unverified types stay on placeholders until the executable state/type path yields a real art binding.
- That loader-shaped bank selection is now already paying off in the live cache: map `9` moved from `0` resolved bundle-mapped items to `111` after the template pass switched to the embedded late-section parse, even though unresolved root-dispatch families such as `0x0042` and `0x0049` still need the downstream state/variant path before they can stop using placeholders.
- Current status: the PSX viewer output is still unreadable as a practical map. The exported placements and projection are good enough to sketch room massing, but most visible section-0 families still lack the executable's final state/variant-driven art binding and therefore remain on placeholders.
- A focused art-binding recovery pass is now landed in the cache builder too; see `docs/psx/art-binding-recovery.md` for the measured recovery note. The exporter now treats many zero-block `DAT_800758d8` constructor-placement types as inherited-art candidates instead of dropping directly to placeholders, first via same-map `DAT_800758cc` script-signature donors and then via a constrained nearest-donor fallback inside the current `0x003e..0x0064` constructor-placement family band.
- That heuristic materially improved the built cache even though it is still provisional rather than executable-proved. The latest rebuild moved the scene set from `58,262` fallback items / `1,714` bundle-mapped items down to `25,038` fallback items / `34,938` bundle-mapped items. Representative maps such as `0`, `9`, and `43` are now mostly real-art scenes, while `map 104` remains the clear outlier with `866` fallback items versus only `136` bundle-mapped items.
- The old fallback art binding is now positively disproven for map rendering, not just "still unverified": in the live cache, early `section0_dispatch_roots` types `0x0042` and `0x0049` repeatedly bind to portrait/talk-animation bundles (for example map `0` offsets `0x000B2970` and `0x000D84F4`), which confirms the section-0 dispatch rows are generic runtime-object descriptors whose visible art still depends on downstream per-type state/variant selection.
- The executable-side type path is now clearer and named in the live PSX Ghidra database. `psx_object_create_simple_record` and `psx_object_create_compound_record` both index the same per-type banks rooted at `DAT_800758d8/d0/cc/d4`; `psx_object_select_state_script` selects an active state script from `DAT_800758cc`, `psx_object_advance_state_script` at `0x80025d68` interprets sentinel-driven script records, `psx_object_lookup_variant_entry` resolves a companion entry from `DAT_800758d4`, and `psx_reset_type_runtime_banks_from` at `0x80025ce8` is the nearby bank-reset helper that had been misnamed earlier. So the missing map-render rule is not one flat `type -> bundle` table but a multi-stage runtime selection path.
- The visible render pass is less opaque now too. `FUN_80041378` draws in three stages: the sorted visible-object list through `FUN_80041458`, a second special-visible list through `FUN_80041144`, and then HUD/overlay/icon primitives through `FUN_800416cc`. That means the remaining map-viewer gap is still mainly in world-object and special-object families, not in the HUD pass.
- The next decompilation target is narrower now too: `FUN_8002906c` is the highest-value follow-up because it is a verified post-construction state reselection path that can overwrite the live script choice from `FUN_8003bc1c(obj) >> 2 & 0xf`, which is exactly the missing bridge between exported placement selectors and the art-facing `DAT_800758d4` variant lookup.
- The stage-2 path is now strong enough to affect renderer planning directly. `FUN_80040f78` is the queue-builder for the `FUN_80041144` pass: it projects an object just like the main `FUN_80040d44` path but appends it to `DAT_80078b70` / `DAT_80067472` instead of the main `DAT_8006ad5c` visible list. So a renderer that only models the stage-1 visible list will still miss a real world-facing object lane.
- The broader lifecycle is readable now too. `psx_level_session_loop` is the outer level-session loop; `wdl_resource_bundle_load_by_index` performs the actual `SPEC_A.WDL` + selected `LSET*.WDL` load and root-record dispatch; `psx_world_frame_tick` is the normal per-frame world loop; and `FUN_80041378` is the top-level draw submission.
- The authored record passes now line up with the viewer model closely enough to use as the current executable ground truth: `psx_dispatch_section0_dispatch_roots` dispatches the `DAT_80067720` `0x18`-stride family plus the extra fixed-size entries near `DAT_80067658`, while `psx_dispatch_section0_constructor_placements` dispatches the `DAT_800678f0` `0x0c`-stride family. Those are the closest executable matches for the current `section0_dispatch_roots` and `section0_constructor_placements` viewer families.
- The live-object passes are separated too: `psx_run_live_object_type_updates` runs the per-type update callback over the linked live object list at `DAT_800675ac`, `psx_run_live_object_behavior_callbacks` runs the later per-object behavior callback stored on each object, and `FUN_80029de0` is the broad world/player motion integrator that sits between behavior updates and draw submission.
- The cull/draw bridge is now explicit too: `FUN_800423b0` gates the two authored record-family dispatch passes, `FUN_80042424` gates already-instantiated live objects, and `FUN_80041458` draws from the final authored screen rectangle while sourcing palette overrides directly from the original record pointer at `obj+0xa0`.
- Palette override provenance is tighter too: object field `+0xa0` is the original authored source-record pointer written by both constructors, so the current override path in `FUN_80041458` is reading authored record bytes directly rather than a hidden runtime side table.
- The cache builder now uses that evidence too: shared PSX reference art is keyed by `map:type:palette`, and scene export applies the authored palette byte when the source record exposes the proven override lane instead of always baking the bundle default palette.
- One narrow renderer-side consequence is now verified in output, not just in notes: the cache builder now applies the executable-backed `0x0050` selector map (`0..3 -> frame 0..3`) as a temporary fallback, and retail map `9` now exports `type=80 state_selector=1 chosen_frame=1` instead of forcing frame `0`.
- The renderer-side state parser is stronger too: the current `buildTypeStateFrameMaps` path no longer treats each `DAT_800758cc` selector as a flat first-word frame index. It now follows the currently verified sentinel control flow (`0xfffe`, `0xfffd`, `0xfffc`, `0xfffb`) when building selector-to-frame candidates from the exported state layer.
- The top-level level-loader header is now tighter too. `wdl_resource_bundle_load_by_index` reads a `0x38` file header whose first nine dwords are section sizes, allocates a separate `0x50` runtime-header block at `DAT_80067794`, loads one extra non-contiguous blob at `DAT_8006767c`, and only then optionally inflates the `DAT_8006b5d8` source into `DAT_8006769c`.
- `FUN_80039dc4` is now identified as the applier for that `DAT_80067794` level runtime-header block, which means the map-viewer export still lacks one executable-backed level metadata lane even after the section-0 and per-type-bank work.
- The current best read on that runtime-header lane is narrower now: because the downstream refresh helper is shared with input/menu paths too, `DAT_80067794` looks more like per-level runtime mode/camera/presentation state than a hidden extra placement layer.
- The state-script control flow is tighter too: `0xfffd` is the direct in-family jump control, `0xfffc` switches to a subsidiary script table entry immediately, and `0xfffb` is the scan-forward variant that consumes the next in-band `0xfffd` selector before switching.
- The per-type bank provenance is tighter too. `psx_load_type_state_banks` runs twice before the selected `LSET*.WDL` opens and twice again after the level-local lanes are loaded, while the standalone late descriptor stream that fills `DAT_800758d8` sits between the second pair. So `DAT_800758d8` is definitely its own late bank, and the remaining `DAT_800758d0/cc/d4` lanes belong to the adjacent state-bank blobs rather than to the decompressed `0x3e00` map-state buffer.
- The unresolved type path is more clearly dynamic than before: `psx_object_lookup_variant_entry` is called from both constructors and from `psx_object_advance_state_script`, so a root-dispatch family can change its visible companion bytes after script jumps instead of fixing them only at spawn time.
- The built scene cache now tightens the remaining `0x0042` blocker too. The exported `state_selector` label is confirmed to be raw word `u4`, not a derived guess, while `lane` is raw word `u5`. A full cache scan found `3944` `type=0x0042` placeholders across `61` maps and showed selectors `0..4`, with real `3`/`4` cases on `map-4`, `map-5`, `map-8`, `map-45`, `map-69`, and `map-85`.
- That same cache scan shows lane and selector are not interchangeable. `0x0042` still clusters mostly on lanes `0x0020` and `0x0022`, but there are also `lane=0x0030` exports with `state_selector=0` (for example on `map-108`). So the next executable-backed pass should treat `u4` as the confirmed selector input and `u5` as a separate lane/class byte while tracing the post-advance `DAT_800758d4` variant lookup.
- The next narrowing pass is now clearer too: the art-facing variant index is not the raw placement selector byte directly. `psx_object_select_state_script` stores the authored selector in `obj+0x9e`, but `psx_object_lookup_variant_entry` indexes `DAT_800758d4` with `obj+0x94`, the current script word. Two runtime paths (`FUN_80028c94` and `FUN_8002906c`) can reseat the active script from `FUN_8003bc1c(obj) >> 2 & 0xf`, so at least some live `0x0042` selectors `3` and `4` come from heading-based runtime reselection rather than from the original record alone.
- The newly traced consumer side narrows that further. `psx_object_advance_state_script` sign-extends the `DAT_800758d4` lookup result into `obj+0x30/+0x34/+0x38`, and the verified downstream consumers of those fields are collision/contact helpers (`psx_object_test_overlap_3d`, `psx_object_update_contact_block_flags`, and the target-bounds reads inside the reselection helpers), not the visible draw path. So the practical exporter change is to serialize `DAT_800758d4` as signed companion-extents metadata and stop treating it as the leading candidate for the missing final art table.
- That exporter step is now landed in the viewer-side cache path too. `buildTypeCompanionExtentsMaps` decodes the exported `DAT_800758d4` layer as a `u32 count + packed 3-byte signed extents` table, scene `stateLayers` now preserve those decoded extents per type/state, and each exported PSX scene item and `mapSource` row now carries the resolved `companionExtents` tuple for its chosen live state when one is available.
- That moves the remaining art problem to a more specific place: unresolved families still need a family-specific rule that explains how the live script word at `obj+0x94` interacts with the drawable resource at `obj+0x10` after post-spawn reselection. The next useful trace should therefore stay close to those family-specific presentation callers instead of spending more time on generic `DAT_800758d4` consumers.
- The latest dispatch-table pass narrows that again for `0x0042` and `0x0049`: they do not have unique per-type descriptor entries. Both sit inside the same generic descriptor cluster covering `0x003e..0x0050`, and their shared descriptor currently resolves to `psx_spawn_compound_record_advance_state_once`, `psx_object_refresh_main_visible_and_cleanup`, and the newly named `psx_object_release_to_free_list`. So the missing art rule is even less likely to be a special-case table fork and more likely to live downstream in generic object state/resource presentation.
- That runtime bridge is now wider and better grounded than it was when `FUN_8002906c` first became the top target. The newly named cluster around it shows that type-4 delayed interactions can actively rewrite the live script after spawn: `psx_type4_update_delayed_interaction` (`0x80029c20`) seeds a forward-probe delay, `psx_type4_reselect_motion_state` (`0x8002906c`) either hands off to `FUN_80028c94` or recomputes heading from motion state before calling `psx_object_select_state_script`, `psx_object_update_nearby_interactions` (`0x80029478`) is the broader non-type-4 nearby-object sweep, `psx_object_test_overlap_3d` (`0x80028298`) is the box-overlap predicate, `psx_object_update_contact_block_flags` (`0x800289f0`) updates directional contact/block bits, and `psx_object_register_contact_pair` (`0x8002845c`) links both objects into the bilateral contact queue.
- The motion-heading piece is now named too. `psx_object_reselect_state_from_target_vector` (`0x80028c94`) writes a target-relative motion vector into `obj+0x60/+0x64/+0x68`, `psx_object_quantize_motion_heading16` (`0x8003bc1c`) wraps the current object motion vector, and `psx_quantize_vector_heading16` (`0x8003b980`) reduces that vector to one of 16 heading buckets before the caller maps it back to a `DAT_800758cc` script selector. Combined with the already-verified `psx_object_advance_state_script -> psx_object_lookup_variant_entry` path, this means the art-facing `DAT_800758d4` lookup is driven by the current script word at `obj+0x94`, not directly by the original placement selector stored at `obj+0x9e`.
- The local render-routing wrappers are named now too, which sharpens the practical exporter story. `psx_spawn_compound_record_advance_state_once` (`0x80013618`) and `psx_spawn_simple_record_set_active_flag` (`0x8001372c`) are constructor-side wrappers that immediately push new objects into specific live states, while `psx_object_refresh_main_visible_and_cleanup` (`0x80013688`) is a compact stage-1 projector/cleanup wrapper and `psx_object_advance_state_and_queue_special_visible` (`0x80013758`) is a compact stage-2 wrapper that advances script state and immediately queues through `psx_project_object_special_visible_queue`. So the executable already contains small dedicated routing helpers for “advance state, then main visible” versus “advance state, then special visible”, not just one monolithic render path.
- The owner-level bridge is named now too: `psx_object_integrate_motion_and_route_visible` (`0x800131a8`) integrates per-object motion, refreshes visibility flags, then advances script state and routes the object into either the stage-1 main visible list or the stage-2 special-visible queue. That means the runtime split relevant to the viewer is not a late draw-only distinction; it is already baked into the per-object motion/update step.
- The drawable-resource side is much tighter now too. Both constructors resolve the per-type art bank and store the resulting drawable resource at `obj+0x10` before the object enters the live update loop; the current script word at `obj+0x94` is then passed straight through `psx_resource_frame_origin_x/y`, `psx_resource_frame_width/height`, and finally into `psx_sprite_resource_submit_frame` or `psx_image_table_submit_frame` during the stage-1 and stage-2 draw passes. So the renderer now has a much stronger executable-backed chain from type bank -> object resource pointer -> live frame index -> final primitive submission.
- The builder under that constructor-side handoff is named too: `psx_create_image_resource_from_descriptor` creates the drawable resource from the per-type art descriptor, using `image_resource_bind_vram_slot` for single-image type-4 resources and `image_bundle_load_to_vram` for multi-frame type-5 bundles. That means the remaining viewer gap is no longer “where does the object resource pointer come from”; it is the last live state/variant rule that determines which resource/frame combination unresolved families actually present.
- The stage-1 list machinery is named end to end as well: `psx_main_visible_list_add`, `psx_main_visible_list_remove`, `psx_main_visible_list_rebucket_object`, `psx_main_visible_list_refresh_from_live_chain`, `psx_main_visible_list_sort_range`, and `psx_main_visible_list_get_sorted_slice` are the concrete helpers behind the main visible-object pass that `psx_draw_world_visible_passes` submits before the stage-2 queue.
- Practical consequence for the viewer plan: the exported placement selector is now firmly only an input, not a final art choice. For unresolved families the next bridge must model at least part of the post-spawn interaction/reselection lane before a `type -> frame` rule can be trusted, otherwise the viewer will keep drawing structurally correct but still unreadable placeholder-heavy scenes.
- `JL-9` already appears in recovered PSX weapon-name tables, but gameplay availability and sprite identity are not yet closed.
## Success Criteria
@ -144,15 +178,17 @@ Expected output:
## Immediate Next Batch
1. In Ghidra, tighten the section-family naming around `DAT_800678f4`, `DAT_80067720`, and the candidate `DAT_8006b5d8` source so the current `section0_*` labels can be promoted from exporter-safe names to exact loader names.
2. Record which helpers read `DAT_80067720` versus which helpers read the decompressed `DAT_8006769c` buffer now that the offline decode path is present in the cache.
2. Record which helpers read `DAT_80067720`, which apply the separate `DAT_80067794` runtime-header block, and which helpers read the decompressed `DAT_8006769c` buffer now that the offline decode path is present in the cache.
3. Compare the rebuilt all-map exports against recognizable rooms and decide whether the remaining missing structure now lives mainly in the decoded `DAT_8006769c` buffer or in still-unrendered subordinate tables.
4. Tighten the raw file mappings for the newly exported runtime-bank layers (`DAT_800758d8`, `DAT_800758d0`, `DAT_800758cc`, `DAT_800758d4`) so their current section selection is proven rather than heuristic.
Current delta: the bank split is stronger now. `DAT_800758d8` comes from its own late descriptor stream, while `DAT_800758d0/cc/d4` belong to the adjacent `psx_load_type_state_banks` blobs. The remaining open question is the exact sub-split inside those state-bank blobs, not whether they come from the decompressed level-state lane.
5. Recover an actual bundle/frame reference from the per-type template payloads or their consumers so the exporter can replace the now-disproven scan-order bundle fallback with a verified type-to-art rule.
Current delta: the template bank selection is now stronger and already recovers real art for a first subset, but the still-missing families need the stage-1/stage-2 object draw path plus `DAT_800758cc/d4` state interpretation, not more HUD/overlay decoding.
Current delta: stage 2 is no longer hypothetical. The next renderer-improvement candidate is to expose/export the queued-object lane that feeds `FUN_80041144`, because the executable now clearly maintains it separately from the main visible list.
Current delta: the unresolved families are also clearly dynamic now because `psx_object_lookup_variant_entry` reruns after `psx_object_advance_state_script`; the next verified art-binding pass should therefore sample post-jump state, not only constructor-time selectors.
6. Split section-0 placements into at least three executable-backed render classes: world-facing geometry/object placements, animated runtime-only objects, and clearly non-map-facing UI/talk assets such as the portrait bundles currently surfacing through fallback art matching.
7. Decode the `psx_object_advance_state_script` sentinel opcodes (`ffff`, `fffe`, `fffd`, `fffc`, `fffb`) well enough to tell when a placement loops, jumps into a subsidiary script, or fires a side-effect helper, because that state-machine branch is now the main discriminator between map-facing art and non-map runtime assets.
Current delta: `fffe` is now closed as an audio/effect dispatch through `FUN_8004061c`, so the next sentinel work should focus on the remaining control-flow opcodes.
Current delta: `fffe` is closed as an audio/effect dispatch through `FUN_8004061c`, `fffd` is the direct indexed jump, and `fffc/fffb` are now separated as the immediate subsidiary-switch versus scan-forward subsidiary-switch pair.
8. In parallel with the map pass, trace the palette-override read path from the known draw helper caller and document which source field feeds the resolved CLUT.
9. Locate the `JL-9` weapon entry in the PSX executable tables and log its table index, surrounding weapon names, and all code/data xrefs.
10. Create a short follow-up note in `docs/psx/` after the batch rather than burying the result only in Ghidra comments.

View file

@ -571,7 +571,7 @@ Immediate implications for the next decode pass:
- the public renderer integration path is now proven enough to use as a live debug target for PSX map-format work
- the next priority is to replace the invalidated `u0 -> bundle index` hypothesis with a real type/resource lookup recovered from `level_resource_stream_load`
- `post_audio_region_00` is now a top-tier candidate for that work because its new diagnostics expose a count-prefixed preamble plus compact typed records that look more loader-compatible than the old region-01 art probe
- the palette override path is still the main blocker to correct final color selection even when the bundle/frame choice is plausible
- the palette override path is now partially landed in the viewer/exporter too: the cache builder applies the executable-backed authored override byte when the source record exposes the proven `+0x06` / `+0x0c` lane, so the remaining blocker is the cases where the runtime first picks a different object/variant/state than the current export model
- once the bundle key and palette control path are recovered, the same scene-export path can graduate from `real-art probe` to actual PSX map rendering
## PSX Provisional Real-Art Probe
@ -632,7 +632,19 @@ The current live viewer export was built on the wrong premise. The `~45..59` rec
What the loader actually does:
- `wdl_resource_bundle_load_by_index` reads the selected `LSET*.WDL` into multiple section pointers, not one flat placement stream.
- the per-level file header is now structurally tighter too: after the initial `0x38` read, the loader treats the first nine dwords as contiguous section sizes, allocates a separate `0x50` runtime-header block at `DAT_80067794`, and later reads one more independently allocated compressed source blob before the optional `FUN_8003b00c(..., 0x3e00, 0x3e00)` inflate.
- The first runtime section is a top-level table at `DAT_800678f4` whose record stride is `0x18` bytes.
- The second runtime section at `DAT_80067720` is also `0x18`-stride, but it is not just a duplicate alias of the first pointer. The loader assigns the contiguous level sections in this order:
- `DAT_800678f4 = section[0]` root dispatch table
- `DAT_80067720 = section[1]` secondary `0x18`-stride control/placement table
- `DAT_800678f0 = section[2]`
- `DAT_80067938 = section[3]`
- `DAT_80067838 = section[5]`
- `DAT_800675f8 = section[6]`
- `DAT_8006754c = section[7]`
- `DAT_80067840 = section[8]`
- `DAT_800676d8 = section[9]` palette block loaded just before `level_palette_header_apply`
- The skipped header slot is not dead space. The loader allocates it separately as `DAT_8006767c`, then feeds it to `FUN_80040768`; that lane stays distinct from the contiguous section pack and from the later `DAT_8006b5d8 -> DAT_8006769c` decompression path.
- The loader iterates that first section with:
- `for each 0x18-byte top-level record`
- `type = record[+0x08]`
@ -684,7 +696,9 @@ Exporter status after the next renderer pass:
- `section0_constructor_placements` for the dense constructor-fed placement records
- a verified full rebuild now exports all `62` PSX maps with large scene volumes and non-flat `z` stats. `LSET1/L0.WDL` now emits `1189` items, `LSET1/L1.WDL` jumps from `53` items to `754`, and the rebuilt catalog reports `62/62` maps with `section0_dispatch_roots + section0_constructor_placements` coverage and `uniqueZCount > 1`.
- the renderer-side reference payload no longer emits one atlas per resolved PSX shape. The new packed-atlas pass reduces the shared PSX reference cache from the old `4032` one-shape atlases to `1925` shared packed atlases across the same `4032` shape definitions, and a spot-check on `LSET1/L0.WDL` now exports the map scene itself with `atlasCount = 1` instead of a long per-bundle atlas list.
- the cache export still carries the parsed `DAT_800758d8` candidate section and an offline `FUN_8003b00c` decode candidate for the compressed source feeding `DAT_8006b5d8 -> DAT_8006769c`, but the generic raw-file `DAT_800758cc/d0/d4` serialization is not currently landing in the live scene cache and should be treated as an open exporter gap rather than a closed layer.
- the cache export now carries more than the parsed `DAT_800758d8` candidate section. In the current `psx-runtime-record-probe-v6` scene path, `map_renderer/src/lib/psx-cache.js` serializes `DAT_800758cc`, `DAT_800758d0`, `DAT_800758d4`, and the offline `FUN_8003b00c` decode candidate for `DAT_8006b5d8 -> DAT_8006769c` into `stateLayers`, and the scene writer preserves those layers in both scene metadata and `mapSource`.
- the new typed-section-16 discovery path is also broader than the earlier section-start probe: when no parsed-section candidate wins, the cache builder now falls back to an absolute-file scan, which is why the late compound-bank blobs can now land in the export even when their serialized source does not start exactly at a pre-labeled section boundary.
- the file-side header block now separates more cleanly too: `FUN_80039c40` allocates a `0x50` level runtime-header block at `DAT_80067794`, and `FUN_80039dc4` copies that block into globals such as `DAT_80078ab0`, `DAT_80078a88`, `DAT_80078a8c`, `DAT_80078a4c`, and `DAT_80067354` before calling `FUN_80042ec4`. So the first visible/root sections are not the only authoritative level metadata; the loader also applies a dedicated `0x50` per-level runtime header after the optional `0x3e00` decode succeeds.
- this still does not mean the PSX map decode is fully solved: the viewer now has enough volume to represent whole-level candidates across the disc, but the remaining blocker is semantic decoding of the subordinate runtime banks and the separate decompressed `0x3e00` buffer, not record-count starvation.
- the type-to-art path is only partially improved. The cache builder now scans the parsed per-type art-template payloads for bundle references, and the renderer no longer treats the disproven scan-order `u0 -> bundle` mapping as trustworthy visible art. Unverified types now stay on placeholder art instead of surfacing known-bad portrait/talk bundles as map geometry.
- the scan-order fallback is now known to be wrong at the root, not merely incomplete. In the live `.cache` output, `section0_dispatch_roots` types `0x0042` and `0x0049` repeatedly bind to portrait/talk-animation bundles such as map `0` type `0042` -> offset `0x000B2970` and map `0` type `0049` -> offset `0x000D84F4`, with the same failure pattern continuing through early maps. Those portrait bundles are useful negative evidence: they show the top-level dispatch rows are generic object/state descriptors, not a direct map-graphics stream that can be paired to bundle order.
@ -695,6 +709,11 @@ Next decoded runtime layers from the constructor pass:
- `DAT_800758d0` is a per-type companion/component bank for the simpler constructor family. `FUN_800249f4` copies the resolved pointer from that bank into the local object payload at `obj->8->[0,4]`, so this looks like a per-type component/template block rather than a top-level placement stream.
- `DAT_800758cc` is a per-type offset-table bank for the compound constructor family. `FUN_80024eec` stores it at `obj+0x88`, and `FUN_800260e8` later indexes it with the placement byte at `record+0x08` to resolve a state/offset subrecord into `obj+0x8c/0x90`.
- `DAT_800758d4` is another per-type companion bank for the compound constructor family. `FUN_80024eec` stores it at `obj+0x84`, and `FUN_8002841c` queries it later using the object's `+0x94` selector, so it behaves like a variant table or companion lookup rather than raw map geometry.
- The raw-file provenance for those banks is tighter now too. Inside `wdl_resource_bundle_load_by_index`, `psx_load_type_state_banks` is called four times total: twice before the selected `LSET*.WDL` opens and twice again after the level-local section pack and palette/header lanes are loaded. The standalone `8`-byte descriptor-table read that assigns `DAT_800758d8` sits between the second pair of `psx_load_type_state_banks` calls. Current best read:
- one common/shared bank pair loads before the map-local WDL opens
- one map-local override pair loads after the level-local header and palette lanes
- `DAT_800758d8` definitely comes from its own late descriptor stream
- `DAT_800758d0` plus `DAT_800758cc/d4` are loaded through the adjacent `psx_load_type_state_banks` blobs rather than from the root section tables or the decompressed `0x3e00` state buffer
- The key functions in that chain are now renamed in the live PSX Ghidra database:
- `FUN_800249f4` -> `psx_object_create_simple_record`
- `FUN_80024eec` -> `psx_object_create_compound_record`
@ -711,16 +730,19 @@ Next decoded runtime layers from the constructor pass:
- `psx_create_image_resource_from_descriptor` turns the `DAT_800758d8` per-type descriptor into a renderable resource/header object; this is why `DAT_800758d8` should be read as an art/template descriptor bank, not as a whole-map tile layer.
- `psx_object_select_state_script` selects a state or animation subrecord from `DAT_800758cc` using a placement byte (`record+0x08` in the compound family), storing the resolved script/state pointer at `obj+0x8c/0x90` and the selector at `obj+0x9e`.
- `psx_object_advance_state_script` then interprets the active state script with sentinel/control values such as `0xffff`, `0xfffe`, `0xfffd`, `0xfffc`, and `0xfffb`, so the visible frame path is explicitly state-driven rather than just "type id -> one bundle".
- `psx_object_lookup_variant_entry` is not only a constructor-time helper. Its call graph now shows three direct consumers: `psx_object_create_simple_record`, `psx_object_create_compound_record`, and `psx_object_advance_state_script`. That means unresolved families cannot be modeled as one spawn-time `type -> variant` choice; the visible companion bytes can be recomputed after state-script control flow advances.
- The current renderer-side consequence is important: section-0 word `u4` is no longer treated as a verified sprite-frame index. It is now carried forward as a state-selector candidate in exported scene metadata until the `DAT_800758cc/d4` path is decoded far enough to pick the right animation frame from executable evidence.
- Current strongest sentinel read:
- `0xfffe` dispatches `FUN_8004061c`, which is an audio/effect helper rather than a visible-frame selector.
- `0xfffd` is an in-script jump/re-anchor control that rewrites `obj+0x90` relative to the current script base.
- `0xfffc` switches `obj+0x8c/0x90` to another subsidiary script selected through the `DAT_800758cc` offset table.
- `0xfffb` also switches into a subsidiary script, but first scans forward to an in-script `0xfffd` marker before choosing the destination entry.
- `0xfffc` resolves a fresh subsidiary script base from the table rooted at `obj+0x88`, then immediately swaps both `obj+0x8c` and `obj+0x90` to that destination before continuing from the first record there.
- `0xfffb` also resolves a subsidiary script from the same table, but first walks the current script forward until it finds an in-band `0xfffd` marker and then uses that marker's selector word to choose the destination entry.
- Current best read of those sentinels:
- `0xffff` marks a terminal or restart control that re-anchors the script at `obj+0x8c` and raises object-state flags.
- `0xfffe` dispatches a side-effect helper (`FUN_8004061c`) using the following word as a parameter before advancing.
- `0xfffd`, `0xfffc`, and `0xfffb` switch into subsidiary scripts through the `DAT_800758cc` offset table rooted at `obj+0x88`.
- `0xfffd` is the direct indexed jump control within the current script family.
- `0xfffc` and `0xfffb` are both subsidiary-script switches through the `DAT_800758cc` offset table rooted at `obj+0x88`, but `0xfffb` is specifically the scan-forward variant that consumes the next in-band `0xfffd` selector.
- because `psx_object_advance_state_script` calls `psx_object_lookup_variant_entry` after those control-flow steps, the visible art choice for unresolved types may depend on post-jump script state rather than on the placement selector byte alone.
- `psx_object_lookup_variant_entry` finally uses `obj+0x94` to look up a companion entry in `DAT_800758d4`, which means even after construction the art-facing choice is still mediated by per-type variant/state tables.
- This means the next PSX layers are now at least structurally separated:
- visible root descriptors (`section0_dispatch_roots`, legacy alias `region00`)
@ -731,9 +753,16 @@ Next decoded runtime layers from the constructor pass:
- per-type compound variant tables (`DAT_800758d4`)
- the still-separate decompressed `0x3e00` level-state buffer (`DAT_8006769c`)
- The current renderer pass now records those banks explicitly as exported scene/state layers, while still only rendering the first two as visible scene items.
- The live viewer now keeps those heavyweight bank dumps out of the hot interactive scene state after load: `stateLayers` and `decodedRuntimeLayers` are preserved for exported scene JSON, but the runtime pan/zoom/editor path uses a trimmed scene object so normal navigation does not keep dragging the full PSX research blobs through the controller.
- The renderer-side state parser is also stronger than the earlier flat selector map. `buildTypeStateFrameMaps` now reads the exported `DAT_800758cc` layer and follows the currently verified control-flow sentinels (`0xfffe`, `0xfffd`, `0xfffc`, `0xfffb`) instead of just taking the first word of each selector entry. That does not fully close type `0x0042` or `0x0049`, but it does mean the viewer can now distinguish more fallback states from real script structure rather than from a blind `selector -> first frame` heuristic.
- The live cache now tightens the selector provenance too. In exported PSX scene items, the `state_selector=...` label is written directly from raw word `u4`, while the trailing `lane=...` label is raw word `u5`; this is not a post-hoc heuristic. A full scan of the built `psx-remorse` scene cache found `3944` visible `type=0x0042` placeholders across `61` maps, and the observed selectors track `u4` exactly (`0 -> 0x0000`, `1 -> 0x0001`, `2 -> 0x0002`, `3 -> 0x0003`, `4 -> 0x0004`).
- The executable-side handoff is narrower than that label might suggest. `psx_object_select_state_script` stores the authored selector byte in `obj+0x9e` and uses it only to choose the initial `DAT_800758cc` script base at `obj+0x8c/0x90`; `psx_object_lookup_variant_entry` does not index `DAT_800758d4` by `obj+0x9e`. It indexes by `obj+0x94`, which `psx_object_advance_state_script` refreshes from the current script entry after control-flow handling.
- Immediate map-viewer consequence: the current fallback art probe should be treated only as a diagnostic overlay for candidate bundle families. A workable renderer will need to recover the per-type `DAT_800758d8` descriptor mapping and the downstream `DAT_800758cc/d4` state+variant selection path before it can decide whether a section-0 placement should show world geometry, an animated object, or something non-map-facing like a portrait/talk asset.
- Current status note: even with the recovered placement/projection path and the first subset of real bundle-backed types, the live PSX map output is still unreadable as a practical map because most section-0 placements still miss the executable's final state/variant-driven art binding and therefore collapse back to placeholders.
- The next loader-side correction is now verified in the live cache too: the effective late `LSET*.WDL` `DAT_800758d8` candidate is not the earlier small-section heuristic, but a large late section whose working descriptor stream begins at an embedded `+0x38` offset. On retail map `9` that correction alone lifts `bundleMappedItemCount` from `0` to `111`, which is enough to restore real bundle-backed art for a first subset of types without reintroducing the disproven scan-order fallback.
The still-unresolved root-dispatch families remain instructive rather than contradictory. `0x0042` and `0x0049` still stay on placeholders after the bank-selection fix, but the same pass now decodes their `DAT_800758cc` state rows more cleanly: type `0x0042` carries three selector-targeted scripts (`0`, `1`, `2`) that all terminate through `0xffff`, while type `0x0049` carries a single selector-`0` script. So the remaining blocker for those roots is no longer "find any late template bank at all"; it is the deeper `DAT_800758cc/d4` state-to-visible-art bridge.
The still-unresolved root-dispatch families remain instructive rather than contradictory. `0x0042` and `0x0049` still stay on placeholders after the bank-selection fix, but the same pass now decodes their `DAT_800758cc` state rows more cleanly: type `0x0042` carries three selector-targeted scripts (`0`, `1`, `2`) that all terminate through `0xffff`, while type `0x0049` carries a single selector-`0` script. The built scene cache shows that this is still not the whole art-facing discriminator: `type=0x0042` placeholders now appear with selectors `0..4`, and the higher selectors `3` and `4` are real exported cases rather than parser noise. Verified maps with `0x0042` selectors above `2` include `map-4`, `map-5`, `map-8`, `map-45`, `map-69`, and `map-85`.
Two runtime reselection paths now explain how those higher selectors can arise without contradicting the earlier three-script file read. `FUN_80028c94` and `FUN_8002906c` both recompute the active script with `psx_object_select_state_script(obj, FUN_8003bc1c(obj) >> 2 & 0xf)`, where `FUN_8003bc1c` quantizes the object's current motion vector at `obj+0x60/+0x64` through `FUN_8003b980` into a 16-way heading bucket. So cache-visible `0x0042` selectors `3` and `4` can come from runtime heading/state reselection, not only from the original placement byte.
That cache sweep also separates selector from lane more clearly than before. `0x0042` appears heavily on lanes `0x0020` and `0x0022`, and there are also map-local `lane=0x0030` cases (for example large clusters on `map-108`) that still export `state_selector=0`. So the unresolved bridge is narrower now: the visible-art rule cannot be modeled as just `u5` or just the initial `DAT_800758cc` selector parse. The remaining unknown is the downstream interaction between `u4`/`obj+0x9e`, the active state-script pointer at `obj+0x8c/0x90`, and the `DAT_800758d4` companion lookup that reruns after state-script advancement.
A first renderer-safe bridge landed even with that exporter gap still open: the verified `0x0050` state-script mapping (`selector 0..3 -> frame 0..3`) is now applied as a narrow fallback in the cache builder, and the rebuilt live map-9 scene now shows `type=80 state_selector=1 chosen_frame=1` instead of the old forced `chosen_frame=0`. Unresolved fallback placeholders are also now clamped to `opacity=0.45` in live scene output so the still-missing families stop visually overpowering the recovered real art. This remains intentionally scoped: the fallback frame map only covers the one family with direct executable-backed frame evidence, and the opacity clamp is diagnostic relief rather than a decoding claim.
The current draw split is clearer too. `FUN_80041378` is a three-stage render pass:
- stage 2: a second special-visible list drawn by `FUN_80041144`
@ -764,6 +793,81 @@ Recovered next visible layer from the bulk placement family:
- The PSX cache builder now uses that recovered `z` byte for `section0_constructor_placements` projection instead of forcing the whole bulk layer onto `z = 0`, while the top-level `section0_dispatch_roots` descriptor stream stays at `z = 0` until its own constructor-backed height source is proven.
- This is now the first PSX export pass in the viewer pipeline that produces visibly multi-layer whole-map candidates across the rebuilt retail catalog from executable-backed height data rather than from a single flattened candidate layer.
Additional constructor-backed coordinate grounding from the current pass:
- the current constructor split is now precise enough to state both authored row layouts at once:
- `psx_object_create_compound_record` reads `type` from `record+0x00`, `x/y` from `u16` words at `+0x02/+0x04`, authored elevation from byte `+0x06`, flags from `+0x0a`, and the initial state selector from byte `+0x08`.
- `psx_object_create_simple_record` reads `type` from `record+0x04`, `x/y` from `u16` words at `+0x08/+0x0a`, authored elevation from byte `+0x0c`, and the initial state selector from byte `+0x0e`.
- both constructors write the original authored source-record pointer to object field `+0xa0`, which keeps the later palette-override and state-selection paths tied to raw placement bytes rather than to a hidden runtime copy.
- both constructors also resolve the same per-type banks before any render pass runs:
- `DAT_800758d8` -> art/template descriptor
- `DAT_800758cc` -> state-script offset table
- `DAT_800758d4` -> companion variant lookup
- `DAT_800758d0` -> simple-record-only component payload
- this strengthens the viewer/exporter rule again: the two visible section-0 row families are constructor inputs, not already-final render primitives. Any direct `type -> bundle/frame` export path still has to respect the later state-script and variant lookups.
Recovered per-level runtime-header lane:
- `FUN_80039c40` is now confirmed as a pure `0x50` allocator for `DAT_80067794`, and `FUN_80039dc4` is the matching applier for that block.
- `FUN_80039dc4` copies fixed fields from `DAT_80067794` into the active level globals, including camera/runtime anchor values and several per-level mode bytes, then calls `FUN_80042ec4` to refresh dependent runtime state.
- The downstream call graph narrows that lane further: `psx_apply_level_runtime_header_block` is the only loader-side caller that feeds those values from WDL data, while `FUN_80042ec4` is also reused by `psx_input_device_init`, `memory_card_menu_tick`, and one additional front-end/system path. Current safest read: `DAT_80067794` is shared per-level runtime mode or presentation state rather than hidden bulk map geometry.
- Practical exporter consequence: keep the `DAT_80067794` fields as a first-class raw metadata lane, but do not treat them as a missing placement stream. They are more likely to affect camera/runtime modes, screen-space behavior, or level-global toggles than to supply extra map cells directly.
- The higher-level level lifecycle is now readable too. `psx_level_session_loop` is the outer level-session loop: it loads the selected WDL through `wdl_resource_bundle_load_by_index`, applies shared overlay/resource setup through `FUN_800388a8`, resets a small per-level step-flag block with `FUN_8003a498`, and then runs `psx_world_frame_tick` as the per-frame world loop until the current level session exits.
- `wdl_resource_bundle_load_by_index` is now mapped tightly enough for viewer work. Its effective order is: load `SPEC_A.WDL` and shared type art/state banks; open the selected `LSET*.WDL`; read the `0x38` section-size header; lay out the contiguous per-level section pack at `DAT_800678f4`, `DAT_80067720`, `DAT_800678f0`, `DAT_80067938`, `DAT_80067838`, `DAT_800675f8`, `DAT_8006754c`, `DAT_80067840`, and `DAT_800676d8`; load the detached `DAT_8006767c` blob; optionally inflate `DAT_8006b5d8` into `DAT_8006769c`; apply the runtime header at `DAT_80067794`; then dispatch the `0x18`-stride root records at `DAT_800678f4` through the per-type function table in `PTR_PTR_80063118`.
- The per-frame world loop in `psx_world_frame_tick` is now split clearly enough for renderer planning. In the normal in-level branch it ticks existing live objects through `psx_run_live_object_type_updates`, instantiates or refreshes nearby authored records through `psx_dispatch_section0_dispatch_roots` and `psx_dispatch_section0_constructor_placements`, runs per-object behavior callbacks through `psx_run_live_object_behavior_callbacks`, integrates world/player motion and active-object state through `FUN_80029de0`, updates queued transient resources through `FUN_8002aed0`, and only then submits the draw pass through `FUN_80041378`.
- The two authored record-family passes now line up directly with the viewer exporter model:
- `psx_dispatch_section0_dispatch_roots` walks the `DAT_80067720` `0x18`-stride family plus the fixed-size entries at `DAT_80067658`, culls them to roughly a `+/-0x140` neighborhood around the current focus object, and dispatches their per-type handlers. This is the closest executable match for the current `section0_dispatch_roots` viewer family.
- `psx_dispatch_section0_constructor_placements` walks the `DAT_800678f0` `0x0c`-stride family with the same neighborhood cull and per-type dispatch. This is the closest executable match for the current `section0_constructor_placements` viewer family.
- The already-instantiated-object passes are separated too:
- `psx_run_live_object_type_updates` iterates the linked live object list at `DAT_800675ac` and calls the per-type update callback (`type_vtable+8`) for active in-world objects.
- `psx_run_live_object_behavior_callbacks` then runs each live object's callback stored at `obj+0x98` / `obj[0x26]`, which is the later object-specific behavior/update pass.
- `FUN_80029de0` is the broad world-motion and player-state integrator that sits between behavior updates and draw submission. For viewer purposes, this is the runtime bridge between authored map placement and the motion/state values that later feed heading-based state reselection and projection.
- The cull-to-draw bridge is now closed too. `FUN_800423b0` is the authored-record screen-space gate used by the two section-0 dispatch passes, while `FUN_80042424` is the corresponding gate for already-instantiated live objects. Both use the same isometric camera basis around `DAT_800678d4`, which means the viewer can treat the record-family export as feeding the same projection space as the later live object list instead of as a separate map coordinate model.
- The main world-object draw helper is now grounded more tightly as well. `FUN_80041458` builds the final sprite primitive from the authored screen rectangle at `obj+0x20..+0x2e`, then ORs in a palette override read from the original source-record pointer at `obj+0xa0`: for types `0x003e..0x00ab` it uses the high byte of source word `+0x06`, and for types `>= 0x00ac` it uses the high byte of source word `+0x0c`. That means the remaining viewer mismatch is not where the override comes from, but when the runtime chooses a different object/variant/state before draw.
- The stage split is tighter too. `psx_project_object_special_visible_queue` (`0x80040f78`) feeds a distinct world-facing stage-2 queue, and `FUN_80041144` consumes that queue with the same projected screen rectangle fields and the same resource-specific draw helpers used by the stage-1 visible list. So the unreadable output is not explained by one missing HUD lane; the dominant gap is still the unresolved final art-binding path, with the stage-2 queue as a secondary world-object lane the viewer must eventually model.
- The next high-value executable target is now partly closed. `FUN_8002906c` is now renamed `psx_type4_reselect_motion_state`, and the surrounding interaction cluster is finally concrete enough to describe instead of leaving it as a black box:
- `psx_type4_update_delayed_interaction` (`0x80029c20`) is the type-4-only delayed wrapper. It probes ahead, stores the hit object at the controller-local `+0x38` slot, seeds a countdown from distance and speed, and dispatches to `psx_type4_reselect_motion_state` when that delay matures.
- `psx_type4_reselect_motion_state` (`0x8002906c`) is the post-construction reselection path for those delayed type-4 interactions. Depending on target flags it either hands off to the older `psx_object_reselect_state_from_target_vector` (`0x80028c94`) helper or flips the object's motion components against the target bounds, then reseats the live state script through `psx_object_select_state_script(obj, psx_object_quantize_motion_heading16(obj) >> 2 & 0xf)` before registering bilateral contact.
- `psx_object_update_nearby_interactions` (`0x80029478`) is the broad nearby-object sweep that feeds most of the non-type-4 collision and interaction bookkeeping. It walks the active object set, culls locally, performs overlap checks, updates directional contact/block flags, and registers contact pairs.
- `psx_object_test_overlap_3d` (`0x80028298`) is the box-overlap predicate used by that sweep, and `psx_object_update_contact_block_flags` (`0x800289f0`) is the direction-sensitive flag updater that writes the contact/block bits on the active object.
- `psx_object_register_contact_pair` (`0x8002845c`) is the bilateral contact queue helper used by both the broad sweep and the type-4 delayed path, which means the interaction lane is no longer speculative glue around the art/state system. It is a verified runtime bridge that can directly reseat the live script before the later `DAT_800758d4` companion lookup.
- The motion-heading side of that bridge is now closed one step further too:
- `psx_object_reselect_state_from_target_vector` (`0x80028c94`) is the older target-relative reselection helper used by `psx_type4_reselect_motion_state`. It builds a motion vector either from a target-relative offset or from a normalized target-to-player vector, writes that vector to `obj+0x60/+0x64/+0x68`, and reseats the live state script from the resulting heading bucket.
- `psx_object_quantize_motion_heading16` (`0x8003bc1c`) is the thin wrapper that feeds the current object motion vector into `psx_quantize_vector_heading16`.
- `psx_quantize_vector_heading16` (`0x8003b980`) is the actual 16-way heading quantizer. It classifies the current x/y vector against the threshold table at `DAT_80064990`, which is why the runtime reselection callers consistently reduce its result with `>> 2 & 0xf` before indexing the `DAT_800758cc` script table.
- The local post-advance render wrappers are also no longer anonymous labels:
- `psx_spawn_compound_record_advance_state_once` (`0x80013618`) creates one compound-record object, forces its script countdown to `1`, immediately runs `psx_object_advance_state_script`, and then marks the object with `obj+0x1e |= 0x20`. This is the cleanest currently recovered example of a constructor wrapper that intentionally advances into a non-initial live state before the object joins the normal update/render flow.
- `psx_spawn_simple_record_set_active_flag` (`0x8001372c`) is the simpler sibling for the simple-record constructor: create the object, then immediately set the low active flag in `obj+0x1e`.
- `psx_object_refresh_main_visible_and_cleanup` (`0x80013688`) is the compact stage-1 handoff wrapper. When the object still has a drawable resource and the `0x20` flag is set, it feeds the object through `psx_project_object_main_visible`; if the object is not in the `obj+0x1c & 1` hold state but does carry `obj+0x1e & 0x10`, it then runs the usual `FUN_80027f80` cleanup/follow-up path.
- `psx_object_advance_state_and_queue_special_visible` (`0x80013758`) is the compact stage-2 handoff wrapper. If the object still has a drawable resource, it advances the active script and immediately queues the object through `psx_project_object_special_visible_queue`, then applies a small sentinel cleanup block that clears world coordinates and selected flags for specific type/selector cases.
- The owner above those wrappers is now named too: `psx_object_integrate_motion_and_route_visible` (`0x800131a8`). It is the per-object bridge between movement state and rendering: it integrates position/velocity fields, refreshes the local visible/on-screen flags, handles the controlled-object side path, then advances the live state script and routes the object either into `psx_project_object_special_visible_queue` for the type-4/special-visible branch or into `psx_project_object_main_visible` for the normal drawable branch before the usual `FUN_80027f80` cleanup. This is the clearest recovered owner-level proof that state advancement and render-lane routing belong to the same runtime step.
- Those wrappers matter because they close one more gap between `psx_object_advance_state_script` and the render split. The stage-1/stage-2 divergence is not only visible in larger caller bodies such as the `0x80013524` / `0x80013564` branches; it also exists as small dedicated wrappers that either project through the main visible list after state work or advance-and-queue directly into the special-visible pass. That makes the renderer problem look even less like a missing flat table and more like a true runtime pipeline with multiple post-script routing paths.
- The render-side leaf chain is now close to end-to-end:
- `psx_project_object_main_visible` and `psx_project_object_special_visible_queue` both use the current script word at `obj+0x94` as the frame selector they pass into the frame-metric helpers.
- `psx_resource_frame_origin_x` (`0x8004513c`), `psx_resource_frame_origin_y` (`0x800451d0`), `psx_resource_frame_width` (`0x80045014`), and `psx_resource_frame_height` (`0x800450a8`) read per-frame rectangle metadata from the current drawable resource at `obj+0x10` using that same live frame index.
- `psx_draw_world_visible_passes` (`0x80041378`) is the top-level world-facing draw orchestrator: stage 1 draws the sorted main visible list through `psx_draw_main_visible_object` (`0x80041458`), stage 2 draws the queued special-visible list through `psx_draw_special_visible_queue` (`0x80041144`), and stage 3 is the non-world HUD/overlay pass.
- `psx_draw_main_visible_object` and `psx_draw_special_visible_queue` then dispatch to one of two final frame submitters depending on resource kind: `psx_sprite_resource_submit_frame` (`0x80044bdc`) handles the streamed/VRAM-backed sprite resource path, while `psx_image_table_submit_frame` (`0x80044e9c`) handles the table-backed image resource path. Both receive the live frame index from `obj+0x94`, not the original authored selector.
- The stage-1 visible list manager is now named end to end too: `psx_main_visible_list_add`, `psx_main_visible_list_remove`, `psx_main_visible_list_rebucket_object`, `psx_main_visible_list_refresh_from_live_chain`, `psx_main_visible_list_sort_range`, and `psx_main_visible_list_get_sorted_slice` cover membership, rebucketing, refresh, dependency/tie-break sorting, and final slice retrieval for the main world-object draw pass.
- The constructor/resource side closes the remaining resource-pointer handoff. `psx_create_image_resource_from_descriptor` is now confirmed as the builder used by both constructors: type-4 descriptors bind a single indexed image resource through `image_resource_bind_vram_slot`, while type-5 descriptors allocate and upload a multi-frame bundle through `image_bundle_load_to_vram`. Both `psx_object_create_simple_record` and `psx_object_create_compound_record` resolve the per-type art bank before any draw pass runs, store the resulting drawable resource on the object at `obj+0x10`, store the per-type variant bank at `obj+0x84`, store the per-type state-script bank at `obj+0x88`, and only then call `psx_object_select_state_script`. So the current strongest end-to-end model is now:
- authored record `type` -> per-type art/state/variant banks
- constructor seeds `obj+0x10` drawable resource plus `obj+0x88/0x84`
- `psx_object_select_state_script` seeds the initial script from `DAT_800758cc`
- runtime reselection / `psx_object_advance_state_script` updates the live script word at `obj+0x94`
- `psx_object_lookup_variant_entry` maps that live script word through `DAT_800758d4`
- the projectors and final draw submitters use the same live `obj+0x94` frame index against the drawable resource at `obj+0x10`
- The newly traced consumer side changes the export diagnosis in an important way. `psx_object_advance_state_script` does not treat the `DAT_800758d4` lookup result as a resource id or replacement frame index; it sign-extends the returned three bytes into `obj+0x30/+0x34/+0x38`, and the verified downstream consumers are all in the overlap/contact lane. `psx_object_test_overlap_3d` uses those fields as box extents against `obj+0x54/+0x58/+0x5c`, `psx_object_update_contact_block_flags` uses the same extents while updating directional block bits, and the reselection helpers read target-object `+0x30/+0x34/+0x38` as target bounds. By contrast, the visible projectors and both stage-1/stage-2 draw helpers still only use `obj+0x10` plus live `obj+0x94` for visible presentation.
- The renderer/exporter path now preserves that distinction too. The PSX cache builder decodes `DAT_800758d4` as a `count + packed signed xyz-extents` table, carries the decoded per-state tuples in the exported state layer, and writes the resolved `companionExtents` tuple onto each scene item and `mapSource` row for the chosen live state when a matching entry exists. So the viewer now keeps the newly verified bounds evidence explicitly instead of flattening it back into the old placeholder-art bucket.
- That is not yet the full remaining answer for unresolved placeholder-heavy families, but the missing piece is narrower again now. The `DAT_800758d4` bytes are no longer the best candidate for the final art table; they are acting like per-state companion extents used by collision/contact logic. So the remaining gap sits inside the family-specific live resource/frame presentation path after script reselection, not in projection, placement decoding, draw-lane discovery, or generic `DAT_800758d4` consumption.
- The latest dispatch-table trace also removes one remaining false lead for `0x0042` and `0x0049`. Their type-table slots do not point to unique family handlers; both currently resolve to the same generic descriptor used across `0x003e..0x0050`, whose callback slots are `psx_spawn_compound_record_advance_state_once`, `psx_object_refresh_main_visible_and_cleanup`, and `psx_object_release_to_free_list`. So the unresolved art rule for those types is not hiding in a dedicated per-type descriptor fork; it still appears to live later in generic object state/resource handling.
- The state/art split is therefore even sharper than the earlier constructor notes implied. `psx_object_select_state_script` only chooses the current script base from `DAT_800758cc` and stores the authored selector at `obj+0x9e`, but both `psx_object_reselect_state_from_target_vector` and `psx_type4_reselect_motion_state` can later overwrite that live script choice from runtime motion state. `psx_object_advance_state_script` then refreshes `obj+0x94` from the current script word and reruns `psx_object_lookup_variant_entry`, and that lookup indexes `DAT_800758d4` by `obj+0x94`, not by the original `obj+0x9e` selector byte.
- That changes the exporter diagnosis in a useful way. The unreadable map output is still real, but the remaining gap is narrower than before: it is not just that `DAT_800758cc` exists, but that runtime interaction and heading-state paths can overwrite the live script after spawn. Any renderer-safe state-to-art rule for unresolved families such as `0x0042` has to account for those post-spawn reselection paths instead of assuming the authored selector byte is final.
- that means the PSX level load now has four distinct evidence-backed layers instead of the earlier two-way split:
- root dispatch records at `DAT_800678f4`
- secondary `0x18`-stride records at `DAT_80067720`
- per-type runtime banks at `DAT_800758d8/d0/cc/d4`
- a dedicated `0x50` level runtime-header block at `DAT_80067794`
- plus the separately loaded compressed source at `DAT_8006767c` and its optional `DAT_8006b5d8 -> DAT_8006769c` decoded `0x3e00` state buffer
## PSX Coordinate Model From Executable
The current coordinate problem is no longer a renderer-only guess. The executable now closes the last projection step well enough to treat PSX placement as its own map-space model instead of as a PC-style direct world export.
@ -800,7 +904,7 @@ Open parts that still matter:
- this closes the final world-to-screen math, but it does not yet prove which raw `post_audio_region_01` or `post_audio_region_00` record family feeds each constructor path
- it also does not close the type/resource lookup that selects the correct bundle/frame through `DAT_800758cc/d0/d4/d8`
- palette override remains a separate unresolved control path layered on top of the now-understood projection math
- palette override is no longer wholly unresolved in the viewer path, but the `>= 0x00ac` source-base semantics and the runtime's later variant reselection still leave some color choices provisional
Immediate consequence for the next pass:

View file

@ -0,0 +1,91 @@
# `combine`, `cast`, and `fast area` in Crusader usecode
This note checks whether the suspicious inherited event labels `combine` (`slot 0x0C`) and `cast` (`slot 0x11`) are actually used by shipped Crusader usecode, and tightens the current explanation for `enterFastArea` / `leaveFastArea`.
## Summary
- `combine` survives in the inherited event vocabulary, but current shipped Crusader exports do **not** show any recovered `slot_0C` handler bodies.
- `cast` is used in Crusader, but the live behaviors look like explicit scripted activation/dispatch lanes, not a classic RPG spell system.
- `fast area` in Crusader is best read as an authored proximity/trigger region around an object or controller. Entering or leaving that region fires usecode callbacks.
## Evidence Scope
Primary evidence for this note comes from the generated pseudocode exports in `Crusader-Map-Viewer/map_renderer/site/data/usecode/*`, the local `crusader-disasm` corpus, and existing Crusader notes on trigger/controller families.
## `combine` (`slot 0x0C`)
### What is actually present
- The inherited Crusader/U8 event table still names event `C` as `combine`.
- Current generated usecode exports show **zero** `slot_0C_combine.txt` files in these shipped corpora:
- `remorse`
- `remorse-101`
- `remorse-demo`
- `regret`
- The same exported pseudocode search also found **zero** `.combine(` callsites.
### What related behavior still exists
- The disasm corpus still exposes a separate item helper named `Item::I_setQAndCombine(Item *, int16 q)`.
- In the local `MISS1EGG` disassembly, that helper is called immediately after item creation to stamp item state before the created item is positioned and added to inventory.
- That is evidence that Crusader still retained some item-side `combine`-related helper semantics in native/intrinsic code, but it is **not** evidence of a shipped usecode `slot 0x0C` event body.
### Best current read
`combine` should stay treated as a historical or compatibility label in the decompiler, not as a confirmed active Crusader event family. The name likely came forward from the inherited Ultima-style event vocabulary, while the shipped Crusader usecode corpus no longer exposes recovered slot-`0x0C` handlers.
## `cast` (`slot 0x11`)
### No Remorse usage
- In the No Remorse exported corpora, `slot_11_cast.txt` appears exactly once per build variant examined:
- `remorse`: `AVATAR/slot_11_cast.txt`
- `remorse-101`: `AVATAR/slot_11_cast.txt`
- `remorse-demo`: `AVATAR/slot_11_cast.txt`
- `AVATAR::cachein` directly spawns `AVATAR.cast(0, arg_06)`.
- The recovered `AVATAR::cast` body does not look like spellcasting. It:
- excludes the process
- waits `10` ticks
- reads the avatar map id
- selects a music track based on odd-numbered map ids (`1, 3, 5, ... 29`)
### Regret usage
- Regret exposes a wider `slot_11_cast` set: `CARD_EW`, `CARD_NS`, `CONT_FL`, `CRUSADER`, `KEYPADEW`, `KEYPADNS`, `SECUREW`, and `SECURNS`.
- Representative Regret `cast` bodies such as `CARD_NS::cast` and `KEYPADNS::cast` behave like controller/trigger activators:
- create a local sprite effect
- play SFX
- wait/suspend through helper slots
- dispatch into `TRIGGER.slot_20`
### Best current read
In Crusader, `cast` is a real event slot, but the live evidence does **not** support reading it as a general magic or spell lane. The stronger read is `explicit scripted activation/dispatch callback`, with behavior chosen per class:
- No Remorse `AVATAR.cast` is a delayed map-music dispatch hook.
- Regret `CARD_*` / `KEYPAD*` `cast` is a controller activation path that fans out into trigger logic.
## `enterFastArea` / `leaveFastArea`
### What the current evidence rules out
- The Crusader evidence does **not** fit a `fast movement zone` or generic speed-boost interpretation.
### What the current evidence supports
- `MONSTER::enterFastArea` is the verified automatic activation lane for frame-`0` `0x04D0` NPC spawners.
- `TIMER::enterFastArea` / `leaveFastArea` only arm a worker lane; the real delayed work happens later in `TIMER.slot_20`.
- `SPECIAL::enterFastArea` / `leaveFastArea` use their entry/exit callbacks as phase/control inputs rather than as locomotion modifiers.
- `BRO_BOOT::enterFastArea` and related controller families use the same event pair as proximity-trigger hooks for nearby scripted behavior.
### Best current read
A `fast area` in Crusader is best described as an **authored proximity/activation region** associated with an item or controller class. When an actor crosses into or out of that region, the corresponding usecode slot fires so the object can arm, disarm, spawn, gate, or route scripted behavior.
That explanation fits the current environmental/controller families much better than `movement speed area`, and it matches the existing evidence for spawners, timers, specials, boots, cameras, and other trigger-like objects.
## Practical naming guidance
- Keep `combine` as a compatibility/historical hint only.
- Keep `cast` as the slot label for compatibility, but document it as a scripted activation/dispatch hook in Crusader.
- Prefer `proximity/activation region` when explaining `enterFastArea` / `leaveFastArea`.

View file

@ -9,6 +9,23 @@ Current generated pseudocode lives at:
- `USECODE/EUSECODE_extracted/pseudocode/JELYHACK/slot_01_use.txt`
- `USECODE/EUSECODE_extracted/pseudocode/JELYH2/slot_01_use.txt`
## Plain-language answer
If you want the short version first:
- `JELYHACK` does not look like the object that contains the real island gameplay script.
- It looks more like a small anchor object that gives the engine a `referent` identity and a tiny shared `use` stub.
- When the player uses it, the stub appears to register a generic interaction/info value (`0x0207`) and then immediately exits.
- The richer gameplay behavior is more likely carried by nearby event-bearing records such as `REE_BOOT`, `SURCAMEW`, or `SFXTRIG`.
So in easy words: `JELYHACK` currently looks less like "the script that does the JELYHACK puzzle" and more like "the map object or anchor that other real trigger objects point at or work beside."
That also answers "what uses it" in the safest current sense:
- the referent/owner side of the runtime uses it as an anchor-like record
- the player can still `use` it, but that `use` body is tiny and generic
- the surrounding event-bearing attachment records are the more likely carriers of the actual gameplay logic
## Direct decompilation result
Current readable decompilation for `JELYHACK::use`:
@ -38,15 +55,77 @@ Byte-faithful decode of the same body:
00EE: 50 ret
```
The parser still sees bytes after `ret`, but on the current readable pass they are intentionally elided because control has already returned. Those post-`ret` bytes are identical between `JELYHACK` and `JELYH2`, so they do not currently support any class-specific behavioral claim.
That readable pseudocode is currently hiding one important detail: there is a small trailer after `ret`.
Pentagram's older disassembly shows the body like this:
```text
Func_1 (Event 1) JELYHACK::use():
0001: 5A init 00
0003: 5C symbol info offset 001Ch = "JELYHACK"
000F: 0B push 0207h
0012: 40 push dword [BP+06h]
0014: 4C push indirect 02h bytes
0016: 77 set info
0017: 78 process exclude
0018: 5B line number 219 (00DBh)
001B: 50 ret
00: 01 type=69 (i) [BP+00h] (00) 00 referent
002A: 7A end
```
So the extra bytes are not currently best read as hidden live code. The strongest reading is:
- executable body ends at `ret`
- one post-return debug/local-symbol record follows
- that record names one slot/argument as `referent`
- then the body terminates with `0x7A end`
That matters because it argues against the dramatic interpretation that we found a chopped-off second script. Right now the evidence fits "metadata trailer" much better than "cut content."
## What the trailer most likely represents
Current best explanation of the bytes after `ret`:
- they are post-return symbol/debug metadata, not a second reachable instruction stream
- they preserve the name `referent`, which matches the descriptor-side picture where `JELYHACK` only exposes a `referent` field
- they reinforce the idea that this class is an anchor/owner record, not a rich event script
In practical terms, the trailer is telling us something like: "this body knows about one meaningful value here, and that value is called `referent`."
That is useful evidence, but it is not new gameplay logic.
## Suggested readable pseudocode form
For human reading, the most honest pseudocode form is probably this:
```text
function jelyhack_use() /* entry=277 class_id=0x04D3 slot=0x01 */
{
set_info(0x0207, *(arg_06));
process_exclude();
return;
/* post-return metadata (not executable):
debug_symbol referent [BP+00h] type=0x69
*/
}
```
That presentation keeps the important distinction visible:
- the function really returns where it looks like it returns
- there is still meaningful trailer data after that return
- the trailer is metadata, not currently credible as reachable lost code
## What is directly supported
1. `JELYHACK` is not an active event hub in the same sense as `EVENT`, `NPCTRIG`, or `_BOOT` classes.
2. Its only non-zero slot is `0x01` (`use`), with `raw_event_entry_word = 0x002A`, `raw_code_offset = 0x00000001`, and body range `0x00D4..0x00FE` (`42` bytes).
3. The actual readable body before return is tiny: one `set_info(0x0207, *(arg_06))`, one `process_exclude()`, then return.
3. The actual executable body is tiny: one `set_info(0x0207, *(arg_06))`, one `process_exclude()`, then return.
4. `JELYH2` is the same script body for practical purposes. The only pre-return differences are the symbol label string and line-number metadata; control flow and active ops are otherwise the same.
5. `JELYHACK` still exposes only the `referent` field on the descriptor side, which keeps it in the `referent-anchor` category rather than the event-bearing category.
5. A post-return trailer still preserves `referent` metadata, which lines up with the descriptor-side fact that `JELYHACK` only exposes `referent`.
6. `JELYHACK` still exposes only the `referent` field on the descriptor side, which keeps it in the `referent-anchor` category rather than the event-bearing category.
## Comparison against the exported pseudocode corpus
@ -74,6 +153,13 @@ Current safest reading:
The unresolved part is the exact gameplay meaning of `set_info(0x0207, *(arg_06))`. The exported corpus shows this exact pattern in multiple unrelated classes, so `0x0207` currently looks more like a shared UI/message/interaction setup code than a JELYHACK-specific action.
That leads to the simplest current gameplay explanation:
- the object can be "used"
- the engine records a generic interaction fact about that use
- the object then gets out of the way
- anything interesting that happens next probably belongs to a neighboring event-bearing attachment, not to `JELYHACK` itself
## What it probably does not do
The current evidence argues against several stronger claims:
@ -81,6 +167,15 @@ The current evidence argues against several stronger claims:
- It is probably not the main script that drives the JELYHACK gameplay behavior by itself.
- It is probably not the actual event-bearing payload that reaches the richer runtime opcode lanes recovered around `000d:208b`, `000d:21ed`, and `000d:22bc`.
- It is probably not a unique script template specialized only for the JELYHACK object.
- The bytes after `ret` are probably not a hidden reachable second branch or a simple "return was moved upward" cut-content remnant.
If this were cut executable content, we would want to see stronger signs such as:
- a branch target that lands in the trailer
- a sensible opcode stream after `ret`
- different trailers between `JELYHACK` and `JELYH2` that might hint at divergent behavior
At the moment we have the opposite pattern: a small named metadata record, a clean `end`, and strong similarity across the twin classes.
## Relationship to JELYH2 and the surrounding island
@ -120,6 +215,7 @@ The decompiled `JELYHACK::use` body is important mainly because it supports a ne
- `JELYHACK` is not hiding a large active event script in its only live slot
- its visible `use` handler is a minimal generic stub shared with several other classes
- the bytes after `ret` look like a metadata trailer naming `referent`, not like secret live code
- the interesting gameplay semantics around the JELYHACK island are still more likely to live in neighboring event-bearing descriptors attached to the same referent context
So the current best human-readable model remains:

View file

@ -15,7 +15,7 @@ Detailed completed analysis belongs in the files under `docs/`, not in this plan
## Progress Snapshot
Latest verified batch: [docs/psx/psx.md](docs/psx/psx.md) now tightens the PSX render-side model enough that a renderer change is easier to justify. The late `LSET*.WDL` `DAT_800758d8` candidate still decodes from the embedded `+0x38` parse start and still recovers the first `111` real map-9 art bindings, while the executable pass still closes the second world-facing render lane: `FUN_80040f78` is the stage-2 queued-object projector/builder for `DAT_80078b70` / `DAT_80067472`, `FUN_80041144` consumes that queue, and `FUN_80044fec` resets it each frame. The new renderer-side bridge is intentionally narrow but now verified in live output too: because the generic raw-file `DAT_800758cc/d0/d4` export is still missing, the cache builder temporarily seeds the executable-backed `0x0050` selector map (`0..3 -> frame 0..3`), and retail map `9` now exports `type=80 state_selector=1 chosen_frame=1` instead of the old forced frame `0`. Alongside the earlier palette-source closure (`obj+0xa0` is the original authored record pointer), the `DAT_80067720` event/control-list evidence, and the `DAT_8006769c` persistent `0x3e00` substrate/state evidence, this means the remaining map-viewer gap is now better bounded: part of the missing world content likely lives in the separate stage-2 queued-object pass, while the rest still depends on restoring the generic `DAT_800758cc/d4` export path and then closing state-to-art selection for unresolved families such as `0x0042` and `0x0049`.
Latest verified batch: [docs/psx/psx.md](docs/psx/psx.md), [docs/psx/map-rendering.md](docs/psx/map-rendering.md), [docs/psx/map-viewer-plan.md](docs/psx/map-viewer-plan.md), and [docs/psx/art-binding-recovery.md](docs/psx/art-binding-recovery.md) now tighten the PSX render-side model another step in both Ghidra and the viewer exporter. The earlier `DAT_800758d4` consumer finding remains intact and is still wired into the viewer-side cache path as explicit `companionExtents` metadata, but the bigger practical change in this batch is the first measured art-binding recovery pass for the viewer exporter: the PSX cache builder now treats large zero-block `DAT_800758d8` constructor-placement bands as inherited-art candidates, first via same-map `DAT_800758cc` script-signature donors and then via a constrained nearest-donor fallback inside the current `0x003e..0x0064` family. That rebuild moved the scene set from `58,262` fallback items / `1,714` bundle-mapped items to `25,038` fallback items / `34,938` bundle-mapped items, making early representative maps such as `0`, `9`, and `43` mostly real-art while leaving `map 104` and the remaining `0x0042` / `0x0055..0x0063` constructor-placement band as the clearest unresolved outliers. The practical remaining gap is therefore narrower now: not "why are most PSX scenes placeholders" but "what executable-backed alias/resource rule explains the remaining zero-block constructor-placement families without leaning on donor heuristics."
- Overall useful decompilation progress: about 58%
- Reasonable uncertainty band: about 55% to 63%
@ -105,6 +105,7 @@ Latest verified batch: [docs/psx/psx.md](docs/psx/psx.md) now tightens the PSX r
3. Promote ledger coverage from existing verified notes before broadening into fresh executable-wide sweeps.
4. Use overlap repair only where it unlocks an active high-payoff lane.
5. Use the map-renderer/tooling lane to validate shape ids, map placements, and viewer semantics before promoting additional static-object names in Ghidra.
6. Keep the PSX lane focused on the final state/variant/art bridge now that the first post-spawn interaction/reselection cluster is named; avoid broad renderer-side heuristics that bypass those runtime paths.
## Next Resume Point
@ -121,6 +122,7 @@ Latest verified batch: [docs/psx/psx.md](docs/psx/psx.md) now tightens the PSX r
10. If the VM lane stalls, revisit `000e:ffb0` from the now-better-constrained video/audio caller windows and try to recover an adjacent non-overlapped helper before attempting broad boundary repair.
11. Continue the map-renderer cross-check lane by building one conservative shape-id/map-placement crosswalk from `shapedata_more_complete.txt`, extracted corpora, and authored scene evidence before promoting more trigger-heavy classes in NE.
12. Keep the PSX pre-alpha lane alive as a secondary target: classify the `LoadExec` callers, test whether the stale `TALK1.XA` path is still reachable, and compare the shipped `LSET1` bundles against the retail extractor outputs.
13. Continue the retail PSX state/art lane from the new art-binding recovery baseline: keep `DAT_800758d4` on the runtime-bounds side unless new family-specific evidence contradicts it, treat `map 104` plus the remaining `0x0042` / `0x0055..0x0063` zero-block constructor-placement band as the primary regression target, and trace the next family-specific callers around `psx_type4_reselect_motion_state`, `FUN_80028c94`, constructor-side resource creation, and the drawable-resource/frame submission lane until the remaining donor-based fallback logic can be replaced with an executable-backed alias/resource rule.
## Remaining Work To Reach A Reasonably Complete Decompilation State

View file

@ -1332,6 +1332,64 @@ def parse_field_tags(body: bytes, start: int) -> FieldTagParseResult | None:
return FieldTagParseResult(field_tags=field_tags, end_offset=end_offset, trailing_bytes=body[end_offset:])
def classify_post_ret_metadata(body: bytes, ops: list[dict[str, Any]]) -> dict[str, Any] | None:
last_ret_index = next((index for index in range(len(ops) - 1, -1, -1) if ops[index]["mnemonic"] == "ret"), None)
if last_ret_index is None:
return None
ret_end = ops[last_ret_index]["offset"] + (len(ops[last_ret_index]["raw_bytes"]) // 2)
if len(body) - ret_end <= 1:
return None
debug_result = parse_debug_symbols(body, ret_end)
if debug_result is not None and debug_result.end_offset == len(body):
return {
"ops": ops[:last_ret_index + 1],
"end_reason": "debug_symbols_then_end",
"unknown_tail": debug_result.trailing_bytes,
"debug_symbol_offset": ret_end,
"debug_symbols": [
{
"index": symbol.index,
"unknown1": symbol.unknown1,
"type_id": symbol.type_id,
"type_char": symbol.type_char,
"bp_offset": symbol.bp_offset,
"bp_repr": symbol.bp_repr,
"unknown3": symbol.unknown3,
"name": symbol.name,
}
for symbol in debug_result.debug_symbols
],
"field_tags": [],
"end_offset": debug_result.end_offset,
}
field_tag_result = parse_field_tags(body, ret_end)
if field_tag_result is not None and field_tag_result.end_offset == len(body):
return {
"ops": ops[:last_ret_index + 1],
"end_reason": "field_tags_then_end",
"unknown_tail": field_tag_result.trailing_bytes,
"debug_symbol_offset": None,
"debug_symbols": [],
"field_tags": [
{
"tag_id": tag.tag_id,
"bp_offset": tag.bp_offset,
"bp_repr": bp_repr(tag.bp_offset),
"value_kind": tag.value_kind,
"name": tag.name,
"tag_label": f"{tag.tag_id:02X}:{tag.bp_offset:02X}{tag.value_kind:02X}->{tag.name}",
}
for tag in field_tag_result.field_tags
],
"end_offset": field_tag_result.end_offset,
}
return None
def parse_body_ir(
event_row: dict[str, str],
layout_row: dict[str, str],
@ -1390,6 +1448,16 @@ def parse_body_ir(
and 0 <= operands["symbol_offset"] < len(body)
}
)
post_ret_metadata = classify_post_ret_metadata(body, ops)
if post_ret_metadata is not None:
ops = post_ret_metadata["ops"]
debug_symbol_offset = post_ret_metadata["debug_symbol_offset"]
debug_symbols = post_ret_metadata["debug_symbols"]
field_tags = post_ret_metadata["field_tags"]
end_reason = post_ret_metadata["end_reason"]
unknown_tail = post_ret_metadata["unknown_tail"]
offset = post_ret_metadata["end_offset"]
last_ret_index = next((index for index in range(len(ops) - 1, -1, -1) if ops[index]["mnemonic"] == "ret"), None)
if end_reason == "unknown_opcode" and last_ret_index is not None:
ret_end = ops[last_ret_index]["offset"] + (len(ops[last_ret_index]["raw_bytes"]) // 2)
@ -2973,6 +3041,17 @@ def render_pseudocode(ir: dict[str, Any], shape_catalog: ShapeCatalog | None = N
else:
lines.extend(render_partially_structured_blocks(rendered_blocks))
if ir["debug_symbols"] or ir["field_tags"]:
lines.append("")
lines.append(" /* post-return metadata (not executable):")
for symbol in ir["debug_symbols"]:
lines.append(
f" debug_symbol {sanitize_identifier(symbol['name'])} {symbol['bp_repr']} type=0x{symbol['type_id']:02X} unk1=0x{symbol['unknown1']:02X} unk3=0x{symbol['unknown3']:02X}"
)
for tag in ir["field_tags"]:
lines.append(f" field_tag {tag['tag_label']} ({tag['bp_repr']})")
lines.append(" */")
lines.append("}")
return apply_shape_catalog_to_pseudocode("\n".join(lines) + "\n", shape_catalog)

View file

@ -0,0 +1,76 @@
from __future__ import annotations
import argparse
import json
from pathlib import Path
from psx_extract_wdl import extract, summarize
def find_wdl_files(root: Path) -> list[Path]:
return sorted(
path
for path in root.rglob("*")
if path.is_file() and path.suffix.lower() == ".wdl"
)
def build_index_entry(root: Path, path: Path, summary: dict[str, object]) -> dict[str, object]:
relative_path = path.relative_to(root)
sprite_bundles = summary.get("sprite_bundles", [])
return {
"source": relative_path.as_posix(),
"stem": path.stem,
"kind": summary["kind"],
"region_count": len(summary.get("regions", [])),
"tim_count": len(summary.get("tim_hits", [])),
"sprite_bundle_count": len(sprite_bundles),
}
def run_batch(input_root: Path, output_root: Path) -> list[dict[str, object]]:
files = find_wdl_files(input_root)
if not files:
raise SystemExit(f"no .WDL files found under {input_root}")
index: list[dict[str, object]] = []
for path in files:
relative_parent = path.relative_to(input_root).parent
target_root = output_root / relative_parent
summary = extract(path, target_root)
log_path = target_root / path.stem / "summary.txt"
log_path.write_text(summarize(path, summary) + "\n", encoding="ascii")
index.append(build_index_entry(input_root, path, summary))
index_path = output_root / "index.json"
index_path.parent.mkdir(parents=True, exist_ok=True)
index_path.write_text(json.dumps(index, indent=2), encoding="ascii")
return index
def main() -> int:
parser = argparse.ArgumentParser(description="Extract all Crusader PSX WDL files under a directory tree.")
parser.add_argument("input_root", type=Path, help="Root directory to scan for .WDL files")
parser.add_argument(
"--output",
type=Path,
default=Path("out") / "psx_wdl_disc",
help="Directory where extracted per-file outputs are written",
)
args = parser.parse_args()
index = run_batch(args.input_root, args.output)
print(f"input_root: {args.input_root}")
print(f"output_root: {args.output}")
print(f"wdl_files: {len(index)}")
for entry in index:
print(
f" {entry['source']}: kind={entry['kind']} tims={entry['tim_count']} "
f"sprite_bundles={entry['sprite_bundle_count']}"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -3,9 +3,11 @@ from __future__ import annotations
import unittest
from tools.poc_crusader_usecode_parser import (
classify_post_ret_metadata,
format_target_event_reference,
get_intrinsic_hints,
intrinsic_display_name,
render_pseudocode,
render_partially_structured_blocks,
render_structured_pseudocode,
try_decode_loop_selector,
@ -14,6 +16,44 @@ from tools.poc_crusader_usecode_parser import (
class UsecodeStructuringTests(unittest.TestCase):
def test_post_ret_debug_symbols_are_classified_as_metadata(self) -> None:
body = bytes([0x50, 0x01, 0x01, 0x69, 0x00, 0x00]) + b"referent\x00" + bytes([0x7A])
ops = [{"mnemonic": "ret", "offset": 0, "raw_bytes": "50", "operands": {}}]
metadata = classify_post_ret_metadata(body, ops)
self.assertIsNotNone(metadata)
self.assertEqual(metadata["end_reason"], "debug_symbols_then_end")
self.assertEqual(metadata["debug_symbol_offset"], 1)
self.assertEqual(len(metadata["debug_symbols"]), 1)
self.assertEqual(metadata["debug_symbols"][0]["name"], "referent")
def test_render_pseudocode_includes_post_ret_metadata_comment(self) -> None:
ir = {
"class": {"class_name": "JELYHACK", "entry_index": 277, "class_id": 0x04D3},
"event": {"event_name_hint": "use", "slot": 0x01},
"body": {"end_reason": "debug_symbols_then_end", "decoded_op_count": 1},
"ops": [{"mnemonic": "ret", "offset": 0, "absolute_body_offset": 0, "raw_bytes": "50", "operands": {}}],
"debug_symbols": [
{
"index": 0,
"unknown1": 0x01,
"type_id": 0x69,
"type_char": "i",
"bp_offset": 0x00,
"bp_repr": "[BP+00h]",
"unknown3": 0x00,
"name": "referent",
}
],
"field_tags": [],
}
rendered = render_pseudocode(ir)
self.assertIn("post-return metadata (not executable)", rendered)
self.assertIn("debug_symbol referent [BP+00h] type=0x69", rendered)
def test_alarmbox_style_forward_flow_renders_without_block_labels(self) -> None:
blocks = [
("entry", ["set_info(0x0211, *(arg_06));", "process_exclude();", "if var goto block_0330;"]),