Recover registered entity arithmetic and area query boundaries
Browse files- docs/ASSET_PIPELINE_TODO.md +2 -0
- docs/FINDINGS.md +6 -0
- docs/PROVISIONAL_TODO.md +1 -1
- docs/RESEARCH_LEDGER.md +4 -0
- docs/REVERSE_ENGINEERING_TODO.md +5 -0
- docs/research/AREA_QUERIES.md +53 -0
- docs/research/README.md +2 -0
- docs/research/area-queries.json +0 -0
- scripts/analyze_area_queries.py +109 -0
- scripts/analyze_collision_composition.py +34 -17
- scripts/test_area_queries.py +92 -0
docs/ASSET_PIPELINE_TODO.md
CHANGED
|
@@ -4,6 +4,8 @@ Purpose: analyse **every file/member variant**, then produce reproducible assets
|
|
| 4 |
|
| 5 |
The [entity-clipping loop collector](research/COLLISION_LOOP.md) extends binary inspection with bounded count/order/sentinel evidence. Reproduce with `python3 scripts/analyze_collision_loop.py`; its fixtures inform future collision sidecar validation without executing original geometry.
|
| 6 |
|
|
|
|
|
|
|
| 7 |
## Planned tool catalogue
|
| 8 |
|
| 9 |
Names and output formats below are proposals to refine after the relevant research gates. They may become Python scripts, Rust CLI subcommands, or both. Runtime-critical parsers and shared schemas should have one authoritative implementation; orchestration scripts must not diverge from it.
|
|
|
|
| 4 |
|
| 5 |
The [entity-clipping loop collector](research/COLLISION_LOOP.md) extends binary inspection with bounded count/order/sentinel evidence. Reproduce with `python3 scripts/analyze_collision_loop.py`; its fixtures inform future collision sidecar validation without executing original geometry.
|
| 6 |
|
| 7 |
+
The [area-query collector](research/AREA_QUERIES.md) adds inclusive-overlap and strict-split fixtures for future converted collision validation; full node placement and map query equivalence remain open. Reproduce with `python3 scripts/analyze_area_queries.py`.
|
| 8 |
+
|
| 9 |
## Planned tool catalogue
|
| 10 |
|
| 11 |
Names and output formats below are proposals to refine after the relevant research gates. They may become Python scripts, Rust CLI subcommands, or both. Runtime-critical parsers and shared schemas should have one authoritative implementation; orchestration scripts must not diverge from it.
|
docs/FINDINGS.md
CHANGED
|
@@ -306,3 +306,9 @@ Bounded qagame paths recover bomb-plant/inactivity correction requests, direct f
|
|
| 306 |
**F-083 — Observed clipping scheduling invariant:** only lane 0 checks incremented counter == count; the paired and peeled continuations rely on parity and stop on nonzero status0. Supplied compositions visit consecutive four-byte array entries through counts 1023/1024. Count ownership, candidate bodies/callees and runtime safety remain open. [LOOP-002](research/COLLISION_LOOP.md).
|
| 307 |
|
| 308 |
**F-084 — Observed related sentinel normalization:** pass 1023 skips entity lookup and writes 0xffffffff; other passes request lookup, whose supplied object +0x200 word is independently normalized from 1023 to 0xffffffff. Complete entity bounds, ownership semantics and runtime validity remain unverified. [LOOP-003](research/COLLISION_LOOP.md).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
**F-083 — Observed clipping scheduling invariant:** only lane 0 checks incremented counter == count; the paired and peeled continuations rely on parity and stop on nonzero status0. Supplied compositions visit consecutive four-byte array entries through counts 1023/1024. Count ownership, candidate bodies/callees and runtime safety remain open. [LOOP-002](research/COLLISION_LOOP.md).
|
| 307 |
|
| 308 |
**F-084 — Observed related sentinel normalization:** pass 1023 skips entity lookup and writes 0xffffffff; other passes request lookup, whose supplied object +0x200 word is independently normalized from 1023 to 0xffffffff. Complete entity bounds, ownership semantics and runtime validity remain unverified. [LOOP-003](research/COLLISION_LOOP.md).
|
| 309 |
+
|
| 310 |
+
**F-085 — Observed registered entity arithmetic:** SV_GentityNum multiplies index/registered stride in 32 bits, sign-extends the product and adds the base without a local bounds check. Exact server-record fixtures corroborate stride 312 and modular inverse index recovery. Registered ABI/lifetime/validity and runtime/platform behavior remain open. [AREA-001](research/AREA_QUERIES.md).
|
| 311 |
+
|
| 312 |
+
**F-086 — Observed area overlap/capacity boundaries:** finite candidate-box comparisons include touching faces/edges/corners. Accepted entries test count == capacity before a four-byte index write and count increment; deliberately invalid counts expose the guard's equality-only nature. Count/array ownership and runtime safety remain unverified. [AREA-002](research/AREA_QUERIES.md).
|
| 313 |
+
|
| 314 |
+
**F-087 — Observed area ordering and split rules:** local linked records precede children; maximum > split requests child 0 before minimum < split tail-selects child 1. Axis -1 returns. Plane equality excludes each corresponding child, so completeness depends on node placement; full trees/callees/runtime remain open. [AREA-003](research/AREA_QUERIES.md).
|
docs/PROVISIONAL_TODO.md
CHANGED
|
@@ -33,7 +33,7 @@ For implementation items, the minimum done condition is a documented contract, a
|
|
| 33 |
|
| 34 |
### First research batch
|
| 35 |
|
| 36 |
-
Progress: [RE-232–234 / TIME-001–003](research/FRAME_TIMING.md) now cover Linux ELF64 elapsed modification, event sources and pause predicates. The [RE-237–239 follow-up](research/EVENT_QUEUES.md) resolves the pause-field address/bot chain and adds queue/journal evidence. The [RE-241–243 announcement pass](research/AUTH_ANNOUNCEMENTS.md) recovers label tables and recipient paths; The [RE-245–247 cvar pass](research/GAME_CVARS.md) adds all 126 main registration rows and resolves auth_verbosity/callers. The [RE-249–251 change pass](research/CVAR_CHANGES.md) adds cache/notification behavior, four numeric correction branches and direct registration coverage. The [RE-253–255 query pass](research/AUTH_QUERIES.md) adds selector helpers, command-role routing and internal authentication modes. The [RE-257–259 bounds/entry pass](research/SELECTOR_BOUNDS.md) adds integer parsing, selector storage/count evidence and native/QVM/chat route connections. The [RE-261–263 chat pass](research/CHAT_PATHS.md) adds native command metadata/filter evidence and bounded message/recipient paths, including the documented argument-mapping correction. The [RE-265–267 delivery pass](research/CHAT_DELIVERY.md) adds bounded dead-chat predicates, formatter argument routing and echo/fanout calls. The [RE-269–271 expansion pass](research/CHAT_EXPANSION.md) adds marker states, twelve key comparisons and health/momo branches. The [RE-273–275 item/time pass](research/CHAT_ITEMS_TIME.md) adds packed ammunition fields, sampled names and countdown branches. The [RE-277–279 location pass](research/CHAT_LOCATIONS.md) adds leader selection, cache/format boundaries and location-list/spatial evidence. The [RE-281–283 crosshair pass](research/CHAT_CROSSHAIR.md) adds finite geometry fixtures, trace arguments and result routing. The [RE-285–287 native spatial pass](research/NATIVE_SPATIAL.md) confirms Linux trig/spatial services, VM pointer branches and collision/query evidence. The [RE-289–291 collision pass](research/NATIVE_COLLISION.md) adds native candidate filters, result decisions and model/field evidence. The [RE-293–295 composition pass](research/COLLISION_COMPOSITION.md) adds three-lane byte-copy/status agreement and world-result routing. The [RE-297–299 loop pass](research/COLLISION_LOOP.md) adds signed count/status gates, parity scheduling and related sentinel normalization. PROV-002 remains open for full output/reset coverage. PROV-001 remains open for complete scheduling and platform coverage.
|
| 37 |
|
| 38 |
Start with the open questions raised by the current binary evidence. These tasks depend on the linked reports, not on a working rewrite.
|
| 39 |
|
|
|
|
| 33 |
|
| 34 |
### First research batch
|
| 35 |
|
| 36 |
+
Progress: [RE-232–234 / TIME-001–003](research/FRAME_TIMING.md) now cover Linux ELF64 elapsed modification, event sources and pause predicates. The [RE-237–239 follow-up](research/EVENT_QUEUES.md) resolves the pause-field address/bot chain and adds queue/journal evidence. The [RE-241–243 announcement pass](research/AUTH_ANNOUNCEMENTS.md) recovers label tables and recipient paths; The [RE-245–247 cvar pass](research/GAME_CVARS.md) adds all 126 main registration rows and resolves auth_verbosity/callers. The [RE-249–251 change pass](research/CVAR_CHANGES.md) adds cache/notification behavior, four numeric correction branches and direct registration coverage. The [RE-253–255 query pass](research/AUTH_QUERIES.md) adds selector helpers, command-role routing and internal authentication modes. The [RE-257–259 bounds/entry pass](research/SELECTOR_BOUNDS.md) adds integer parsing, selector storage/count evidence and native/QVM/chat route connections. The [RE-261–263 chat pass](research/CHAT_PATHS.md) adds native command metadata/filter evidence and bounded message/recipient paths, including the documented argument-mapping correction. The [RE-265–267 delivery pass](research/CHAT_DELIVERY.md) adds bounded dead-chat predicates, formatter argument routing and echo/fanout calls. The [RE-269–271 expansion pass](research/CHAT_EXPANSION.md) adds marker states, twelve key comparisons and health/momo branches. The [RE-273–275 item/time pass](research/CHAT_ITEMS_TIME.md) adds packed ammunition fields, sampled names and countdown branches. The [RE-277–279 location pass](research/CHAT_LOCATIONS.md) adds leader selection, cache/format boundaries and location-list/spatial evidence. The [RE-281–283 crosshair pass](research/CHAT_CROSSHAIR.md) adds finite geometry fixtures, trace arguments and result routing. The [RE-285–287 native spatial pass](research/NATIVE_SPATIAL.md) confirms Linux trig/spatial services, VM pointer branches and collision/query evidence. The [RE-289–291 collision pass](research/NATIVE_COLLISION.md) adds native candidate filters, result decisions and model/field evidence. The [RE-293–295 composition pass](research/COLLISION_COMPOSITION.md) adds three-lane byte-copy/status agreement and world-result routing. The [RE-297–299 loop pass](research/COLLISION_LOOP.md) adds signed count/status gates, parity scheduling and related sentinel normalization. The [RE-301–303 area pass](research/AREA_QUERIES.md) adds registered address arithmetic, inclusive overlap and count/split ordering boundaries. PROV-002 remains open for full output/reset coverage. PROV-001 remains open for complete scheduling and platform coverage.
|
| 37 |
|
| 38 |
Start with the open questions raised by the current binary evidence. These tasks depend on the linked reports, not on a working rewrite.
|
| 39 |
|
docs/RESEARCH_LEDGER.md
CHANGED
|
@@ -161,3 +161,7 @@ Completed bounded RE-293–295 with `analyze_collision_composition.py`: paired c
|
|
| 161 |
## 2026-10-11 — Entity-clipping loop and sentinel normalization
|
| 162 |
|
| 163 |
Completed bounded RE-297–299 with `analyze_collision_loop.py`: paired verified graphs, 72 entry/20 normalization/360 continuation slices and 70 scheduling compositions, including supplied counts 1023/1024. LOOP-001–003/F-082–084 distinguish equality-only count guards from a proven runtime safety invariant and pass lookup requests from executed callees. Added restricted signed TEST/CMP branches, simple LEA/ADDQ/low-byte tests and opt-in register capture to the byte evaluator. Eight tests independently check parity/order/counter/pointer/status/sentinel behavior, paired outputs, flag preservation and ambiguous/unmodeled inputs. The full suite passes 206 tests; old/new collectors regenerate byte-identical reports, documentation links and storage policy checks pass. RE-296 remains open; RE-300 tracks count/order/lookup ownership, candidate/callee state and full collision/runtime/platform coverage. No original process, area query, entity lookup or collision callee executes.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
## 2026-10-11 — Entity-clipping loop and sentinel normalization
|
| 162 |
|
| 163 |
Completed bounded RE-297–299 with `analyze_collision_loop.py`: paired verified graphs, 72 entry/20 normalization/360 continuation slices and 70 scheduling compositions, including supplied counts 1023/1024. LOOP-001–003/F-082–084 distinguish equality-only count guards from a proven runtime safety invariant and pass lookup requests from executed callees. Added restricted signed TEST/CMP branches, simple LEA/ADDQ/low-byte tests and opt-in register capture to the byte evaluator. Eight tests independently check parity/order/counter/pointer/status/sentinel behavior, paired outputs, flag preservation and ambiguous/unmodeled inputs. The full suite passes 206 tests; old/new collectors regenerate byte-identical reports, documentation links and storage policy checks pass. RE-296 remains open; RE-300 tracks count/order/lookup ownership, candidate/callee state and full collision/runtime/platform coverage. No original process, area query, entity lookup or collision callee executes.
|
| 164 |
+
|
| 165 |
+
## 2026-10-11 — Entity addressing and area-query boundaries
|
| 166 |
+
|
| 167 |
+
Completed bounded/static RE-301–303 with `analyze_area_queries.py`: ten verified native graphs, 64 direct/24 reverse address traces, two registration slices, 432 overlap/48 insertion slices and 48 split compositions. AREA-001–003/F-085–087 distinguish registered game stride from fixed server stride, closed box overlap from strict tree splits, and equality guards from established safety. Eight tests check independent arithmetic/geometry/capacity/order oracles, registration widths, paired results and unknown/exceptional flag/address domains. The byte evaluator adds RIP/scaled addresses, multiply/sign-extension/shift/subtract/qword-test and finite comparison branches, with unmodeled flags rejected. The full suite passes 214 tests; new and prior collision reports regenerate byte-identically, and storage policy/documentation link checks pass. RE-300 remains open; RE-304 tracks complete registration/node/list/callee/count ownership, geometry/runtime and platform behavior. No original process, recursive query or recorded native callee executes.
|
docs/REVERSE_ENGINEERING_TODO.md
CHANGED
|
@@ -357,3 +357,8 @@ Purpose: turn the supplied installation into an evidence-backed specification of
|
|
| 357 |
- [x] RE-298 Recover three continuation count/pointer/status rules and bounded scheduling compositions through capacity boundary fixtures ([LOOP-002](research/COLLISION_LOOP.md)).
|
| 358 |
- [x] RE-299 Recover pass/related sentinel normalization and isolated lookup request boundaries ([LOOP-003](research/COLLISION_LOOP.md)).
|
| 359 |
- [ ] RE-300 Recover area-query count/order ownership and legal bounds, entity lookup arithmetic/validity, all candidate body/callee state effects, collision-produced status reachability and full geometry/original-runtime/all-platform scheduling conformance.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
- [x] RE-298 Recover three continuation count/pointer/status rules and bounded scheduling compositions through capacity boundary fixtures ([LOOP-002](research/COLLISION_LOOP.md)).
|
| 358 |
- [x] RE-299 Recover pass/related sentinel normalization and isolated lookup request boundaries ([LOOP-003](research/COLLISION_LOOP.md)).
|
| 359 |
- [ ] RE-300 Recover area-query count/order ownership and legal bounds, entity lookup arithmetic/validity, all candidate body/callee state effects, collision-produced status reachability and full geometry/original-runtime/all-platform scheduling conformance.
|
| 360 |
+
|
| 361 |
+
- [x] RE-301 Recover paired registered entity-address arithmetic, registration slot writes and exact stride-312 server-record correspondence ([AREA-001](research/AREA_QUERIES.md)).
|
| 362 |
+
- [x] RE-302 Recover finite inclusive candidate-box overlap and equality-only capacity/index insertion slices ([AREA-002](research/AREA_QUERIES.md)).
|
| 363 |
+
- [x] RE-303 Recover static local-list-before-children order and bounded strict split/leaf/child-order compositions ([AREA-003](research/AREA_QUERIES.md)).
|
| 364 |
+
- [ ] RE-304 Recover registered ABI callers/sizes/counts/lifetime and pointer/index validation, area node/list construction/relinking/order/duplicates, full recursive count/capacity propagation and callee state effects, exceptional floats and original geometry/runtime/all-platform query fixtures.
|
docs/research/AREA_QUERIES.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Native entity addressing and area-query boundaries
|
| 2 |
+
|
| 3 |
+
Static/bounded pass, 2026-10-11. Reproduce with `python3 scripts/analyze_area_queries.py`; check with `python3 scripts/test_area_queries.py`. [Generated evidence](area-queries.json) verifies both original Linux ELF64 engines and their disassembly, retains five graphs per engine, and records **64 direct entity-address traces, 24 server-to-game address traces, two registration slices, 432 overlap slices, 48 insertion slices and 48 split-traversal compositions**. This extends the [clipping count/sentinel work](COLLISION_LOOP.md).
|
| 4 |
+
|
| 5 |
+
No original process or native callee executes. Registered addresses/strides, bounds, count/capacity and recursive-return state are synthetic inputs. Comparisons accept only finite binary32 values. Negative/out-of-capacity examples demonstrate raw instruction behavior; they are not observed valid game states.
|
| 6 |
+
|
| 7 |
+
## AREA-001: registered game records and server-record addressing
|
| 8 |
+
|
| 9 |
+
SV_LocateGameData stores its incoming arguments into five adjacent native globals. The report's isolated registration slice supplies illustrative values rather than executing a QVM registration call:
|
| 10 |
+
|
| 11 |
+
| Global relative to entity base slot | Width | Incoming register | Supplied fixture value |
|
| 12 |
+
| --- | --- | --- | --- |
|
| 13 |
+
| +0 | 8 | rdi | 0x100000000 |
|
| 14 |
+
| +8 | 4 | edx | 880 |
|
| 15 |
+
| +12 | 4 | esi | 1024 |
|
| 16 |
+
| +16 | 8 | rcx | 0x200000000 |
|
| 17 |
+
| +24 | 4 | r8d | 256 |
|
| 18 |
+
|
| 19 |
+
The first two slots are used directly by SV_GentityNum. The helper computes `product = low32(index * registered_stride)`, sign-extends product to 64 bits, then adds the registered base modulo 2^64. **It has no range/null/stride check in its four-instruction body.** Multiplication occurs before sign extension; widening index/stride first in Rust would differ on overflow. The 32 cases per engine cover indices 0, 1, 7, 1023, 1024, -1 and signed extremes with stride bits 880, 312, 0 and -1. These raw inputs do not establish legal entities, actual registered sizes or valid memory.
|
| 20 |
+
|
| 21 |
+
SV_GEntityForSvEntity subtracts the fixed server-record base, arithmetic-shifts the byte difference right by three, then uses low-width multiplication by registered game stride and `0x96f96f97` before sign extension/base addition. The area insertion path similarly shifts the difference by three and multiplies by `0x6f96f96f96f96f97` before storing its low 32 bits. The constants are modular inverses of 39 in their respective widths: **312 = 8 * 39**. For tested pointers exactly `server_base + 312*i`, the recovered game pointer is `game_base + registered_stride*i` and the emitted index is i. No general signed division, misaligned-pointer validation or full array extent is inferred from these exact-record fixtures.
|
| 22 |
+
|
| 23 |
+
The dedicated globals are base slot **0x86da30**, stride slot **0x86da38**, fixed server-record base **0x81fa28**. Client addresses are preserved separately in the report. Native registration callers, QVM-provided record identities/counts, allocation lifetime and all-platform arithmetic remain open.
|
| 24 |
+
|
| 25 |
+
## AREA-002: inclusive overlap and equality-only capacity guard
|
| 26 |
+
|
| 27 |
+
SV_AreaEntities statically constructs a 32-byte query record: min pointer +0, max pointer +8, output pointer +16, count +24 initialized zero, and capacity +28 from incoming ecx. It passes that record to SV_AreaEntities_r, then returns its count. The clipping caller builds the same field sequence in its larger stack frame with capacity 1024. These are code/data relationships, not executed allocation or traversal proofs.
|
| 28 |
+
|
| 29 |
+
For a supplied returned game object, the recursive helper rejects if any object +0x1d0/+0x1d4/+0x1d8 component is **greater than** the corresponding query maximum, or any +0x1dc/+0x1e0/+0x1e4 component is **less than** the corresponding query minimum. The observed finite overlap therefore includes faces, edges and corners at equality. All 216 combinations per engine use a query [-1,1]^3 and six intervals per axis, covering interior, crossing, exact touching and separation on each axis. The field names min/max are supported by these predicates; full structure/producers remain open.
|
| 30 |
+
|
| 31 |
+
An accepted candidate reads count and checks **count == capacity** before computing/storing the index. Equality enters the MAXCOUNT warning path without output or count writes. Otherwise it writes one four-byte index to output +4*sign_extend(count), then increments count modulo 2^32. Twenty-four insertion cases per engine include equality, just below capacity, and deliberately invalid above-capacity/negative-count fixtures. Above capacity does not trigger the guard; negative count can select storage before the supplied output base. This is not a claim those states occur normally.
|
| 32 |
+
|
| 33 |
+
The guard bounds writes under a valid initial count, nonnegative capacity, valid array and unchanged query-state invariant. Recover those owners and mutation paths before claiming runtime memory safety. The warning path returns from the current recursive invocation; it does not supply a global abort status to its caller, which can still consider another child after return. Full capacity propagation and warning multiplicity remain open.
|
| 34 |
+
|
| 35 |
+
## AREA-003: local list order and strict split traversal
|
| 36 |
+
|
| 37 |
+
At each visited tree node, the native code starts with linked-list head node +0x18. Before requesting SV_GEntityForSvEntity for a record, it saves record +8 as the next pointer; after overlap/insertion it advances to that saved pointer. Static order is therefore **node's linked records before child traversal**, with no sort in this helper. Link creation, insertion direction, deletion/relinking, duplicate membership and actual map candidate order remain unresolved.
|
| 38 |
+
|
| 39 |
+
After the local list, node word +0 is interpreted as split axis. **-1** returns immediately. Supplied axes 0/1/2 address query components with a four-byte scale; arbitrary axes are not validated in these slices.
|
| 40 |
+
|
| 41 |
+
- Query maximum > node float +4 requests a recursive call on child pointer +8 (child 0).
|
| 42 |
+
- After that supplied return, or when maximum <= split, query minimum < split follows child pointer +16 (child 1) through a tail loop.
|
| 43 |
+
- Minimum >= split skips child 1. Equality does not visit that child; maximum equality does not visit child 0.
|
| 44 |
+
|
| 45 |
+
The 24 compositions per engine cover leaf/three axes and below, touching, degenerate-on-plane, crossing and above bounds around split zero. If both children qualify, child 0 precedes child 1. Each recursion is stopped before its call; a separate post-call slice supplies the preserved node/query registers. This is a **split-decision composition**, not execution of an entire tree or proof of callee state preservation. A query exactly on the split skips both children after processing the local list; its correctness depends on unrecovered entity-to-node placement rules.
|
| 46 |
+
|
| 47 |
+
## Reproduction limits and Rust implications
|
| 48 |
+
|
| 49 |
+
The byte-addressed evaluator now supports RIP-relative addresses using the next instruction address, scaled indexed addresses, low-width multiplication, sign extension, arithmetic right shift, subtraction, qword tests and finite greater/less branches. Unknown memory/registers, malformed addressing, nonfinite comparisons, unmodeled flags and step escape fail closed. Multiply/shift invalidate flag knowledge; tests reject subsequent unmodeled conditional branches. Old collision reports regenerate unchanged.
|
| 50 |
+
|
| 51 |
+
Independent fixtures check modular address arithmetic, the registered field write widths, overlap equations, capacity writes, split order/leaf stop, paired engine outputs and evaluator failure boundaries. The collector retains source hashes and exact graphs for each binary; it does not infer Windows/macOS/Linux32 equivalence.
|
| 52 |
+
|
| 53 |
+
For the Rust collision contract, keep broad-phase box overlap distinct from split traversal, define candidate ordering explicitly, validate registered record/index/array bounds, and preserve observed 32-bit arithmetic only where required by verified behavior. RE-304 tracks complete node/list construction and query composition, full registered ABI/lifetime/callee effects, exceptional floats, original geometry/runtime fixtures and other-platform verification. No broad game/asset/port coverage task is completed by these slices alone.
|
docs/research/README.md
CHANGED
|
@@ -105,3 +105,5 @@ The extension collector adds five focused tests for native tail calls, command-r
|
|
| 105 |
[Collision copy/status composition and world decisions](COLLISION_COMPOSITION.md) adds RE-293–295: all three bounded merge/copy lanes, byte-addressed payload/status preservation and world sentinel/zero-fraction routing. Reproduce with `python3 scripts/analyze_collision_composition.py`; check with `python3 scripts/test_collision_composition.py`. Whole-loop/geometry/exception/runtime/platform behavior remains open.
|
| 106 |
|
| 107 |
[Entity-clipping loop and sentinel normalization](COLLISION_LOOP.md) adds RE-297–299: signed count/status entry gates, parity-selected lanes, bounded continuation/scheduling compositions and pass/related sentinel normalization. Reproduce with `python3 scripts/analyze_collision_loop.py`; check with `python3 scripts/test_collision_loop.py`. Count/order/lookup ownership, candidate/callee/geometry behavior and runtime/platform conformance remain open.
|
|
|
|
|
|
|
|
|
| 105 |
[Collision copy/status composition and world decisions](COLLISION_COMPOSITION.md) adds RE-293–295: all three bounded merge/copy lanes, byte-addressed payload/status preservation and world sentinel/zero-fraction routing. Reproduce with `python3 scripts/analyze_collision_composition.py`; check with `python3 scripts/test_collision_composition.py`. Whole-loop/geometry/exception/runtime/platform behavior remains open.
|
| 106 |
|
| 107 |
[Entity-clipping loop and sentinel normalization](COLLISION_LOOP.md) adds RE-297–299: signed count/status entry gates, parity-selected lanes, bounded continuation/scheduling compositions and pass/related sentinel normalization. Reproduce with `python3 scripts/analyze_collision_loop.py`; check with `python3 scripts/test_collision_loop.py`. Count/order/lookup ownership, candidate/callee/geometry behavior and runtime/platform conformance remain open.
|
| 108 |
+
|
| 109 |
+
[Entity addressing and area-query boundaries](AREA_QUERIES.md) adds RE-301–303: registered low-width addressing, inclusive overlap/capacity insertion and static list/strict split ordering. Reproduce with `python3 scripts/analyze_area_queries.py`; check with `python3 scripts/test_area_queries.py`. Complete node/list/count/ABI ownership, query composition and runtime/platform conformance remain open.
|
docs/research/area-queries.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
scripts/analyze_area_queries.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Bounded entity addressing, area overlap/insertion and split traversal evidence."""
|
| 3 |
+
import gzip
|
| 4 |
+
import itertools
|
| 5 |
+
import json
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
import re
|
| 8 |
+
from analyze_chat_crosshair import bits
|
| 9 |
+
from analyze_collision_composition import evaluate, get_word, put_word
|
| 10 |
+
from analyze_engine_control import control_flow
|
| 11 |
+
from analyze_qvm import write_json
|
| 12 |
+
from analyze_vm_extensions import native_functions, verified
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def site(g,op,args):
|
| 16 |
+
matches=[x['address'] for x in g['instructions'] if x['opcode']==op and x['operands'].split('#')[0].strip()==args]
|
| 17 |
+
if len(matches)!=1:raise ValueError('ambiguous area site '+args)
|
| 18 |
+
return matches[0]
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def run(g,start,stops,m,regs):return evaluate(g,start,stops,m,regs,capture_registers=True)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def main():
|
| 25 |
+
out={'native':{},'limitations':'Bounded synthetic instructions with supplied pointers, registered fields, '
|
| 26 |
+
'bounds/counts and recursive-return state. No original process/native callee executes. Finite floats '
|
| 27 |
+
'only; linked-list/tree construction, actual query composition, pointer validity, ABI lifetimes, '
|
| 28 |
+
'geometry and original-runtime/other-platform behavior remain open.'}
|
| 29 |
+
for label in ['linux--Quake3-UrT-Ded.x86_64','linux--Quake3-UrT.x86_64']:
|
| 30 |
+
r=json.loads(Path(f'docs/research/native/{label}.json').read_text())
|
| 31 |
+
verified((Path('.cache/UrbanTerror43')/r['source']['path']).read_bytes(),r['source']['sha256'])
|
| 32 |
+
a=r['artifacts']['disassembly.txt'];fs=native_functions(verified(gzip.open(a['path'],'rb').read(),a['uncompressed_sha256']).decode())
|
| 33 |
+
graphs={n:control_flow(fs[n]) for n in ['SV_GentityNum','SV_GEntityForSvEntity','SV_LocateGameData','SV_AreaEntities','SV_AreaEntities_r']}
|
| 34 |
+
def global_address(n,op):
|
| 35 |
+
x=next(x for x in graphs[n]['instructions'] if x['opcode']==op and '(%rip)' in x['operands'])
|
| 36 |
+
return int(re.search(r'#\s*0x([0-9a-f]+)',x['operands'])[1],16)
|
| 37 |
+
stride_address=global_address('SV_GentityNum','imull');base_address=global_address('SV_GentityNum','addq')
|
| 38 |
+
g=graphs['SV_GEntityForSvEntity'];svbase=int(next(x for x in g['instructions'] if x['opcode']=='subq')['operands'].split(',')[0][1:],0)
|
| 39 |
+
entity=[]
|
| 40 |
+
for index,stride in itertools.product([0,1,7,1023,1024,0xffffffff,0x7fffffff,0x80000000],[880,312,0,0xffffffff]):
|
| 41 |
+
m={};put_word(m,stride_address,stride);put_word(m,base_address,0x100000000,8)
|
| 42 |
+
g=graphs['SV_GentityNum'];stop=next(x['address'] for x in g['instructions'] if x['opcode']=='retq')
|
| 43 |
+
t=run(g,g['entry'],[stop],m,{'%rdi':index})
|
| 44 |
+
entity.append({'index_bits':index,'stride_bits':stride,'base':0x100000000,'result':t['registers']['rax'],'trace':t['trace']})
|
| 45 |
+
reverse=[]
|
| 46 |
+
g=graphs['SV_GEntityForSvEntity'];stop=next(x['address'] for x in g['instructions'] if x['opcode']=='retq')
|
| 47 |
+
for index,stride in itertools.product([-1,0,1,7,1023,1024],[880,312]):
|
| 48 |
+
m={};put_word(m,stride_address,stride);put_word(m,base_address,0x100000000,8)
|
| 49 |
+
t=run(g,g['entry'],[stop],m,{'%rdi':svbase+312*index})
|
| 50 |
+
reverse.append({'sv_record_index':index,'sv_pointer':svbase+312*index,'stride':stride,'base':0x100000000,'result':t['registers']['rax'],'trace':t['trace']})
|
| 51 |
+
g=graphs['SV_LocateGameData'];stop=next(x['address'] for x in g['instructions'] if x['opcode']=='retq')
|
| 52 |
+
registration=run(g,g['entry'],[stop],{}, {'%rdi':0x100000000,'%rsi':1024,'%rdx':880,'%rcx':0x200000000,'%r8':256})
|
| 53 |
+
g=graphs['SV_AreaEntities_r'];rows=g['instructions']
|
| 54 |
+
overlap_start=site(g,'movq','0x8(%rbp), %rdx');accept=site(g,'movl','0x18(%rbp), %eax');reject=site(g,'testq','%r12, %r12')
|
| 55 |
+
count_cmp=site(g,'cmpl','0x1c(%rbp), %eax');i=next(i for i,x in enumerate(rows) if x['address']==count_cmp)
|
| 56 |
+
if rows[i+1]['opcode']!='je':raise ValueError('capacity branch changed')
|
| 57 |
+
warning=int(rows[i+1]['operands'].split()[0],16)
|
| 58 |
+
split_sites=[i for i,x in enumerate(rows[:-1]) if x['opcode']=='movl' and x['operands']=='(%r13), %ecx' and rows[i+1]['operands']=='$-0x1, %ecx']
|
| 59 |
+
if len(split_sites)!=1:raise ValueError('ambiguous split site')
|
| 60 |
+
i=split_sites[0];split_start=rows[i]['address']
|
| 61 |
+
if rows[i+2]['opcode']!='je':raise ValueError('leaf branch changed')
|
| 62 |
+
leaf_exit=int(rows[i+2]['operands'].split()[0],16)
|
| 63 |
+
tail=site(g,'movq','0x18(%r13), %rbx')
|
| 64 |
+
recursive=[i for i,x in enumerate(rows) if '<SV_AreaEntities_r>' in x['operands'] and x['opcode']=='callq']
|
| 65 |
+
if len(recursive)!=1:raise ValueError('recursive site changed')
|
| 66 |
+
call=rows[recursive[0]]['address'];after_call=rows[recursive[0]+1]['address']
|
| 67 |
+
query,obj,lo,hi,output,node=0x20000,0x30000,0x40000,0x40020,0x50000,0x60000
|
| 68 |
+
def query_memory(minimum,maximum):
|
| 69 |
+
m={};put_word(m,query,lo,8);put_word(m,query+8,hi,8);put_word(m,query+16,output,8)
|
| 70 |
+
for axis in range(3):put_word(m,lo+4*axis,bits(minimum[axis]));put_word(m,hi+4*axis,bits(maximum[axis]))
|
| 71 |
+
return m
|
| 72 |
+
overlap=[]
|
| 73 |
+
choices=[(-2,-1),(-1,1),(1,2),(1.01,2),(-2,-1.01),(-.5,.5)]
|
| 74 |
+
for bounds in itertools.product(choices,repeat=3):
|
| 75 |
+
m=query_memory([-1]*3,[1]*3)
|
| 76 |
+
for axis,(minimum,maximum) in enumerate(bounds):put_word(m,obj+0x1d0+4*axis,bits(minimum));put_word(m,obj+0x1dc+4*axis,bits(maximum))
|
| 77 |
+
t=run(g,overlap_start,[accept,reject],m,{'%rbp':query,'%rax':obj})
|
| 78 |
+
overlap.append({'entity_min':[x[0] for x in bounds],'entity_max':[x[1] for x in bounds], 'accepted':t['stop']==accept,'trace':t['trace']})
|
| 79 |
+
insertion=[]
|
| 80 |
+
magic=next(x for x in rows if x['opcode']=='movabsq')['operands'].split(',')[0][1:]
|
| 81 |
+
for count,capacity in [(0,0),(0,1),(1,1),(1,2),(1023,1024),(1024,1024),(1025,1024),(-1,1024)]:
|
| 82 |
+
for index in [0,1,1023]:
|
| 83 |
+
m={output-1:0xab,output+4096:0xcd};put_word(m,query+16,output,8);put_word(m,query+24,count);put_word(m,query+28,capacity)
|
| 84 |
+
t=run(g,accept,[reject,warning],m,{'%rbp':query,'%rbx':svbase+312*index,'%r14':int(magic,0)})
|
| 85 |
+
insertion.append({'count_before':count,'capacity':capacity,'sv_record_index':index,'warning_path':t['stop']==warning,
|
| 86 |
+
'count_after':get_word(t['memory'],query+24),'output_writes':[{'address':w['address'],'bytes':w['bytes'],'value':get_word(t['memory'],w['address'],w['bytes'])} for w in t['writes'] if w['address']!=query+24],
|
| 87 |
+
'trace':t['trace']})
|
| 88 |
+
traversal=[]
|
| 89 |
+
for axis in [-1,0,1,2]:
|
| 90 |
+
for minimum,maximum in [(-2,-1),(-1,0),(0,0),(0,1),(-1,1),(1,2)]:
|
| 91 |
+
m=query_memory([minimum]*3,[maximum]*3);put_word(m,node,axis);put_word(m,node+4,bits(0));put_word(m,node+8,node+0x100,8);put_word(m,node+16,node+0x200,8)
|
| 92 |
+
regs={'%r13':node,'%rbp':query};t=run(g,split_start,[call,tail,leaf_exit],m,regs);trace=t['trace'];children=[]
|
| 93 |
+
if t['stop']==call:
|
| 94 |
+
children.append(0)
|
| 95 |
+
# Isolated return boundary: no recursive callee runs; preserved node/query are supplied.
|
| 96 |
+
t=run(g,after_call,[tail,leaf_exit],m,{'%r13':node,'%rbp':query});trace+=t['trace']
|
| 97 |
+
if t['stop']==tail:children.append(1)
|
| 98 |
+
traversal.append({'axis':axis,'query_min':minimum,'query_max':maximum,'split':0,'children':children,'trace':trace})
|
| 99 |
+
out['native'][label]={'source':r['source'],'disassembly_sha256':a['uncompressed_sha256'],'graphs':graphs,
|
| 100 |
+
'globals':{'entity_base':base_address,'entity_stride':stride_address,'sv_record_base':svbase},
|
| 101 |
+
'entity_address_traces':entity,'sv_to_game_address_traces':reverse,
|
| 102 |
+
'registration':{'registers':registration['registers'],'trace':registration['trace'],'writes':[{'address':w['address'],'bytes':w['bytes'],'value':get_word(registration['memory'],w['address'],w['bytes'])} for w in registration['writes']]},
|
| 103 |
+
'sites':{'overlap':overlap_start,'accept':accept,'reject':reject,'capacity_warning':warning,'split':split_start,'child0_call':call,'after_child0_call':after_call,'child1_tail':tail,'leaf_exit':leaf_exit},
|
| 104 |
+
'overlap_traces':overlap,'insertion_traces':insertion,'split_traversal_compositions':traversal}
|
| 105 |
+
write_json(Path('docs/research/area-queries.json'),out)
|
| 106 |
+
print('Recovered 64 entity/24 reverse-address, 432 overlap, 48 insertion and 48 split cases')
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
if __name__=='__main__':main()
|
scripts/analyze_collision_composition.py
CHANGED
|
@@ -17,7 +17,7 @@ def register(r):
|
|
| 17 |
r=r.lstrip('%')
|
| 18 |
if r in aliases:return aliases[r]
|
| 19 |
if re.fullmatch(r'r\d+d',r):return r[:-1],32
|
| 20 |
-
if re.fullmatch(r'r(?:ax|di|si|dx|cx|bp|bx|sp|\d+)',r):return r,64
|
| 21 |
if re.fullmatch(r'xmm\d+',r):return r,32
|
| 22 |
raise ValueError('unsupported register '+r)
|
| 23 |
|
|
@@ -33,15 +33,15 @@ def get_word(m,address,width=4):
|
|
| 33 |
|
| 34 |
def evaluate(graph,start,stops,memory,registers,limit=128,capture_registers=False):
|
| 35 |
rows=graph['instructions'];indices={x['address']:i for i,x in enumerate(rows)}
|
| 36 |
-
regs={register(k)[0]:v for k,v in registers.items()};m=dict(memory);equal=ge=
|
| 37 |
def read_reg(name):
|
| 38 |
key,size=register(name)
|
| 39 |
if key not in regs:raise ValueError('unknown register')
|
| 40 |
return regs[key]&((1<<size)-1)
|
| 41 |
def address(arg):
|
| 42 |
-
match=re.fullmatch(r'(-?0x[0-9a-f]+)?\((%\w+)\)',arg)
|
| 43 |
if not match:raise ValueError('unsupported memory operand')
|
| 44 |
-
return read_reg(match[2])+int(match[1] or '0',0)
|
| 45 |
def read(arg,width):
|
| 46 |
if arg.startswith('$'):return int(arg[1:],0)&((1<<(width*8))-1)
|
| 47 |
if arg.startswith('%'):return read_reg(arg)
|
|
@@ -58,39 +58,56 @@ def evaluate(graph,start,stops,memory,registers,limit=128,capture_registers=Fals
|
|
| 58 |
if pc not in indices or len(trace)>=limit:raise ValueError('composition slice escaped bound')
|
| 59 |
x=rows[indices[pc]];trace.append(pc);op=x['opcode'];args=x['operands'].split('#')[0].strip()
|
| 60 |
i=indices[pc];pc=rows[i+1]['address'] if i+1<len(rows) else None
|
|
|
|
| 61 |
if op=='nop':pass
|
| 62 |
-
elif op in ['movl','movq','movss']:
|
| 63 |
-
a,b=args.split(', ');width=8 if op
|
|
|
|
|
|
|
| 64 |
elif op in ['leal','leaq']:
|
| 65 |
a,b=args.split(', ');write(b,address(a),4 if op=='leal' else 8)
|
| 66 |
-
elif op in ['testl','testb','cmpl']:
|
| 67 |
-
width=1 if op=='testb' else 4
|
| 68 |
a,b=args.split(', ');v,w=read(a,width),read(b,width)
|
| 69 |
def signed(value):
|
| 70 |
sign=1<<(width*8-1)
|
| 71 |
return (value^sign)-sign
|
| 72 |
equal=v==w if op=='cmpl' else (v&w)==0
|
| 73 |
signed_le=signed(w)<=signed(v) if op=='cmpl' else signed(v&w)<=0
|
| 74 |
-
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
a,b=args.split(', ');v,w=read(a,width),read(b,width)
|
| 77 |
-
result=v^w if op=='xorl' else v|w if op=='orl' else v+w
|
| 78 |
write(b,result,width);equal=(result&((1<<(width*8))-1))==0
|
| 79 |
-
signed_le=None
|
| 80 |
elif op=='xorps':
|
| 81 |
a,b=args.split(', ')
|
| 82 |
if a!=b:raise ValueError('unsupported SIMD xor')
|
| 83 |
write(b,0,4) # Does not change integer flags.
|
| 84 |
elif op=='comiss':
|
| 85 |
-
a,b=args.split(', ');v,w=number(read(a,4)),number(read(b,4));equal=v==w;ge=w>=v;signed_le=None
|
| 86 |
-
elif op=='sete':
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
| 88 |
if op=='jle' and signed_le is None:raise ValueError('unmodeled signed flags')
|
| 89 |
-
|
|
|
|
| 90 |
if taken:pc=int(args.split()[0],16)
|
| 91 |
else:raise ValueError('unsupported composition opcode '+op)
|
| 92 |
result={'trace':trace,'stop':pc,'memory':m,'writes':writes}
|
| 93 |
-
if capture_registers:result['registers']=regs
|
| 94 |
return result
|
| 95 |
|
| 96 |
|
|
|
|
| 17 |
r=r.lstrip('%')
|
| 18 |
if r in aliases:return aliases[r]
|
| 19 |
if re.fullmatch(r'r\d+d',r):return r[:-1],32
|
| 20 |
+
if re.fullmatch(r'r(?:ax|di|si|dx|cx|bp|bx|sp|ip|\d+)',r):return r,64
|
| 21 |
if re.fullmatch(r'xmm\d+',r):return r,32
|
| 22 |
raise ValueError('unsupported register '+r)
|
| 23 |
|
|
|
|
| 33 |
|
| 34 |
def evaluate(graph,start,stops,memory,registers,limit=128,capture_registers=False):
|
| 35 |
rows=graph['instructions'];indices={x['address']:i for i,x in enumerate(rows)}
|
| 36 |
+
regs={register(k)[0]:v for k,v in registers.items()};m=dict(memory);equal=None;ge=greater=below=None;signed_le=None;pc=start;trace=[];writes=[]
|
| 37 |
def read_reg(name):
|
| 38 |
key,size=register(name)
|
| 39 |
if key not in regs:raise ValueError('unknown register')
|
| 40 |
return regs[key]&((1<<size)-1)
|
| 41 |
def address(arg):
|
| 42 |
+
match=re.fullmatch(r'(-?0x[0-9a-f]+)?\((%\w+)(?:,(%\w+),([1248]))?\)',arg)
|
| 43 |
if not match:raise ValueError('unsupported memory operand')
|
| 44 |
+
return (read_reg(match[2])+int(match[1] or '0',0)+(read_reg(match[3])*int(match[4]) if match[3] else 0))&((1<<64)-1)
|
| 45 |
def read(arg,width):
|
| 46 |
if arg.startswith('$'):return int(arg[1:],0)&((1<<(width*8))-1)
|
| 47 |
if arg.startswith('%'):return read_reg(arg)
|
|
|
|
| 58 |
if pc not in indices or len(trace)>=limit:raise ValueError('composition slice escaped bound')
|
| 59 |
x=rows[indices[pc]];trace.append(pc);op=x['opcode'];args=x['operands'].split('#')[0].strip()
|
| 60 |
i=indices[pc];pc=rows[i+1]['address'] if i+1<len(rows) else None
|
| 61 |
+
regs['rip']=pc
|
| 62 |
if op=='nop':pass
|
| 63 |
+
elif op in ['movl','movq','movss','movabsq']:
|
| 64 |
+
a,b=args.split(', ');width=8 if op in ['movq','movabsq'] else 4;write(b,read(a,width),width)
|
| 65 |
+
elif op=='movslq':
|
| 66 |
+
a,b=args.split(', ');v=read(a,4);write(b,v if v<2**31 else v-2**32,8)
|
| 67 |
elif op in ['leal','leaq']:
|
| 68 |
a,b=args.split(', ');write(b,address(a),4 if op=='leal' else 8)
|
| 69 |
+
elif op in ['testl','testb','testq','cmpl']:
|
| 70 |
+
width=1 if op=='testb' else 8 if op=='testq' else 4
|
| 71 |
a,b=args.split(', ');v,w=read(a,width),read(b,width)
|
| 72 |
def signed(value):
|
| 73 |
sign=1<<(width*8-1)
|
| 74 |
return (value^sign)-sign
|
| 75 |
equal=v==w if op=='cmpl' else (v&w)==0
|
| 76 |
signed_le=signed(w)<=signed(v) if op=='cmpl' else signed(v&w)<=0
|
| 77 |
+
ge=greater=below=None
|
| 78 |
+
elif op in ['imull','imulq']:
|
| 79 |
+
width=8 if op=='imulq' else 4;parts=args.split(', ')
|
| 80 |
+
if len(parts)==2:a,b=parts;v,w=read(a,width),read(b,width);dest=b
|
| 81 |
+
elif len(parts)==3:a,b,dest=parts;v,w=read(a,width),read(b,width)
|
| 82 |
+
else:raise ValueError('unsupported multiply operands')
|
| 83 |
+
write(dest,v*w,width);equal=None;signed_le=None;ge=greater=below=None
|
| 84 |
+
elif op=='sarq':
|
| 85 |
+
a,b=args.split(', ');v=read(b,8);v=v if v<2**63 else v-2**64
|
| 86 |
+
write(b,v>>(read(a,8)&63),8);equal=None;signed_le=None;ge=greater=below=None
|
| 87 |
+
elif op in ['xorl','orl','addl','addq','subq']:
|
| 88 |
+
width=8 if op in ['addq','subq'] else 4
|
| 89 |
a,b=args.split(', ');v,w=read(a,width),read(b,width)
|
| 90 |
+
result=v^w if op=='xorl' else v|w if op=='orl' else w-v if op=='subq' else v+w
|
| 91 |
write(b,result,width);equal=(result&((1<<(width*8))-1))==0
|
| 92 |
+
signed_le=None;ge=greater=below=None
|
| 93 |
elif op=='xorps':
|
| 94 |
a,b=args.split(', ')
|
| 95 |
if a!=b:raise ValueError('unsupported SIMD xor')
|
| 96 |
write(b,0,4) # Does not change integer flags.
|
| 97 |
elif op=='comiss':
|
| 98 |
+
a,b=args.split(', ');v,w=number(read(a,4)),number(read(b,4));equal=v==w;ge=w>=v;greater=w>v;below=w<v;signed_le=None
|
| 99 |
+
elif op=='sete':
|
| 100 |
+
if equal is None:raise ValueError('unmodeled zero flag')
|
| 101 |
+
write(args,int(equal),1)
|
| 102 |
+
elif op in ['je','jne','jae','ja','jb','jle','jmp']:
|
| 103 |
+
if op in ['je','jne'] and equal is None:raise ValueError('unmodeled zero flag')
|
| 104 |
if op=='jle' and signed_le is None:raise ValueError('unmodeled signed flags')
|
| 105 |
+
if op in ['jae','ja','jb'] and ge is None:raise ValueError('unmodeled unsigned flags')
|
| 106 |
+
taken=op=='jmp' or (equal if op=='je' else not equal if op=='jne' else signed_le if op=='jle' else greater if op=='ja' else below if op=='jb' else ge)
|
| 107 |
if taken:pc=int(args.split()[0],16)
|
| 108 |
else:raise ValueError('unsupported composition opcode '+op)
|
| 109 |
result={'trace':trace,'stop':pc,'memory':m,'writes':writes}
|
| 110 |
+
if capture_registers:result['registers']={k:v for k,v in regs.items() if k!='rip'}
|
| 111 |
return result
|
| 112 |
|
| 113 |
|
scripts/test_area_queries.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Independent finite geometry/address/count oracles for the bounded area slices."""
|
| 3 |
+
import json
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
import unittest
|
| 6 |
+
from analyze_collision_composition import evaluate,get_word
|
| 7 |
+
from test_collision_composition import graph
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def signed32(x):return x if x<2**31 else x-2**32
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class AreaQueryTests(unittest.TestCase):
|
| 14 |
+
@classmethod
|
| 15 |
+
def setUpClass(cls):cls.r=json.loads(Path('docs/research/area-queries.json').read_text())
|
| 16 |
+
|
| 17 |
+
def test_registered_entity_address_uses_wrapped_signed_product(self):
|
| 18 |
+
for n in self.r['native'].values():
|
| 19 |
+
self.assertEqual(len(n['entity_address_traces']),32)
|
| 20 |
+
for x in n['entity_address_traces']:
|
| 21 |
+
expected=(x['base']+signed32((x['index_bits']*x['stride_bits'])&0xffffffff))&((1<<64)-1)
|
| 22 |
+
self.assertEqual(x['result'],expected)
|
| 23 |
+
self.assertTrue(any(x['index_bits']==1024 for x in n['entity_address_traces']))
|
| 24 |
+
|
| 25 |
+
def test_stride312_reverse_address_and_registration_fields(self):
|
| 26 |
+
self.assertEqual((39*0x96f96f97)%(2**32),1)
|
| 27 |
+
self.assertEqual((39*0x6f96f96f96f96f97)%(2**64),1)
|
| 28 |
+
for n in self.r['native'].values():
|
| 29 |
+
self.assertEqual(len(n['sv_to_game_address_traces']),12)
|
| 30 |
+
for x in n['sv_to_game_address_traces']:
|
| 31 |
+
self.assertEqual(x['sv_pointer'],n['globals']['sv_record_base']+312*x['sv_record_index'])
|
| 32 |
+
self.assertEqual(x['result'],x['base']+x['stride']*x['sv_record_index'])
|
| 33 |
+
writes=n['registration']['writes'];base=n['globals']['entity_base']
|
| 34 |
+
self.assertEqual([(x['address']-base,x['bytes'],x['value']) for x in writes],
|
| 35 |
+
[(0,8,0x100000000),(8,4,880),(12,4,1024),(16,8,0x200000000),(24,4,256)])
|
| 36 |
+
|
| 37 |
+
def test_closed_overlap_including_faces_edges_and_corners(self):
|
| 38 |
+
for n in self.r['native'].values():
|
| 39 |
+
self.assertEqual(len(n['overlap_traces']),216)
|
| 40 |
+
for x in n['overlap_traces']:
|
| 41 |
+
expected=all(low<=1 and high>=-1 for low,high in zip(x['entity_min'],x['entity_max']))
|
| 42 |
+
self.assertEqual(x['accepted'],expected)
|
| 43 |
+
touching=next(x for x in n['overlap_traces'] if x['entity_min']==[1,1,1])
|
| 44 |
+
self.assertTrue(touching['accepted'])
|
| 45 |
+
|
| 46 |
+
def test_capacity_equality_and_four_byte_index_insertion(self):
|
| 47 |
+
for n in self.r['native'].values():
|
| 48 |
+
self.assertEqual(len(n['insertion_traces']),24)
|
| 49 |
+
for x in n['insertion_traces']:
|
| 50 |
+
stopped=x['count_before']==x['capacity']
|
| 51 |
+
self.assertEqual(x['warning_path'],stopped)
|
| 52 |
+
self.assertEqual(x['count_after'],(x['count_before']+int(not stopped))&0xffffffff)
|
| 53 |
+
expected=[] if stopped else [{'address':0x50000+4*x['count_before'],'bytes':4,'value':x['sv_record_index']}]
|
| 54 |
+
self.assertEqual(x['output_writes'],expected)
|
| 55 |
+
|
| 56 |
+
def test_strict_split_boundaries_child_order_and_leaf_stop(self):
|
| 57 |
+
for n in self.r['native'].values():
|
| 58 |
+
self.assertEqual(len(n['split_traversal_compositions']),24)
|
| 59 |
+
for x in n['split_traversal_compositions']:
|
| 60 |
+
expected=[] if x['axis']==-1 else ([0] if x['query_max']>0 else [])+([1] if x['query_min']<0 else [])
|
| 61 |
+
self.assertEqual(x['children'],expected)
|
| 62 |
+
if expected==[0,1]:self.assertIn(n['sites']['after_child0_call'],x['trace'])
|
| 63 |
+
rows=n['graphs']['SV_AreaEntities_r']['instructions']
|
| 64 |
+
call=next(i for i,x in enumerate(rows) if '<SV_GEntityForSvEntity>' in x['operands'])
|
| 65 |
+
self.assertEqual(rows[call-1]['operands'],'0x8(%rbx), %r12')
|
| 66 |
+
self.assertLess(n['sites']['overlap'],n['sites']['split'])
|
| 67 |
+
|
| 68 |
+
def test_paired_engines_agree_on_bounded_outputs(self):
|
| 69 |
+
a,b=self.r['native'].values()
|
| 70 |
+
for key in ['entity_address_traces','sv_to_game_address_traces','overlap_traces','insertion_traces','split_traversal_compositions']:
|
| 71 |
+
def outputs(n):return [{k:v for k,v in x.items() if k not in ['trace','sv_pointer']} for x in n[key]]
|
| 72 |
+
self.assertEqual(outputs(a),outputs(b))
|
| 73 |
+
|
| 74 |
+
def test_indexed_address_sign_extension_and_shift_boundaries(self):
|
| 75 |
+
g=graph(('movslq','%edi, %rax'),('sarq','$0x3, %rax'),('movl','$0x123, (%rbx,%rax,4)'))
|
| 76 |
+
t=evaluate(g,1,[4],{}, {'%rdi':0xfffffff8,'%rbx':0x10000},capture_registers=True)
|
| 77 |
+
self.assertEqual(get_word(t['memory'],0xfffc),0x123)
|
| 78 |
+
self.assertEqual(t['registers']['rax'],2**64-1)
|
| 79 |
+
g=graph(('imull','$0x80000000, %edi, %eax'),('movslq','%eax, %rax'))
|
| 80 |
+
t=evaluate(g,1,[3],{}, {'%rdi':3},capture_registers=True)
|
| 81 |
+
self.assertEqual(t['registers']['rax'],0xffffffff80000000)
|
| 82 |
+
|
| 83 |
+
def test_unknown_reads_unmodeled_flags_and_nonfinite_fail_closed(self):
|
| 84 |
+
with self.assertRaises(ValueError):evaluate(graph(('movq','0x1(%rip), %rax')),1,[2],{}, {})
|
| 85 |
+
with self.assertRaises(ValueError):evaluate(graph(('sete','%al')),1,[2],{}, {})
|
| 86 |
+
with self.assertRaises(ValueError):evaluate(graph(('imull','$0x1, %eax'),('je','0x3')),1,[3],{}, {'%rax':0})
|
| 87 |
+
with self.assertRaises(ValueError):evaluate(graph(('cmpl','$0x1, %eax'),('ja','0x3')),1,[3],{}, {'%rax':0})
|
| 88 |
+
with self.assertRaises(ValueError):evaluate(graph(('comiss','%xmm0, %xmm1')),1,[2],{}, {'%xmm0':0x7f800000,'%xmm1':0})
|
| 89 |
+
with self.assertRaises(ValueError):evaluate(graph(('movl','(%rbx,%rax,3), %edi')),1,[2],{}, {'%rbx':0,'%rax':0})
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
if __name__=='__main__':unittest.main()
|