# 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: - `EntityVmRuntime` - `EntityVmOwnerResource` - `EntityVmContext` - 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](docs/raw-0008-000c.md) and [docs/raw-000a-000d.md](docs/raw-000a-000d.md): 1. startup path resolves a configured EUSECODE root/path 2. `entity_vm_runtime_create` allocates the main runtime body 3. runtime constructor attaches one file-backed helper created by `entity_vm_runtime_owner_resource_create` 4. gameplay entities map to slot indices through `entity_vm_slot_index_from_entity` 5. masked-create helpers test owner-side capability bits and then build per-entity or per-slot `EntityVmContext` objects 6. 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_configured` - `000d:4c99 entity_vm_runtime_create` - `000d:4d36 entity_vm_runtime_init_slots` - `000d:4d75 entity_vm_runtime_release_slots` - `000d:4e01 entity_vm_runtime_destroy` Current strongest structural claims: - runtime body is the global owner behind `0x6611/0x6613` - front region behaves like a `0x80` entry slot table with stride `0x26` - tail region around `+0x1300..+0x1318` holds 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 - `1420:1866 Remorse::EntityVmRuntime::InitSlotOwnerBuffers` - reads owner-resource metadata for one slot id - allocates the two slot-local working buffers later stored at `+0x1e/+0x20` and `+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` 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_create` - `000d:70fd entity_vm_runtime_owner_resource_destroy` Best current helper shape: - compact file-backed helper object - helper-owned count at `+0x14` - far-pointer table at `+0x10` - paired 16-bit table at `+0x18` - helper vtable `+0x04` acts as size query - helper vtable `+0x0c` materializes the `0x0d`-stride owner rows later consumed by contexts 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` ### 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 `+0x10` and `+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_entity` - `000d:46ec entity_vm_context_create_from_slot_index` - `000d:48b6 entity_vm_context_free_buffer` - `000d:48da entity_vm_context_sync_global_value_and_dispatch` - `000d:4962 entity_vm_context_destroy` - `000d:498f entity_vm_context_save` - `000d: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: - `+0x32` stores slot index - `+0x34` stores the additive offset word used by the `slot_load_value_plus_offset` lane - `+0x36` embeds the mini-VM/state object - `+0xd6/+0xd8` hold the seeded source/control stream lane - `+0x102` is the backward-growing local payload/buffer lane - `+0x10c/+0x10e` store a derived low/high pair reused by save/load - `+0x117/+0x119` cache the owner-linked source pair - `+0x123` behaves 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 `NPCTRIG` versus `EVENT` directly - 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:463a` checks 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` / `0x0b` are 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 1. `EntityVmOwnerResource` 2. `EntityVmRuntime` 3. `EntityVmContext` 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 `Remorse` and class owners `Remorse::EntityVmOwnerResource`, `Remorse::EntityVmRuntime`, and `Remorse::EntityVmContext`. - Created provisional datatype `/Remorse/EntityVmOwnerResource` with current stable anchors: - `+0x10 owner_row_table` - `+0x14 entry_count` - `+0x18 entry_word_table` - Created provisional datatype `/Remorse/EntityVmRuntime` with size `0x1319` and only the currently stable tail anchors around the owner-resource attachment lane. - Created provisional datatype `/Remorse/EntityVmSlotEntry` with size `0x26` and 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:0000` under `Remorse::EntityVmOwnerResource::Create`. - Moved `1430:00fd` under `Remorse::EntityVmOwnerResource::Destroy`. - Moved `1420:1499` under `Remorse::EntityVmRuntime::Create`. - Moved `1420:1536` under `Remorse::EntityVmRuntime::InitSlots`. - Moved `1420:1575` under `Remorse::EntityVmRuntime::ReleaseSlots`. - Moved `1420:1601` under `Remorse::EntityVmRuntime::Destroy`. - Moved `1420:167c` under `Remorse::EntityVmRuntime::AcquireSlotForEntity` after live decompilation showed a `0x80`-entry scan over the runtime slot table with free-slot fallback and eviction of the currently selected slot. - Moved `1420:1866` under `Remorse::EntityVmRuntime::InitSlotOwnerBuffers` after live decompilation showed owner-resource reads plus the two slot-local buffer allocations and initial sentinel fill. - Moved `1420:19fd` under `Remorse::EntityVmRuntime::EnsureSlotChunkLoaded` after live decompilation showed per-slot chunk materialization and cache-presence marking. - Renamed `1420:1cca` to `entity_vm_runtime_debug_dump_slot_memory` after live decompilation showed a debug-gated walk of the runtime slot list with slot memory usage output. - Renamed `1420:1f24` to `entity_vm_runtime_apply_to_matching_owner_rows` after live decompilation showed filtered iteration over the runtime owner-row list. - Renamed `1420:2040` to `entity_vm_slot_entry_create_or_clear` after live decompilation showed allocation and zeroing of one `0x26`-byte slot record. - Added short decompiler comments at `1430:0000` and `1430:00fd` to preserve the raw `000d:7000` / `000d:70fd` provenance. - Added short decompiler comments at `1420:1499`, `1420:1536`, `1420:1575`, and `1420:1601` to preserve the runtime-lifecycle provenance and current layout claims. - Added short decompiler comments at `1420:167c`, `1420:1866`, `1420:19fd`, `1420:1cca`, `1420:1f24`, and `1420:2040` so the slot-table evidence stays visible in the live database. - Repaired the decompiler health of `1420:1499 Remorse::EntityVmRuntime::Create` after the delete/recreate cycle left it throwing `Low-level Error: Symbol $$undef00000006 extends beyond the end of the address space`; the root cause was the shared allocator helper at `1000:42e2`, whose pointer-return signature decompiled with a hidden `__return_storage_ptr__` and poisoned the caller stack model until it was normalized to an explicit `dword` return plus explicit stack storage. - Verified second batch landed in the live `CRUSADER.EXE` session on 2026-04-06. - Moved `1420:0eec` under `Remorse::EntityVmContext::CreateFromSlotIndex`. - Moved `1420:10b6` under `Remorse::EntityVmContext::FreeBuffer`. - Moved `1420:10da` under `Remorse::EntityVmContext::SyncGlobalValueAndDispatch`. - Moved `1420:1162` under `Remorse::EntityVmContext::Destroy`. - Moved `1420:118f` under `Remorse::EntityVmContext::Save`. - Moved `1420:1278` under `Remorse::EntityVmContext::Load`. - Added short decompiler comments at `1420:0eec`, `1420:10b6`, `1420:10da`, `1420:1162`, `1420:118f`, and `1420:1278` to preserve the raw `000d:46ec`, `000d:48b6`, `000d:48da`, `000d:4962`, `000d:498f`, and `000d:4a78` provenance after the class-owner move. - Verified third batch landed through local PyGhidra on 2026-04-06 after the live `run_write_script(...)` route still returned `404 No context found for request` against the active GUI session. - Created provisional datatype `/Remorse/EntityVmContext` with size `0x124` and 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_clear` to `EntityVmSlotEntry * __cdecl16far entity_vm_slot_entry_create_or_clear(EntityVmSlotEntry * slot_entry)` so the slot-record helper no longer falls back to anonymous `byte *` parameters. - Updated `1420:167c Remorse::EntityVmRuntime::AcquireSlotForEntity` to return `EntityVmSlotEntry *`, while leaving the third `param_3` entity-like pointer conservative until caller-side role recovery is tighter. - Updated `1420:1866 Remorse::EntityVmRuntime::InitSlotOwnerBuffers` so the third parameter is now `EntityVmSlotEntry * slot_entry`. - Updated `1420:1536 Remorse::EntityVmRuntime::InitSlots` to `void __cdecl16far InitSlots(EntityVmRuntime * this)`. - Updated `1420:1575 Remorse::EntityVmRuntime::ReleaseSlots` to `void __cdecl16far ReleaseSlots(EntityVmRuntime * this)`. - Tried the same typed-`this` collapse on `1420:1499 Remorse::EntityVmRuntime::Create`, but the pointer-sized `this` variant reintroduced a hidden `__return_storage_ptr__`; the function was restored immediately to the known-good split-word custom-storage signature `dword __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_a` decompiler local in `1420:19fd Remorse::EntityVmRuntime::EnsureSlotChunkLoaded` to `EntityVmSlotEntry *`, so the slot-entry cache path now renders the stable `owner_buffer_offset` and `chunk_state_offset` fields directly instead of anonymous `undefined4` pairs. - Retried the `EntityVmContext` lifecycle typing pass through live MCP. `apply_class_layout` dry-run for `/Remorse/EntityVmContext` now returns a normal structured preview instead of the earlier null failure, but the real apply path still fails with `Failed to apply this type: Storage size does not match data type size: 2`, and direct live `set_function_this_type` calls on `FreeBuffer`, `SyncGlobalValueAndDispatch`, `Destroy`, `Save`, and `Load` hit the same storage-size mismatch. - Retried live `run_write_script(...)` with and without explicit target selectors on `CRUSADER.EXE`, but the route still returned `404 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::CreateFromSlotIndex` so the first parameter is now `EntityVmContext * this` while preserving the existing `UsecodeProcess *` return type until the constructor/factory semantics are tighter. - Updated `1420:10b6 FreeBuffer`, `1420:10da SyncGlobalValueAndDispatch`, `1420:1162 Destroy`, `1420:118f Save`, and `1420:1278 Load` so the first parameter is now `EntityVmContext * this` instead of `UsecodeProcess *`. - 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:42e2` and `1420: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_storage` returned the old `set_function_prototype` failure body, and `/set_storage_aware_prototype` returned `404 No context found for request`. That confirms the remaining issue is live deployment parity, not endpoint design. - Rechecked the direct callers of `CreateFromSlotIndex`: `Usecode_ItemCallEvent` plus two `Interpreter_NextUsecodeOp` call sites. The `Usecode_ItemCallEvent` path explicitly calls `CreateFromSlotIndex((EntityVmContext *)0x0,0,...)` as an allocate-and-return factory, and the current caller-side uses immediately consume only base `Process`-style fields such as `procid` and termination flags. The two interpreter call sites likewise just store the returned far pointer in `DX:AX` scratch pairs before later base-process handling. - That caller evidence is enough to keep the current conservative return type for now: `CreateFromSlotIndex` is clearly manufacturing an `EntityVmContext`, but promoting the return to `EntityVmContext *` 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 explicit `AX:DX` return storage in-session, but two live issues remain: stack offsets at `10` and above currently need `0x` prefixes to avoid landing at `0x10`/`0x12`/`0x14`/`0x16`, and `calling_convention='__cdecl16far'` still normalizes the repaired functions to plain `__cdecl`. - Updated `/Remorse/EntityVmSlotEntry` one step deeper from the `InitSlotOwnerBuffers` and `EnsureSlotChunkLoaded` evidence: - `+0x00 match_key_farptr` - `+0x0a owner_chunk_count` - `+0x12 owner_data_base` - retained the earlier `+0x1e..+0x24` owner-buffer and chunk-state pointer pairs - Updated local variable typing so `AcquireSlotForEntity` now carries `EntityVmSlotEntry *` locals for the current slot cursor/free-slot candidate lane, and `InitSlotOwnerBuffers` now carries an `EntityVmSlotEntry *` local for the owner-metadata scratch object. - The decompiler payoff is immediate: `InitSlotOwnerBuffers` now shows `owner_chunk_count`, `owner_buffer_*`, and `chunk_state_*` directly, and `EnsureSlotChunkLoaded` now shows `owner_data_base` where 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 with `Storage 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 current `EntityVmRuntime *` 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 explicit `AX:DX` return storage cleanly, but `calling_convention='__cdecl16far'` still normalizes both `1000:42e2` and `1420:1499` to plain `__cdecl`. - Renamed `1420:1d72` to `entity_vm_runtime_get_slot_chunk_ptr_at_offset` after confirming from `CreateFromSlotIndex`, `Load`, and `FUN_1418_035f` that it is just a wrapper over `EnsureSlotChunkLoaded` plus a caller-supplied offset. - Renamed `1420:1d8d` to `entity_vm_runtime_release_slot_chunk_ref` after confirming from the `Interpreter_NextUsecodeOp` caller that it decrements one live chunk-state refcount and asserts if the chunk was not retained. - Renamed `1420:1e17` to `entity_vm_runtime_try_unload_slot_chunk` after confirming from `entity_vm_runtime_apply_to_matching_owner_rows` that 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/EntityVmLoadedChunkRecord` with 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_chunk` so the second parameter is now `EntityVmLoadedChunkRecord * loaded_chunk_record`, and then tightened the return to `byte __cdecl16far` with explicit `AL` storage after caller disassembly at `1420:1f50` and `1420:1fc1` showed both call sites consume only `AL`. - Updated the iterator local `uStack_6` in `1420:1f24 entity_vm_runtime_apply_to_matching_owner_rows` to `EntityVmLoadedChunkRecord *`, so the owner-row cleanup walk now renders `next_*`, `slot_index`, and `chunk_index` directly instead of anonymous stack-pair traffic. - Confirmed the interpreter-side release helper caller at `1418:3330` pushes the live chunk record's `slot_index` / `chunk_index` pair from `ES:[BX+0x32]` / `ES:[BX+0x34]` together with the runtime far pointer before calling `entity_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:003c` to `interpreter_pop_saved_farptr` after confirming from its only caller in `Interpreter_NextUsecodeOp` that it decrements a saved-farptr stack count at `+0x80` and returns the far pointer stored at the new top entry. - Added short decompiler comments at `1418:003c` and `1418:3330` so 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::EntityVmSlotEntry` in the live database and moved `1420:2040` under it as `CreateOrClear`. - Tightened `Remorse::EntityVmSlotEntry::CreateOrClear` so the single parameter is now named `this` and the explicit far return storage is restored to `AX` for the `EntityVmSlotEntry *` result. - Moved the previously global runtime cleanup helpers under `Remorse::EntityVmRuntime` as real methods: - `1420:1d72` -> `GetSlotChunkPtrAtOffset` - `1420:1d8d` -> `ReleaseSlotChunkRef` - `1420:1cca` -> `DebugDumpSlotMemory` - `1420:1e17` -> `TryUnloadSlotChunk` - `1420:1f24` -> `ApplyToMatchingOwnerRows` - Tightened the `ReleaseSlotChunkRef` parameter names to `runtime_farptr`, `slot_index`, and `chunk_index`, and renamed the `DebugDumpSlotMemory` far-pointer argument to `runtime_farptr` so 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::GetSlotChunkPtrAtOffset` to `dword __stdcall16far GetSlotChunkPtrAtOffset(dword runtime_farptr, int slot_index, int chunk_index, dword intra_chunk_offset)` after re-checking the `CreateFromSlotIndex` and `Load` callers. 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::ApplyToMatchingOwnerRows` to `byte __cdecl16far ApplyToMatchingOwnerRows(dword runtime_farptr, int slot_index_filter, int chunk_index_filter)` after re-checking the `AcquireSlotForEntity` and `EnsureSlotChunkLoaded` callers. 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 `GetSlotChunkPtrAtOffset` still returns its far pointer in `DX:AX` and `ApplyToMatchingOwnerRows` still returns its boolean result in `AL`. - Verified thirteenth live batch landed on 2026-04-06. - Lifted the grouped runtime methods from split-word `runtime_farptr` parameters to explicit 4-byte `EntityVmRuntime * this` storage using `/Remorse/EntityVmRuntime *32` in-session. The live signatures now read as real methods for: - `Create` - `InitSlots` - `ReleaseSlots` - `DebugDumpSlotMemory` - `ReleaseSlotChunkRef` - `GetSlotChunkPtrAtOffset` - `TryUnloadSlotChunk` - `ApplyToMatchingOwnerRows` - `EnsureSlotChunkLoaded` - `Remorse::EntityVmRuntime::Create` is the biggest change in that batch: it no longer needs the old split-word placeholder form and now holds `dword __cdecl16far Create(EntityVmRuntime * this, word owner_type, word owner_id)` with the original `AX:DX` return preserved. - `EnsureSlotChunkLoaded` now also carries the clearer `EntityVmRuntime * this, short slot_index, short chunk_index` signature with the original far-pointer return preserved in `DX:AX`. - `AcquireSlotForEntity` and `InitSlotOwnerBuffers` are now fully over that hurdle too: `AcquireSlotForEntity` returns `EntityVmSlotEntry *32` in `DX:AX`, and `InitSlotOwnerBuffers` now carries `EntityVmSlotEntry *32 slot_entry` as 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 explicit `EntityVmContext *32 this` - That leaves `CreateFromSlotIndex` as the one clearly still-complex VM signature in this family cluster: the body still shows a far `this`, but the remaining argument pack needs a dedicated caller-side recovery pass rather than another pointer-only rewrite. - Verified fifteenth live batch landed on 2026-04-06. - Recovered the mixed caller pack on `1420:0eec Remorse::EntityVmContext::CreateFromSlotIndex` far enough to replace the old anonymous split arguments with caller-backed names: - `dword owner_source_farptr` - `dword pitemno_farptr` - `word mode_flags` - `word slot_index` - `word value_add_offset` - `word intra_chunk_offset` - `dword ucparam_farptr` - `uint ucparamsize` - Restored explicit far return storage on `CreateFromSlotIndex` to `AX:DX` after 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 __cdecl` instead of preserving the earlier higher-level `UsecodeProcess *` / `__stdcall16far` surface, even though the decompiler now keeps the correct argument boundaries and the return really is back in `AX:DX`. - Current best caller-backed read for `CreateFromSlotIndex` is now narrower and more useful than the old placeholder form: - `owner_source_farptr` is a real far-pointer input that is persisted to context `+0x11b/+0x11d` - `ucparam_farptr` is a real far-pointer input copied into the backward-growing buffer at `+0x102` - `slot_index`, `value_add_offset`, and `intra_chunk_offset` are 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`, not `final inheritance-clean constructor signature` Current live datatype state: - `/Remorse/EntityVmOwnerResource` is the cleanest landed class in this lane so far. - `/Remorse/EntityVmRuntime` currently only freezes the stable tail fields and helper pointer, not the full slot-entry schema. - `/Remorse/EntityVmSlotEntry` now exists both as a bounded helper datatype and as a live `Remorse` class owner. Its current authored surface is intentionally small: one constructor/clear method plus the stable `match_key_farptr`, `owner_chunk_count`, `owner_data_base`, and owner-buffer / chunk-state pointer anchors. - `/Remorse/EntityVmLoadedChunkRecord` now 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/EntityVmContext` now 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_layout` succeeded for `Remorse::EntityVmOwnerResource` but failed for `Remorse::EntityVmRuntime` when the binder tried to apply a `this` type, even though plain ownership moves worked. - The old `apply_class_layout` dry-run null failure for `Remorse::EntityVmContext` no longer reproduces on the current live server, but the actual write-side `this` typing path is still effectively old-build behavior: the real apply and direct `set_function_this_type` calls still fail on the existing `UsecodeProcess *` lifecycle signatures with `Storage size does not match data type size: 2`. - The `EntityVmContext` lifecycle signatures are now locally repaired through PyGhidra: `CreateFromSlotIndex` plus `FreeBuffer` / `SyncGlobalValueAndDispatch` / `Destroy` / `Save` / `Load` all carry `EntityVmContext * this` as their first parameter. - `CreateFromSlotIndex` should still keep a conservative semantic return in the notes for the moment. The active live endpoint now textualizes it as `dword __cdecl` after the caller-packed custom-storage cleanup, but the allocate-and-return behavior is clear, the real return storage is back in `AX:DX`, and the known callers still consume the result through base-process fields rather than through an inheritance-aware `EntityVmContext : UsecodeProcess` datatype model. - The runtime lane is now split more accurately: `InitSlots` and `ReleaseSlots` can carry a direct `EntityVmRuntime * this`, while `Create` still 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::EntityVmRuntime` instead 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 helper `EntityVmLoadedChunkRecord` all now participate in a mostly far-pointer-correct live type model, with `CreateFromSlotIndex` as the main remaining signature outlier. - The slot-entry model is tighter again: beyond the earlier `owner_buffer_*` and `chunk_state_*` tails, the datatype now also exposes `owner_chunk_count` and `owner_data_base`, which makes the allocator/count path in `InitSlotOwnerBuffers` and the owner-data window math in `EnsureSlotChunkLoaded` read 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 into owned methods yet - no speculative promotion of the masked-create wrapper ladder into `EntityVmContext` methods - 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..+0x24` tail region and the current conservative helper prototypes Best immediate next moves after this landed: - inspect `EnsureSlotChunkLoaded` and adjacent `1420:` helpers again now that `AcquireSlotForEntity` returns `EntityVmSlotEntry *`, and push the slot-entry type one step deeper only where the resulting local/object read is genuinely clearer - decide whether `CreateFromSlotIndex` can safely promote its return type from `UsecodeProcess *` to `EntityVmContext *`, or whether it should stay a factory-style bridge that only types `this` - if the context/base-process inheritance story becomes explicit in datatypes, revisit `CreateFromSlotIndex` return typing then; until that point, keep the current `UsecodeProcess *` return even though the body itself clearly builds an `EntityVmContext` - decide whether `match_key_farptr` at `+0x00` should 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 for `Create` specifically; the live route now works well enough to test explicit 4-byte storage, but the remaining blocker is the 2-byte `EntityVmRuntime *` datatype itself rather than endpoint reachability - inspect the broader `Interpreter_NextUsecodeOp` lane around `1418:3330` now that the release call and `interpreter_pop_saved_farptr` are 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_layout` build, then retry the `EntityVmContext` lifecycle 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 `ApplyToMatchingOwnerRows` should keep its current generic split-word parameters under `Remorse::EntityVmRuntime` or whether the first argument pair is now well enough understood to collapse into a typed runtime `this` - decide whether the newly clarified `runtime_farptr` argument on `GetSlotChunkPtrAtOffset` and `ApplyToMatchingOwnerRows` is enough to justify a safe typed-`this` experiment on those methods, or whether the current `EntityVmRuntime *` pointer-size issue still makes the explicit `dword runtime_farptr` form the least misleading representation - use the now-recovered `CreateFromSlotIndex` caller 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: - `EntityVmOwnerResource` with explicit loader/index fields and placeholder virtual/helper methods - `EntityVmRuntime` with fixed-size slot table, owner pointer, category-base fields, and create/destroy methods - `EntityVmContext` with 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`.