36 KiB
Entity VM Runtime And Owner-Resource Layout
Purpose
This note gathers the current class-lift-relevant structure for the VM runtime lane into one place.
It focuses on four connected objects:
EntityVmRuntimeEntityVmOwnerResourceEntityVmContext- the slot/value helpers that connect gameplay entities to owner-loaded VM source data
The goal is not full opcode recovery. The goal is to make later class authoring and C++ skeleton emission faster by freezing the current ownership model.
High-Level Ownership Model
Current best model from docs/raw-0008-000c.md and docs/raw-000a-000d.md:
- startup path resolves a configured EUSECODE root/path
entity_vm_runtime_createallocates the main runtime body- runtime constructor attaches one file-backed helper created by
entity_vm_runtime_owner_resource_create - gameplay entities map to slot indices through
entity_vm_slot_index_from_entity - masked-create helpers test owner-side capability bits and then build per-entity or per-slot
EntityVmContextobjects - contexts seed their local stream/value state from owner-loaded source rows and runtime slot caches
EntityVmRuntime
Strong anchors:
000d:44df entity_vm_runtime_init_from_path_if_configured000d:4c99 entity_vm_runtime_create000d:4d36 entity_vm_runtime_init_slots000d:4d75 entity_vm_runtime_release_slots000d:4e01 entity_vm_runtime_destroy
Current strongest structural claims:
- runtime body is the global owner behind
0x6611/0x6613 - front region behaves like a
0x80entry slot table with stride0x26 - tail region around
+0x1300..+0x1318holds runtime budget/default metadata plus the owner-resource helper pointer - helper attachment lives at
+0x1315/+0x1317
Live runtime-helper classification added during the 2026-04-05 MCP-upgrade pass:
1420:167c Remorse::EntityVmRuntime::AcquireSlotForEntity- scans the
0x80-entry slot table for an existing entity match or the first free slot - can evict the currently selected slot via the owner-row iterator when the runtime needs to recycle one
- scans the
1420:1866 Remorse::EntityVmRuntime::InitSlotOwnerBuffers- reads owner-resource metadata for one slot id
- allocates the two slot-local working buffers later stored at
+0x1e/+0x20and+0x22/+0x24 - seeds the sentinel-filled chunk-state array used by later lazy chunk loads
1420:19fd Remorse::EntityVmRuntime::EnsureSlotChunkLoaded- lazily materializes one indexed owner chunk for a slot into runtime memory
- marks the chunk present through the slot-local state arrays and updates the runtime-wide budget counters
1420:1f24 entity_vm_runtime_apply_to_matching_owner_rows- runtime-owned iterator over the owner list, used both for broad cleanup and for filtered slot/category work
1420:2040 entity_vm_slot_entry_create_or_clear- allocates or clears one
0x26-byte slot entry record - gives the current strongest live evidence for the stable slot-entry size and the buffer/cache lane at
+0x1e..+0x24
- allocates or clears one
Current safe class role:
- long-lived VM root object that owns slot state, owner resource, category-base words, and runtime-wide value budgets
EntityVmOwnerResource
Strong anchors:
000d:7000 entity_vm_runtime_owner_resource_create000d:70fd entity_vm_runtime_owner_resource_destroy
Best current helper shape:
- compact
0x14-byte file-backed wrapper object - first
0x08bytes are the embedded file-handle base initialized byFile_MallocOrInit8Bytes - helper vtable/dispatch pointer lives at
+0x08 - materialized owner-row table far pointer lives at
+0x0c - helper vtable
+0x04acts as the current best size/query callback - helper vtable
+0x0cmaterializes owner data; the thin wrapper now asserts if the first output byte is0xff
Current safest interpretation:
- this is the most bounded class-lift target in the VM lane
- it looks like a real helper object with a compact stable layout and a clear owner relationship to
EntityVmRuntime - the earlier
+0x14/+0x18count-and-table claims belong to the downstream materialized loader data, not to the outer0x14-byte wrapper itself
seg070 loader contract
The paired loops rooted at raw windows 0009:67b6 and 0009:6916 are current best evidence that the helper is file-backed rather than a pure in-memory descriptor copier.
Verified behavior already captured in the main notes:
- iterate helper-owned count at
+0x14 - index path/id tables at
+0x10and+0x18 - build formatted paths with two distinct format strings
- open, seek/read, close, and free loop-local buffers through the DOS/file helper lane
Current caution:
- exact per-family record schema is still open, so the helper should be modeled as a loader/index object first, not as a final descriptor-schema class.
EntityVmContext
Strong anchors:
000d:463a entity_vm_context_try_create_masked_for_entity000d:46ec entity_vm_context_create_from_slot_index000d:48b6 entity_vm_context_free_buffer000d:48da entity_vm_context_sync_global_value_and_dispatch000d:4962 entity_vm_context_destroy000d:498f entity_vm_context_save000d:4a78 entity_vm_context_load
Current safe role:
- per-entity or per-slot execution/context object built from runtime slot state plus owner-loaded source data
Current layout claims that matter for class lifting:
+0x32stores slot index+0x34stores the additive offset word used by theslot_load_value_plus_offsetlane+0x36embeds the mini-VM/state object+0xd6/+0xd8hold the seeded source/control stream lane+0x102is the backward-growing local payload/buffer lane+0x10c/+0x10estore a derived low/high pair reused by save/load+0x117/+0x119cache the owner-linked source pair+0x123behaves as a busy or active flag in the sync/dispatch path
Current caution:
- context dispatch semantics are still active work, so this object should be modeled around lifecycle and data ownership first, not around final method names for every opcode-facing helper.
Gameplay Entity To VM Bridge
Slot selection
entity_vm_slot_index_from_entity (000d:45c5) is the key bridge from gameplay entity identity into the VM lane.
Current safest summary:
- it does not choose
NPCTRIGversusEVENTdirectly - it maps gameplay entities into category spans using runtime base words such as
0x8c7c/0x8c7e/0x8c80 - owner-row capability bits and later slot-value materialization do the next stage of filtering
Masked-create helpers
The masked-create family is already class-lift relevant even before final event labels are known.
What is safe now:
- the hub at
000d:463achecks runtime-disable state and owner-side mask bits - low-slot and high-slot wrappers differ by slot id, mask, and whether they pass an extra signed/additive word
- wrappers like slot
0x0a/0x0bare offset-specialized context creators, not separate selector universes
This means future Ghidra or C++ modeling should treat them as helper factories around EntityVmContext, not as methods on unrelated gameplay classes.
Best Class-Lift Targets In This Lane
EntityVmOwnerResourceEntityVmRuntimeEntityVmContext
Why this order:
- owner-resource helper is compact and structurally bounded
- runtime has clear ownership over the helper and slot table
- context has the richest semantics but also the most unresolved dispatcher behavior
Live Ghidra Authoring Status
Verified first batch landed in the live CRUSADER.EXE session on 2026-04-05.
- Created namespace
Remorseand class ownersRemorse::EntityVmOwnerResource,Remorse::EntityVmRuntime, andRemorse::EntityVmContext. - Created provisional datatype
/Remorse/EntityVmOwnerResourcewith current stable anchors:- first
0x08bytes reserved for the embedded file base +0x08 helper_vtable+0x0c owner_row_table
- first
- Created provisional datatype
/Remorse/EntityVmRuntimewith size0x1319and only the currently stable tail anchors around the owner-resource attachment lane. - Created provisional datatype
/Remorse/EntityVmSlotEntrywith size0x26and only the currently stable tail buffer fields named:+0x1e owner_buffer_offset+0x20 owner_buffer_segment+0x22 chunk_state_offset+0x24 chunk_state_segment
- Moved
1430:0000underRemorse::EntityVmOwnerResource::Create. - Moved
1430:00fdunderRemorse::EntityVmOwnerResource::Destroy. - Moved
1420:1499underRemorse::EntityVmRuntime::Create. - Moved
1420:1536underRemorse::EntityVmRuntime::InitSlots. - Moved
1420:1575underRemorse::EntityVmRuntime::ReleaseSlots. - Moved
1420:1601underRemorse::EntityVmRuntime::Destroy. - Moved
1420:167cunderRemorse::EntityVmRuntime::AcquireSlotForEntityafter live decompilation showed a0x80-entry scan over the runtime slot table with free-slot fallback and eviction of the currently selected slot. - Moved
1420:1866underRemorse::EntityVmRuntime::InitSlotOwnerBuffersafter live decompilation showed owner-resource reads plus the two slot-local buffer allocations and initial sentinel fill. - Moved
1420:19fdunderRemorse::EntityVmRuntime::EnsureSlotChunkLoadedafter live decompilation showed per-slot chunk materialization and cache-presence marking. - Renamed
1420:1ccatoentity_vm_runtime_debug_dump_slot_memoryafter live decompilation showed a debug-gated walk of the runtime slot list with slot memory usage output. - Renamed
1420:1f24toentity_vm_runtime_apply_to_matching_owner_rowsafter live decompilation showed filtered iteration over the runtime owner-row list. - Renamed
1420:2040toentity_vm_slot_entry_create_or_clearafter live decompilation showed allocation and zeroing of one0x26-byte slot record. - Added short decompiler comments at
1430:0000and1430:00fdto preserve the raw000d:7000/000d:70fdprovenance. - Added short decompiler comments at
1420:1499,1420:1536,1420:1575, and1420:1601to preserve the runtime-lifecycle provenance and current layout claims. - Added short decompiler comments at
1420:167c,1420:1866,1420:19fd,1420:1cca,1420:1f24, and1420:2040so the slot-table evidence stays visible in the live database. - Repaired the decompiler health of
1420:1499 Remorse::EntityVmRuntime::Createafter the delete/recreate cycle left it throwingLow-level Error: Symbol $$undef00000006 extends beyond the end of the address space; the root cause was the shared allocator helper at1000:42e2, whose pointer-return signature decompiled with a hidden__return_storage_ptr__and poisoned the caller stack model until it was normalized to an explicitdwordreturn plus explicit stack storage. - Verified second batch landed in the live
CRUSADER.EXEsession on 2026-04-06. - Moved
1420:0eecunderRemorse::EntityVmContext::CreateFromSlotIndex. - Moved
1420:10b6underRemorse::EntityVmContext::FreeBuffer. - Moved
1420:10daunderRemorse::EntityVmContext::SyncGlobalValueAndDispatch. - Moved
1420:1162underRemorse::EntityVmContext::Destroy. - Moved
1420:118funderRemorse::EntityVmContext::Save. - Moved
1420:1278underRemorse::EntityVmContext::Load. - Added short decompiler comments at
1420:0eec,1420:10b6,1420:10da,1420:1162,1420:118f, and1420:1278to preserve the raw000d:46ec,000d:48b6,000d:48da,000d:4962,000d:498f, and000d:4a78provenance after the class-owner move. - Verified third batch landed through local PyGhidra on 2026-04-06 after the live
run_write_script(...)route still returned404 No context found for requestagainst the active GUI session. - Created provisional datatype
/Remorse/EntityVmContextwith size0x124and the currently safest stable anchors:+0x32 slot_index+0x34 value_add_offset+0xd6/+0xd8 source_stream_offset/source_stream_segment+0x10c/+0x10e derived_pair_lo/derived_pair_hi+0x117/+0x119 owner_source_offset/owner_source_segment+0x123 busy_flag
- Updated
1420:2040 entity_vm_slot_entry_create_or_cleartoEntityVmSlotEntry * __cdecl16far entity_vm_slot_entry_create_or_clear(EntityVmSlotEntry * slot_entry)so the slot-record helper no longer falls back to anonymousbyte *parameters. - Updated
1420:167c Remorse::EntityVmRuntime::AcquireSlotForEntityto returnEntityVmSlotEntry *, while leaving the thirdparam_3entity-like pointer conservative until caller-side role recovery is tighter. - Updated
1420:1866 Remorse::EntityVmRuntime::InitSlotOwnerBuffersso the third parameter is nowEntityVmSlotEntry * slot_entry. - Updated
1420:1536 Remorse::EntityVmRuntime::InitSlotstovoid __cdecl16far InitSlots(EntityVmRuntime * this). - Updated
1420:1575 Remorse::EntityVmRuntime::ReleaseSlotstovoid __cdecl16far ReleaseSlots(EntityVmRuntime * this). - Tried the same typed-
thiscollapse on1420:1499 Remorse::EntityVmRuntime::Create, but the pointer-sizedthisvariant reintroduced a hidden__return_storage_ptr__; the function was restored immediately to the known-good split-word custom-storage signaturedword __cdecl16far Create(word this, word runtime_segment, word owner_type, word owner_id). - Verified fourth live batch landed on 2026-04-06.
- Updated the
local_adecompiler local in1420:19fd Remorse::EntityVmRuntime::EnsureSlotChunkLoadedtoEntityVmSlotEntry *, so the slot-entry cache path now renders the stableowner_buffer_offsetandchunk_state_offsetfields directly instead of anonymousundefined4pairs. - Retried the
EntityVmContextlifecycle typing pass through live MCP.apply_class_layoutdry-run for/Remorse/EntityVmContextnow returns a normal structured preview instead of the earlier null failure, but the real apply path still fails withFailed to apply this type: Storage size does not match data type size: 2, and direct liveset_function_this_typecalls onFreeBuffer,SyncGlobalValueAndDispatch,Destroy,Save, andLoadhit the same storage-size mismatch. - Retried live
run_write_script(...)with and without explicit target selectors onCRUSADER.EXE, but the route still returned404 No context found for request, so there is still no live in-session fallback for forcing the dynamic-storage rewrite on the context methods. - Verified fifth batch landed through local PyGhidra on 2026-04-06 after the live write-side routes still blocked the context pass.
- Updated
1420:0eec Remorse::EntityVmContext::CreateFromSlotIndexso the first parameter is nowEntityVmContext * thiswhile preserving the existingUsecodeProcess *return type until the constructor/factory semantics are tighter. - Updated
1420:10b6 FreeBuffer,1420:10da SyncGlobalValueAndDispatch,1420:1162 Destroy,1420:118f Save, and1420:1278 Loadso the first parameter is nowEntityVmContext * thisinstead ofUsecodeProcess *. - That local fallback confirms the newer dynamic-storage rewrite is sufficient for the context lifecycle cluster when applied outside the live GUI session. The remaining MCP issue is deployment/session parity, not whether the typing model itself works.
- Verified sixth analysis-only live batch on 2026-04-06.
- Exercised the new storage-aware prototype route against the two known 16-bit repair cases (
1000:42e2and1420:1499) through the active MCP session. The checked-in source has the new route wiring, but the live GUI plugin still answered with legacy behavior:/set_function_prototype_storagereturned the oldset_function_prototypefailure body, and/set_storage_aware_prototypereturned404 No context found for request. That confirms the remaining issue is live deployment parity, not endpoint design. - Rechecked the direct callers of
CreateFromSlotIndex:Usecode_ItemCallEventplus twoInterpreter_NextUsecodeOpcall sites. TheUsecode_ItemCallEventpath explicitly callsCreateFromSlotIndex((EntityVmContext *)0x0,0,...)as an allocate-and-return factory, and the current caller-side uses immediately consume only baseProcess-style fields such asprocidand termination flags. The two interpreter call sites likewise just store the returned far pointer inDX:AXscratch pairs before later base-process handling. - That caller evidence is enough to keep the current conservative return type for now:
CreateFromSlotIndexis clearly manufacturing anEntityVmContext, but promoting the return toEntityVmContext *before the inheritance/base-process datatype story is explicit would probably make current caller decompilation less clear rather than more clear. - Verified seventh live batch landed on 2026-04-06 after the refreshed MCP build came up.
- Re-exercised
set_function_prototype_storage(...)in-session on the two known 16-bit repair cases. The route now reaches the real storage-aware implementation and can preserve the explicitAX:DXreturn storage in-session, but two live issues remain: stack offsets at10and above currently need0xprefixes to avoid landing at0x10/0x12/0x14/0x16, andcalling_convention='__cdecl16far'still normalizes the repaired functions to plain__cdecl. - Updated
/Remorse/EntityVmSlotEntryone step deeper from theInitSlotOwnerBuffersandEnsureSlotChunkLoadedevidence:+0x00 match_key_farptr+0x0a owner_chunk_count+0x12 owner_data_base- retained the earlier
+0x1e..+0x24owner-buffer and chunk-state pointer pairs
- Updated local variable typing so
AcquireSlotForEntitynow carriesEntityVmSlotEntry *locals for the current slot cursor/free-slot candidate lane, andInitSlotOwnerBuffersnow carries anEntityVmSlotEntry *local for the owner-metadata scratch object. - The decompiler payoff is immediate:
InitSlotOwnerBuffersnow showsowner_chunk_count,owner_buffer_*, andchunk_state_*directly, andEnsureSlotChunkLoadednow showsowner_data_basewhere the slot metadata seeds the later owner-data window. - Tried the stronger storage-aware
Create(this: /Remorse/EntityVmRuntime * @ stack:0x4:4, ...)model through the new endpoint, but it still fails withStorage size does not match data type size: 2. That makes the remaining blocker more precise again: the live MCP route is now good enough to express the desired 4-byte storage, but the currentEntityVmRuntime *datatype in this 16-bit NE session still resolves to a 2-byte pointer type. - Verified eighth live batch landed on 2026-04-06.
- Reloaded the live plugin and re-verified
set_function_prototype_storage(...)on the two known 16-bit proof cases. The route now works in-session and preserves explicitAX:DXreturn storage cleanly, butcalling_convention='__cdecl16far'still normalizes both1000:42e2and1420:1499to plain__cdecl. - Renamed
1420:1d72toentity_vm_runtime_get_slot_chunk_ptr_at_offsetafter confirming fromCreateFromSlotIndex,Load, andFUN_1418_035fthat it is just a wrapper overEnsureSlotChunkLoadedplus a caller-supplied offset. - Renamed
1420:1d8dtoentity_vm_runtime_release_slot_chunk_refafter confirming from theInterpreter_NextUsecodeOpcaller that it decrements one live chunk-state refcount and asserts if the chunk was not retained. - Renamed
1420:1e17toentity_vm_runtime_try_unload_slot_chunkafter confirming fromentity_vm_runtime_apply_to_matching_owner_rowsthat it only unloads a chunk when the chunk-state count has reached zero, restoring the owner-buffer entry and freeing runtime budget during cleanup/eviction. - Added short decompiler comments to those three helpers so the slot-entry ownership story stays visible in the live database.
- Verified ninth live batch landed on 2026-04-06.
- Created provisional datatype
/Remorse/EntityVmLoadedChunkRecordwith the current stable cleanup/iterator anchors:+0x06 next_offset+0x08 next_segment+0x0e saved_chunk_offset+0x10 saved_chunk_segment+0x12 slot_index+0x14 chunk_index
- Updated
1420:1e17 entity_vm_runtime_try_unload_slot_chunkso the second parameter is nowEntityVmLoadedChunkRecord * loaded_chunk_record, and then tightened the return tobyte __cdecl16farwith explicitALstorage after caller disassembly at1420:1f50and1420:1fc1showed both call sites consume onlyAL. - Updated the iterator local
uStack_6in1420:1f24 entity_vm_runtime_apply_to_matching_owner_rowstoEntityVmLoadedChunkRecord *, so the owner-row cleanup walk now rendersnext_*,slot_index, andchunk_indexdirectly instead of anonymous stack-pair traffic. - Confirmed the interpreter-side release helper caller at
1418:3330pushes the live chunk record'sslot_index/chunk_indexpair fromES:[BX+0x32]/ES:[BX+0x34]together with the runtime far pointer before callingentity_vm_runtime_release_slot_chunk_ref, which makes the loaded-chunk record a real shared runtime record rather than a one-off cleanup scratch blob. - Verified tenth live batch landed on 2026-04-06.
- Renamed local helper
1418:003ctointerpreter_pop_saved_farptrafter confirming from its only caller inInterpreter_NextUsecodeOpthat it decrements a saved-farptr stack count at+0x80and returns the far pointer stored at the new top entry. - Added short decompiler comments at
1418:003cand1418:3330so the interpreter-side release/restore lane stays visible without overcommitting the restored far pointer to a stronger semantic than the current evidence supports. - Verified eleventh live batch landed on 2026-04-06.
- Created class owner
Remorse::EntityVmSlotEntryin the live database and moved1420:2040under it asCreateOrClear. - Tightened
Remorse::EntityVmSlotEntry::CreateOrClearso the single parameter is now namedthisand the explicit far return storage is restored toAXfor theEntityVmSlotEntry *result. - Moved the previously global runtime cleanup helpers under
Remorse::EntityVmRuntimeas real methods:1420:1d72->GetSlotChunkPtrAtOffset1420:1d8d->ReleaseSlotChunkRef1420:1cca->DebugDumpSlotMemory1420:1e17->TryUnloadSlotChunk1420:1f24->ApplyToMatchingOwnerRows
- Tightened the
ReleaseSlotChunkRefparameter names toruntime_farptr,slot_index, andchunk_index, and renamed theDebugDumpSlotMemoryfar-pointer argument toruntime_farptrso the runtime-owned chunk/refcount lane reads more like method code than detached helper code. - Verified twelfth live batch landed on 2026-04-06.
- Tightened
Remorse::EntityVmRuntime::GetSlotChunkPtrAtOffsettodword __stdcall16far GetSlotChunkPtrAtOffset(dword runtime_farptr, int slot_index, int chunk_index, dword intra_chunk_offset)after re-checking theCreateFromSlotIndexandLoadcallers. The current best read is: load/ensure one slot chunk through the runtime, then add a caller-supplied intra-chunk offset pair to the returned far pointer. - Tightened
Remorse::EntityVmRuntime::ApplyToMatchingOwnerRowstobyte __cdecl16far ApplyToMatchingOwnerRows(dword runtime_farptr, int slot_index_filter, int chunk_index_filter)after re-checking theAcquireSlotForEntityandEnsureSlotChunkLoadedcallers. The current best read is: iterate the runtime-owned loaded-chunk list either broadly (-1/-1) or for one current slot/chunk pair. - Restored explicit return storage after the storage-aware retype pass so
GetSlotChunkPtrAtOffsetstill returns its far pointer inDX:AXandApplyToMatchingOwnerRowsstill returns its boolean result inAL. - Verified thirteenth live batch landed on 2026-04-06.
- Lifted the grouped runtime methods from split-word
runtime_farptrparameters to explicit 4-byteEntityVmRuntime * thisstorage using/Remorse/EntityVmRuntime *32in-session. The live signatures now read as real methods for:CreateInitSlotsReleaseSlotsDebugDumpSlotMemoryReleaseSlotChunkRefGetSlotChunkPtrAtOffsetTryUnloadSlotChunkApplyToMatchingOwnerRowsEnsureSlotChunkLoaded
Remorse::EntityVmRuntime::Createis the biggest change in that batch: it no longer needs the old split-word placeholder form and now holdsdword __cdecl16far Create(EntityVmRuntime * this, word owner_type, word owner_id)with the originalAX:DXreturn preserved.EnsureSlotChunkLoadednow also carries the clearerEntityVmRuntime * this, short slot_index, short chunk_indexsignature with the original far-pointer return preserved inDX:AX.AcquireSlotForEntityandInitSlotOwnerBuffersare now fully over that hurdle too:AcquireSlotForEntityreturnsEntityVmSlotEntry *32inDX:AX, andInitSlotOwnerBuffersnow carriesEntityVmSlotEntry *32 slot_entryas its third parameter.- Verified fourteenth live batch landed on 2026-04-06.
- Finished the remaining straightforward VM pointer cleanup outside the hottest runtime helper cluster:
1430:0000 Remorse::EntityVmOwnerResource::Create->byte __cdecl16far Create(EntityVmOwnerResource * this, dword owner_resource_spec)1430:00fd Remorse::EntityVmOwnerResource::Destroy->Destroy(EntityVmOwnerResource * this, uint destroy_flags)1420:1601 Remorse::EntityVmRuntime::Destroy->byte __cdecl16far Destroy(EntityVmRuntime * this, word destroy_flags)1420:10b6/10da/1162/118f/1278 Remorse::EntityVmContext::{FreeBuffer, SyncGlobalValueAndDispatch, Destroy, Save, Load}now all carry explicitEntityVmContext *32 this
- That leaves
CreateFromSlotIndexas the one clearly still-complex VM signature in this family cluster: the body still shows a farthis, but the remaining argument pack needs a dedicated caller-side recovery pass rather than another pointer-only rewrite. - Verified sixteenth live batch landed on 2026-04-08.
- Re-read
1430:0000 Remorse::EntityVmOwnerResource::Createand1430:00fd Destroyagainst the live decompiler and corrected the outer-wrapper model: the object itself is only0x14bytes, with the first0x08bytes acting as the embedded file base, the helper vtable pointer at+0x08, and the materialized owner-row table far pointer at+0x0c. - Moved
1430:014cunderRemorse::EntityVmOwnerResourceasMaterializeCheckedafter confirming fromInitSlotOwnerBuffersandEnsureSlotChunkLoadedthat it is the thin wrapper over helper vtable slot+0x0cused to materialize owner data and assert on0xffsentinel failure. - Moved
1430:0195underRemorse::EntityVmOwnerResourceasQueryMaterializationSizeafter confirming it is the adjacent thin wrapper over helper vtable slot+0x04, with the current safest read still being the same size/query callback already seen from theCreatepath. - Added short decompiler comments at
1430:014cand1430:0195so the callback-slot evidence remains visible in-session without pretending the full seg069/070 helper contract is closed. - Verified fifteenth live batch landed on 2026-04-06.
- Recovered the mixed caller pack on
1420:0eec Remorse::EntityVmContext::CreateFromSlotIndexfar enough to replace the old anonymous split arguments with caller-backed names:dword owner_source_farptrdword pitemno_farptrword mode_flagsword slot_indexword value_add_offsetword intra_chunk_offsetdword ucparam_farptruint ucparamsize
- Restored explicit far return storage on
CreateFromSlotIndextoAX:DXafter the storage-aware apply briefly dropped it. - The same live pass also made the remaining endpoint weakness more concrete again: once the caller-backed custom-storage pack is applied, the endpoint still textualizes the function as plain
dword __cdeclinstead of preserving the earlier higher-levelUsecodeProcess */__stdcall16farsurface, even though the decompiler now keeps the correct argument boundaries and the return really is back inAX:DX. - Current best caller-backed read for
CreateFromSlotIndexis now narrower and more useful than the old placeholder form:owner_source_farptris a real far-pointer input that is persisted to context+0x11b/+0x11ducparam_farptris a real far-pointer input copied into the backward-growing buffer at+0x102slot_index,value_add_offset, andintra_chunk_offsetare distinct scalar inputs rather than one collapsed anonymous pack- the conservative semantic story is still
factory/setup bridge that returns a far process/context pointer, notfinal inheritance-clean constructor signature
Current live datatype state:
/Remorse/EntityVmOwnerResourceis the cleanest landed class in this lane so far.- Its current stable outer-wrapper model is now tighter too: embedded file base at
+0x00..+0x07, helper vtable at+0x08, and materialized owner-row table far pointer at+0x0c. /Remorse/EntityVmRuntimecurrently only freezes the stable tail fields and helper pointer, not the full slot-entry schema./Remorse/EntityVmSlotEntrynow exists both as a bounded helper datatype and as a liveRemorseclass owner. Its current authored surface is intentionally small: one constructor/clear method plus the stablematch_key_farptr,owner_chunk_count,owner_data_base, and owner-buffer / chunk-state pointer anchors./Remorse/EntityVmLoadedChunkRecordnow exists as the shared cleanup/iteration record for the chunk-release and conditional-unload lane, with the currently stable next-link, saved-owner-buffer, slot-index, and chunk-index fields named./Remorse/EntityVmContextnow exists and matches the current owned lifecycle cluster, but it still only records the safest field anchors rather than the full embedded mini-VM layout.apply_class_layoutsucceeded forRemorse::EntityVmOwnerResourcebut failed forRemorse::EntityVmRuntimewhen the binder tried to apply athistype, even though plain ownership moves worked.- The old
apply_class_layoutdry-run null failure forRemorse::EntityVmContextno longer reproduces on the current live server, but the actual write-sidethistyping path is still effectively old-build behavior: the real apply and directset_function_this_typecalls still fail on the existingUsecodeProcess *lifecycle signatures withStorage size does not match data type size: 2. - The
EntityVmContextlifecycle signatures are now locally repaired through PyGhidra:CreateFromSlotIndexplusFreeBuffer/SyncGlobalValueAndDispatch/Destroy/Save/Loadall carryEntityVmContext * thisas their first parameter. CreateFromSlotIndexshould still keep a conservative semantic return in the notes for the moment. The active live endpoint now textualizes it asdword __cdeclafter the caller-packed custom-storage cleanup, but the allocate-and-return behavior is clear, the real return storage is back inAX:DX, and the known callers still consume the result through base-process fields rather than through an inheritance-awareEntityVmContext : UsecodeProcessdatatype model.- The runtime lane is now split more accurately:
InitSlotsandReleaseSlotscan carry a directEntityVmRuntime * this, whileCreatestill needs the split-word custom-storage form to avoid hidden return-storage breakage. - The runtime lane is grouped more accurately too: the chunk-access, chunk-ref release, debug-dump, conditional-unload, and owner-row iterator helpers now sit under
Remorse::EntityVmRuntimeinstead of remaining global free functions. - The runtime lane is also typed more accurately now: the chunk accessor is no longer a five-word anonymous wrapper, and the owner-row iterator no longer pretends its runtime pointer is two independent split-word parameters.
- The authored VM lane is now much closer to a real class surface than a namespace grouping:
EntityVmRuntime,EntityVmOwnerResource,EntityVmContext,EntityVmSlotEntry, and the helperEntityVmLoadedChunkRecordall now participate in a mostly far-pointer-correct live type model, withCreateFromSlotIndexas the main remaining signature outlier. - The slot-entry model is tighter again: beyond the earlier
owner_buffer_*andchunk_state_*tails, the datatype now also exposesowner_chunk_countandowner_data_base, which makes the allocator/count path inInitSlotOwnerBuffersand the owner-data window math inEnsureSlotChunkLoadedread as object state rather than anonymous offset pairs. - The adjacent helper map is tighter too: the slot-entry consumer side now has one pointer-plus-offset accessor, one chunk-ref release helper, one conditional-unload helper, and one named loaded-chunk iterator record instead of a mix of anonymous
1420:placeholders and anonymous stack-pair scratch state.
Current scope of that batch stayed intentionally conservative:
- no final source-format schema naming for the owner rows
- no speculative promotion of additional seg069/070 helper callbacks beyond
QueryMaterializationSizeandMaterializeChecked - no speculative promotion of the masked-create wrapper ladder into
EntityVmContextmethods - no speculative typing yet for the entity-like pointer parameter on
AcquireSlotForEntity - no attempt yet to force slot-entry field names beyond the stable
+0x1e..+0x24tail region and the current conservative helper prototypes
Best immediate next moves after this landed:
- inspect
EnsureSlotChunkLoadedand adjacent1420:helpers again now thatAcquireSlotForEntityreturnsEntityVmSlotEntry *, and push the slot-entry type one step deeper only where the resulting local/object read is genuinely clearer - decide whether
CreateFromSlotIndexcan safely promote its return type fromUsecodeProcess *toEntityVmContext *, or whether it should stay a factory-style bridge that only typesthis - if the context/base-process inheritance story becomes explicit in datatypes, revisit
CreateFromSlotIndexreturn typing then; until that point, keep the currentUsecodeProcess *return even though the body itself clearly builds anEntityVmContext - decide whether
match_key_farptrat+0x00should stay as a neutral far-pointer field or can now be promoted to a stronger entity/owner key name from caller-side evidence - recover a storage-aware
this-typing path forCreatespecifically; the live route now works well enough to test explicit 4-byte storage, but the remaining blocker is the 2-byteEntityVmRuntime *datatype itself rather than endpoint reachability - inspect the broader
Interpreter_NextUsecodeOplane around1418:3330now that the release call andinterpreter_pop_saved_farptrare anchored, and decide whether the loaded-chunk record can absorb any more of the surrounding save/restore stack traffic without overfitting transient interpreter locals - redeploy or otherwise verify the live storage-fallback
set_function_this_type/apply_class_layoutbuild, then retry theEntityVmContextlifecycle typing pass in-session before dropping back to local PyGhidra - identify one or two additional strongly owned runtime or owner-resource helpers if the live session exposes them cleanly
- decide whether
ApplyToMatchingOwnerRowsshould keep its current generic split-word parameters underRemorse::EntityVmRuntimeor whether the first argument pair is now well enough understood to collapse into a typed runtimethis - decide whether the newly clarified
runtime_farptrargument onGetSlotChunkPtrAtOffsetandApplyToMatchingOwnerRowsis enough to justify a safe typed-thisexperiment on those methods, or whether the currentEntityVmRuntime *pointer-size issue still makes the explicitdword runtime_farptrform the least misleading representation - use the now-recovered
CreateFromSlotIndexcaller pack as the baseline for any next cleanup, and only chase a prettier return type once the base-process inheritance story is explicit enough to make that promotion a real readability win - keep the masked-create hub and offset-specialized wrapper ladder outside the class until caller-side role recovery is tighter
Source-Emission Guidance
If emitted as provisional C++ later, safest early skeleton is:
EntityVmOwnerResourcewith explicit loader/index fields and placeholder virtual/helper methodsEntityVmRuntimewith fixed-size slot table, owner pointer, category-base fields, and create/destroy methodsEntityVmContextwith exact saved-field placeholders and a distinct embedded mini-VM state member
Avoid in the first skeleton:
- speculative opcode enums presented as final
- collapsing the owner-resource helper into plain runtime fields
- flattening the source/control stream pair into one host-only pointer abstraction if Track A remains active
Bottom Line
The VM lane now supports a real class model, but it should start with ownership and layout rather than with overconfident script-semantic names.
The most defensible current model is runtime owns helper and slot state; contexts are short-lived objects built from slot selection plus owner-loaded source rows.