Download docs/research/NATIVE_SPATIAL.md from nmarshall/urt-rs: direct link, hf CLI and curl.
- Browser
- Download file 7.45 kB
-
https://huggingface.co/nmarshall/urt-rs/resolve/main/docs/research/NATIVE_SPATIAL.md
- Command line
-
hf download hf://nmarshall/urt-rs/docs/research/NATIVE_SPATIAL.md
-
curl -L -o NATIVE_SPATIAL.md https://huggingface.co/nmarshall/urt-rs/resolve/main/docs/research/NATIVE_SPATIAL.md
Native trigonometry, spatial services and VM pointers
Static pass, 2026-10-11. Reproduce with python3 scripts/analyze_native_spatial.py; check with python3 scripts/test_native_spatial.py. Generated evidence verifies both supplied Linux ELF64 original/disassembly hashes, rereads native switch tables, retains complete selected case paths and six helper graphs per image, and evaluates eighteen synthetic VM_ArgPtr paths. No original process or native callee executes. Windows, macOS and Linux i386 parity remain open. These findings extend crosshair and location recovery.
NAT-001: confirmed shipped service destinations and float transport
Both Linux 64-bit engines, client and dedicated, have these actual SV_GameSystemCalls destinations:
| Service | QVM encoded CALL | Native destination | VM_ArgPtr calls |
|---|---|---|---|
| 24 | -25 | SV_Trace | 5 |
| 26 | -27 | SV_inPVS | 2 |
| 32 | -33 | SV_AreaEntities | 3 |
| 103 | -104 | sinf@plt | 0 |
| 104 | -105 | cosf@plt | 0 |
The collector extends each case beyond the older 24-instruction dispatch snippet to its terminal transfer. Unexpected branches, missing addresses or unbounded case paths fail closed. These mappings are confirmed in shipped Linux code rather than inferred solely from upstream enums; runtime semantics and other images remain separate requirements.
The trig cases load a single-precision word from native argument-array offset 8 into xmm0, call sinf/cosf, store the float result to a four-byte stack slot and return that slot's integer bit pattern through EAX followed by signed extension (CLTQ). This transports float bits rather than converting the result numerically to an integer. The previous crosshair paired-output sin/cos interpretation is now corroborated for these Linux engines. Actual libm execution, rounding/exception behavior and noncardinal output comparisons remain unverified.
Trace case 24 converts its five pointers (result, start, mins, maxs, end) through VM_ArgPtr, forwards the two scalar words through 32-bit argument registers/stack, appends an additional native argument zero, calls SV_Trace and clears EAX for the syscall return. The observed crosshair mask 1 and field-140 scalar are therefore forwarded to the trace layer, not interpreted by this dispatcher. Case 26 converts its two position pointers; case 32 converts mins/maxs/output-list and forwards maximum count through ECX, returning a signed-extended result count.
NAT-002: actual VM_ArgPtr branches
The full sixteen-instruction helper in each engine has the same observed logic over native currentVM fields:
- Raw argument zero returns NULL immediately.
- Nonzero argument with currentVM NULL also returns NULL.
- If currentVM word at 0x58 is nonzero, return data base at 0x90 + raw argument using 64-bit addition.
- Otherwise load the mask at 0x98 as a signed 32-bit word, extend it to 64 bits, AND with the argument, then add data base 0x90.
The collector's restricted interpreter follows these real instruction addresses, supports only the observed register/memory/test/branch/add/mask/return forms, rejects unknown operations/reads and has a step bound. currentVM and its fields are explicitly supplied synthetic inputs. It is not a general native emulator or a proof of valid mask/allocation invariants.
Nine fixtures per image cover NULL argument/currentVM, direct addition, positive mask, all-ones signed mask, negative argument and a nonzero 2^32 argument. That last argument returns base+2^32 in the nonzero-field-0x58 path, but the data base itself in the mask-65535 path. Thus “masked offset zero” and “raw argument zero” have distinct results. Legal VM argument widths, field identities, allocation bounds, active-VM lifetime and malformed-pointer dereference behavior remain open.
NAT-003: trace, PVS and area-query structure
SV_Trace substitutes a shared vector for NULL mins/maxs through conditional moves. The vector lies in .bss, not file-backed data; its twelve bytes have a loader zero-fill initial value. The report marks that origin explicitly rather than claiming a file read or verified unchanged runtime contents. The graph calls CM_BoxTrace before the possible SV_ClipMoveToEntities path. File-backed constants 1.0/-1.0 and native swept-bound instructions are retained. The complete collision algorithm and exceptional float branches are not evaluated.
The trace return path copies seven eight-byte chunks, output offsets 0/8/16/24/32/40/48, from its local collision result to the caller. That establishes an observed 56-byte copy span, not every member's type or a portable C-layout assertion. In particular, the crosshair word at result +4 and endpoint reads +12/+16/+20 still need semantic field corroboration before naming them as final Rust types.
SV_inPVS obtains both point leaf numbers, clusters and areas. It asks CM_ClusterPVS for the first point's cluster mask. With a non-NULL mask, it tests the second cluster's byte/bit using signed index shift by 3 and low bits masked by 7; a clear visibility bit returns false. A NULL mask bypasses that bit restriction. The remaining path calls CM_AreasConnected and normalizes nonzero to true. This is cluster/area connectivity evidence, not an unobstructed ray test. Cluster bounds, NULL-mask production, portals and real map fixtures remain open.
SV_AreaEntities initializes a stack query record: mins/maxs pointers, output-list pointer, count zero and maximum count. It calls SV_AreaEntities_r with the native spatial-tree root, then returns the count. The recursive graph visits linked entities, gets their game objects through SV_GEntityForSvEntity, and checks raw coordinate fields 0x1d0 through 0x1e4 against query bounds. Strict greater/less comparisons imply touching finite bounds are not rejected by those six checks; NaNs are not characterized here.
The capacity test is count == maximum, not a recovered >= guard. At equality, the graph tail requests Com_Printf with file-backed SV_AreaEntities: MAXCOUNT\n. Otherwise it writes an index at output + count*4 and increments count. This confirms four-byte index elements for the QVM location query, whose supplied maximum is 1024. Exact index derivation, tree ordering, node-boundary recursion, malformed count/maximum inputs, link ownership and clipping remain open; no query fixture is executed against original map geometry.
Remaining work and Rust dependencies
RE-285–287 record shipped destination/transport, bounded VM-pointer and native spatial-graph milestones. RE-284 remains open for runtime/layout/platform completeness; RE-288 tracks all-platform dispatch comparison, libm/float exception conformance, active-VM allocation/mask invariants, full trace fields and collision internals, PVS/portal/tree/index ordering and original map/runtime fixtures. Rust can now distinguish sine/cosine float-bit services and point/box/visibility interfaces with direct Linux evidence, while full collision parity and cross-platform behavior remain explicit dependencies.
The HIT-001–003 follow-up narrows entity filtering, result-field/copy and collision-model questions through bounded first-lane probes and retained native graphs. Full geometry, composition and runtime/platform parity remain open under RE-288/292.