Recover native collision filters and result decisions
Browse files- 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/NATIVE_COLLISION.md +60 -0
- docs/research/NATIVE_SPATIAL.md +2 -0
- docs/research/README.md +4 -1
- docs/research/native-collision.json +0 -0
- scripts/analyze_binaries.py +1 -1
- scripts/analyze_native_collision.py +101 -0
- scripts/test_native_collision.py +75 -0
docs/FINDINGS.md
CHANGED
|
@@ -288,3 +288,9 @@ Bounded qagame paths recover bomb-plant/inactivity correction requests, direct f
|
|
| 288 |
**F-074 — Observed VM pointer branch distinction:** raw zero/currentVM NULL returns NULL; other arguments use direct base addition or signed-extended 32-bit masking followed by addition. A nonzero argument that masks to zero can return the data base. Paired synthetic instruction traces do not establish legal allocation/mask invariants. [NAT-002](research/NATIVE_SPATIAL.md).
|
| 289 |
|
| 290 |
**F-075 — Observed native spatial structure:** trace NULL extents reference zero-fill storage and return copies span 56 bytes; PVS combines cluster bits with area connectivity. Area queries write four-byte indices and test equality with capacity before warning. Full fields/geometry/portals/tree ownership and runtime behavior remain open. [NAT-003](research/NATIVE_SPATIAL.md).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
**F-074 — Observed VM pointer branch distinction:** raw zero/currentVM NULL returns NULL; other arguments use direct base addition or signed-extended 32-bit masking followed by addition. A nonzero argument that masks to zero can return the data base. Paired synthetic instruction traces do not establish legal allocation/mask invariants. [NAT-002](research/NATIVE_SPATIAL.md).
|
| 289 |
|
| 290 |
**F-075 — Observed native spatial structure:** trace NULL extents reference zero-fill storage and return copies span 56 bytes; PVS combines cluster bits with area connectivity. Area queries write four-byte indices and test equality with capacity before warning. Full fields/geometry/portals/tree ownership and runtime behavior remain open. [NAT-003](research/NATIVE_SPATIAL.md).
|
| 291 |
+
|
| 292 |
+
**F-076 — Observed native candidate exclusions:** pass sentinel 1023 bypasses candidate/pass/related-word exclusions but not content-mask overlap. Paired first-lane instruction probes distinguish nonzero contents from actual mask overlap; complete owner/layout/producer/lane behavior remains open. [HIT-001](research/NATIVE_COLLISION.md).
|
| 293 |
+
|
| 294 |
+
**F-077 — Observed trace decision separation:** candidate status updates precede finite strict candidate-float < current-float replacement; equal floats skip copy. Static copy paths preserve prior offset-4 status with OR and write candidate identity at result +52. Complete status/copy/world/lane/runtime composition remains open. [HIT-002](research/NATIVE_COLLISION.md).
|
| 295 |
+
|
| 296 |
+
**F-078 — Observed model/transform routes:** inline model selection uses candidate field 0x1b0 and index 0xa0; temporary models use bounds and byte-flag selection. Transformed trace routes preserve origin/orientation pointers. Full flags, ownership and brush/patch/capsule/runtime geometry remain unverified. [HIT-003](research/NATIVE_COLLISION.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. 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. 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
|
@@ -149,3 +149,7 @@ Completed bounded milestones RE-281–283 with `analyze_chat_crosshair.py`: five
|
|
| 149 |
## 2026-10-11 — Native trig/spatial service and pointer evidence
|
| 150 |
|
| 151 |
Completed bounded/static RE-285–287 with `analyze_native_spatial.py`: ten complete case paths, twelve helper graphs, eighteen synthetic VM_ArgPtr instruction traces, explicit zero-fill extent origin, file-backed trace constants and capacity warning strings. NAT-001–003/F-073–075 confirm shipped Linux service destinations while retaining all-platform/runtime uncertainty and the distinction between zero-fill initialization and observed runtime contents. Seven tests check mappings/float transport, conversion counts, pointer branches/failure bounds, case boundaries and spatial static gates/copy/index evidence. RE-284 remains open; RE-288 tracks VM allocation, complete collision/PVS/tree/layout and original-runtime/platform requirements. No original process or native callee executes.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
## 2026-10-11 — Native trig/spatial service and pointer evidence
|
| 150 |
|
| 151 |
Completed bounded/static RE-285–287 with `analyze_native_spatial.py`: ten complete case paths, twelve helper graphs, eighteen synthetic VM_ArgPtr instruction traces, explicit zero-fill extent origin, file-backed trace constants and capacity warning strings. NAT-001–003/F-073–075 confirm shipped Linux service destinations while retaining all-platform/runtime uncertainty and the distinction between zero-fill initialization and observed runtime contents. Seven tests check mappings/float transport, conversion counts, pointer branches/failure bounds, case boundaries and spatial static gates/copy/index evidence. RE-284 remains open; RE-288 tracks VM allocation, complete collision/PVS/tree/layout and original-runtime/platform requirements. No original process or native callee executes.
|
| 152 |
+
|
| 153 |
+
## 2026-10-11 — Native collision candidate and result decisions
|
| 154 |
+
|
| 155 |
+
Completed bounded/static RE-289–291 with `analyze_native_collision.py`: ten native graphs, CM_Trace windows, 256 first-lane filter and 64 result-decision instruction probes. HIT-001–003/F-076–078 distinguish content overlap from nonzero contents, status updates from strict hit replacement and static copy/model routes from executed geometry. Seven tests check paired raw predicates, sentinel/contents behavior, finite fraction ties/status effects, retained model/producer/copy evidence and probe failure domains. RE-288 remains open; RE-292 tracks all lanes, copy/status composition, layout/producers/geometry/runtime/platform requirements. No original process or native collision callee executes.
|
docs/REVERSE_ENGINEERING_TODO.md
CHANGED
|
@@ -342,3 +342,8 @@ Purpose: turn the supplied installation into an evidence-backed specification of
|
|
| 342 |
- [x] RE-286 Recover bounded native VM_ArgPtr NULL/direct/mask/signed-extension branches with paired instruction traces ([NAT-002](research/NATIVE_SPATIAL.md)).
|
| 343 |
- [x] RE-287 Recover static NULL trace extents, 56-byte result-copy span, PVS/area gates and area-query index/capacity evidence ([NAT-003](research/NATIVE_SPATIAL.md)).
|
| 344 |
- [ ] RE-288 Compare other-platform dispatch, libm/float exception semantics, VM mask/allocation lifetime, full trace/collision fields/internals, PVS/portals/tree/index ordering and original map/runtime fixtures.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
- [x] RE-286 Recover bounded native VM_ArgPtr NULL/direct/mask/signed-extension branches with paired instruction traces ([NAT-002](research/NATIVE_SPATIAL.md)).
|
| 343 |
- [x] RE-287 Recover static NULL trace extents, 56-byte result-copy span, PVS/area gates and area-query index/capacity evidence ([NAT-003](research/NATIVE_SPATIAL.md)).
|
| 344 |
- [ ] RE-288 Compare other-platform dispatch, libm/float exception semantics, VM mask/allocation lifetime, full trace/collision fields/internals, PVS/portals/tree/index ordering and original map/runtime fixtures.
|
| 345 |
+
|
| 346 |
+
- [x] RE-289 Recover paired bounded first-lane pass/related-object and content-mask exclusion predicates ([HIT-001](research/NATIVE_COLLISION.md)).
|
| 347 |
+
- [x] RE-290 Recover finite first-lane status updates and strict closer-hit replacement decisions; retain copy/OR/result-field relationships ([HIT-002](research/NATIVE_COLLISION.md)).
|
| 348 |
+
- [x] RE-291 Recover static inline/temporary model selection and transformed-trace argument routes in both Linux ELF64 engines ([HIT-003](research/NATIVE_COLLISION.md)).
|
| 349 |
+
- [ ] RE-292 Validate every unrolled lane, existing-status/copy/OR composition, trace/entity layouts, owner/count producers, model/flag transforms, full geometry/order and original-runtime/all-platform fixtures.
|
docs/research/NATIVE_COLLISION.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Native entity collision filters and trace decisions
|
| 2 |
+
|
| 3 |
+
Static pass, 2026-10-11. Reproduce with `python3 scripts/analyze_native_collision.py`; check with `python3 scripts/test_native_collision.py`. [Generated evidence](native-collision.json) verifies original/disassembly hashes for both Linux ELF64 engines and retains five graphs per image plus CM_Trace windows. It evaluates **256 filter and 64 result-decision probes** against actual selected instruction addresses with supplied memory. No original process, collision callee or complete native function executes. These findings extend [native spatial services](NATIVE_SPATIAL.md).
|
| 4 |
+
|
| 5 |
+
## HIT-001: pass/related-object and contents filtering
|
| 6 |
+
|
| 7 |
+
SV_ClipMoveToEntities begins with an area query capped at **1024** indices. Its retained prologue derives a related-object word from the pass entity's field **0x200**, converting sentinel **1023** to **-1**; pass-entity sentinel 1023 also selects -1 without that entity lookup. Count <=0 or nonzero current result's first status word can end entity processing.
|
| 8 |
+
|
| 9 |
+
The bounded first candidate lane tests the following raw fields before model selection:
|
| 10 |
+
|
| 11 |
+
| Input | Native address/field |
|
| 12 |
+
| --- | --- |
|
| 13 |
+
| Pass entity index P | clip structure 0x74 |
|
| 14 |
+
| Candidate index C | current four-byte area-query element |
|
| 15 |
+
| Candidate related/owner-like word O | candidate object 0x200 |
|
| 16 |
+
| Previously derived pass-related word R | stack slot 0x2c |
|
| 17 |
+
| Content mask M | clip structure 0x78 |
|
| 18 |
+
| Candidate contents K | candidate object 0x1cc |
|
| 19 |
+
|
| 20 |
+
If **P !=1023**, candidate is rejected when **P==C**, **P==O**, or **R==O**. Pass sentinel 1023 bypasses those three comparisons. Independently, **M & K must be nonzero**. The contents test remains active with the sentinel.
|
| 21 |
+
|
| 22 |
+
Each engine has 128 first-lane probes: pass indices 1023/7/5/9, candidate index 7, related word 1023/5/9/-1, supplied pass-related -1/9 and contents 0/1/2/3 under mask 1. Contents 2 is nonzero but does not overlap mask 1; contents 3 does. First-match rejection and model-entry acceptance are reached through the original branches, not a gameplay execution harness.
|
| 23 |
+
|
| 24 |
+
Fields 0x200/0x1cc have owner-like/content-mask interpretations supported by comparisons and downstream collision calls. Full shared-object layout, legal entity domains, pass-related producers, linked-list/query capacity and the other unrolled filter lanes remain separate tasks.
|
| 25 |
+
|
| 26 |
+
## HIT-002: result status updates versus closer-hit replacement
|
| 27 |
+
|
| 28 |
+
The first post-CM_TransformedBoxTrace lane reads candidate result offset **0**. Nonzero sets current result status offset 0 to **1**, supplies candidate object word zero as the temporary hit identifier, and skips that lane's offset-4 test. If candidate offset 0 is zero, candidate offset **4** nonzero sets current status offset 4 to **1** and likewise supplies the temporary identifier.
|
| 29 |
+
|
| 30 |
+
Next the candidate float at result offset **8** is compared with current result offset 8. For finite values, only **candidate < current** reaches the copy/replacement gate. Equality or greater skips the copy. Thus status updates can precede—and survive—a rejected replacement. They are not the same decision as choosing a closer hit.
|
| 31 |
+
|
| 32 |
+
Thirty-two probes per image cover candidate statuses 0/1 at offsets 0/4, candidate float 0/0.25/0.5/1 and current float 0.5/1. Current statuses are initially zero in these fixtures. The restricted interpreter supports the selected MOVL/MOVSS/TESTL/CMPL/finite COMISS and branch forms only; unknown reads/opcodes, nonfinite comparisons and step bounds fail closed. Probe input floats are supplied collision results, not calculated intersections.
|
| 33 |
+
|
| 34 |
+
The retained copy path writes candidate object word zero into candidate result offset **52**, copies the **56-byte** result into the clip's current trace, and ORs the previous current offset-4 status into the copied offset-4 status. Other retained unrolled lanes have analogous calls/copies but are not evaluated by these first-lane probes. Initial nonzero current statuses, copy/OR composition, world/entity ties, NaNs, ordering and every lane need further validation.
|
| 35 |
+
|
| 36 |
+
The following raw result roles are corroborated across producer/consumer instructions:
|
| 37 |
+
|
| 38 |
+
| Result-relative offset | Observed role |
|
| 39 |
+
| ---: | --- |
|
| 40 |
+
| 0 | Status word that can stop further entity processing |
|
| 41 |
+
| 4 | Independently accumulated status; the crosshair branch compares it to exactly 1 |
|
| 42 |
+
| 8 | Float used to choose the closer result |
|
| 43 |
+
| 12/16/20 | Three endpoint words read by crosshair substitution |
|
| 44 |
+
| 52 | Hit identifier supplied from candidate object word zero |
|
| 45 |
+
|
| 46 |
+
CM_TraceThroughBrush writes status words at work offsets **0xcc/0xd0**, zero float at **0xd4**, and a brush-related word at **0xfc** in its containment paths. The status/float writes share the above relative spacing; work offset 0xfc is result-relative **48**, separate from the entity hit identifier at 52. The graphs/windows retain those writes; complete allsolid/startsolid/fraction/content member naming, plane layout and geometric proof remain open rather than inferred from spacing alone.
|
| 47 |
+
|
| 48 |
+
## HIT-003: collision model and transform routes
|
| 49 |
+
|
| 50 |
+
The retained clip graph selects models as follows:
|
| 51 |
+
|
| 52 |
+
- Candidate object word **0x1b0 nonzero** loads model index **0xa0** and calls **CM_InlineModel**.
|
| 53 |
+
- Otherwise it passes bounds pointers **object+0x1b4 / object+0x1c0** to **CM_TempBoxModel**. The byte test **object+0x1a9 &4** chooses the third argument **1** when set, **0** when clear. Capsule/flag identity and temporary-model ownership remain open.
|
| 54 |
+
- The subsequent **CM_TransformedBoxTrace** receives start/end, trace extents, selected model, contents mask and an additional mode word. Transform origin is **object+0x1e8**; orientation uses **object+0x1f4** when 0x1b0 is nonzero, otherwise the shared zero-initialized vector.
|
| 55 |
+
|
| 56 |
+
Three unrolled call sites and their related out-of-line branches are retained in each engine. CM_BoxTrace's wrapper calls CM_Trace with a shared vector and an extra zero argument; CM_TempBoxModel, CM_InlineModel and the brush-trace graph are also retained. This confirms the static model/transform routing without executing temporary-model mutation, brush planes, patches, capsule geometry or rotated-model results.
|
| 57 |
+
|
| 58 |
+
## Remaining work and Rust dependencies
|
| 59 |
+
|
| 60 |
+
RE-289–291 record first-lane filters, bounded result decisions and static model/result-field evidence. RE-288 remains open; RE-292 tracks every unrolled lane, existing-status and copy/OR composition, complete trace/entity fields, owner/count producers, model/flag/transform identities, collision internals/ordering and original-runtime/all-platform fixtures. A Rust collision contract must preserve exclusion relations, content overlap, status accumulation and strict hit replacement independently; a single nearest-hit operation would not capture the recovered status behavior.
|
docs/research/NATIVE_SPATIAL.md
CHANGED
|
@@ -48,3 +48,5 @@ The capacity test is **count == maximum**, not a recovered >= guard. At equality
|
|
| 48 |
## Remaining work and Rust dependencies
|
| 49 |
|
| 50 |
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.
|
|
|
|
|
|
|
|
|
| 48 |
## Remaining work and Rust dependencies
|
| 49 |
|
| 50 |
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.
|
| 51 |
+
|
| 52 |
+
The [HIT-001–003 follow-up](NATIVE_COLLISION.md) 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.
|
docs/research/README.md
CHANGED
|
@@ -42,10 +42,11 @@ python3 scripts/test_chat_items_time.py
|
|
| 42 |
python3 scripts/test_chat_locations.py
|
| 43 |
python3 scripts/test_chat_crosshair.py
|
| 44 |
python3 scripts/test_native_spatial.py
|
|
|
|
| 45 |
python3 scripts/storage_policy.py check --require-setup
|
| 46 |
```
|
| 47 |
|
| 48 |
-
The pipeline runs `fetch_analysis_references.py`, `analyze_qvm.py`, `analyze_native.py`, `analyze_vm_abi.py`, `analyze_native_dispatch.py`, `analyze_vm_extensions.py`, `analyze_native_metadata.py`, `analyze_native_coverage.py`, `analyze_auth_drop.py`, `analyze_deferred_auth.py`, `analyze_engine_control.py`, `analyze_frame_timing.py`, `analyze_event_queues.py`, `analyze_auth_announcements.py`, `analyze_game_cvars.py`, `analyze_cvar_changes.py`, `analyze_auth_queries.py`, `analyze_selector_bounds.py`, `analyze_chat_paths.py`, `analyze_chat_delivery.py`, `analyze_chat_expansion.py`, `analyze_chat_items_time.py`, `analyze_chat_locations.py`, `analyze_chat_crosshair.py`, and `
|
| 49 |
|
| 50 |
Generated JSON/JSONL and compressed evidence are regenerated in place; keep prose notes in Markdown. Rerunning the four analysis stages reproduced every generated report and gzip artifact byte-for-byte on the same toolchain. gzip timestamps and stored filenames are fixed. Different objdump versions can produce different disassembly text, so tool version and uncompressed evidence hashes are recorded. Reports are UTF-8 normal Git text, with individual files below the storage policy's 2 MiB limit.
|
| 51 |
|
|
@@ -97,3 +98,5 @@ The extension collector adds five focused tests for native tail calls, command-r
|
|
| 97 |
[Crosshair substitution geometry and results](CHAT_CROSSHAIR.md) adds RE-281–283: bounded origin/basis/endpoint arithmetic, exact trace arguments and status/temp-object/fallback routes. Reproduce with `python3 scripts/analyze_chat_crosshair.py`; check with `python3 scripts/test_chat_crosshair.py`. Engine trig/collision and complete output/runtime/platform conformance remain open.
|
| 98 |
|
| 99 |
[Native trig, spatial services and VM pointers](NATIVE_SPATIAL.md) adds RE-285–287: confirmed paired Linux service mappings/float transport, bounded VM_ArgPtr branches and native trace/PVS/area-query graphs. Reproduce with `python3 scripts/analyze_native_spatial.py`; check with `python3 scripts/test_native_spatial.py`. Complete geometry, layout, exceptional arithmetic and all-platform runtime contracts remain open.
|
|
|
|
|
|
|
|
|
| 42 |
python3 scripts/test_chat_locations.py
|
| 43 |
python3 scripts/test_chat_crosshair.py
|
| 44 |
python3 scripts/test_native_spatial.py
|
| 45 |
+
python3 scripts/test_native_collision.py
|
| 46 |
python3 scripts/storage_policy.py check --require-setup
|
| 47 |
```
|
| 48 |
|
| 49 |
+
The pipeline runs `fetch_analysis_references.py`, `analyze_qvm.py`, `analyze_native.py`, `analyze_vm_abi.py`, `analyze_native_dispatch.py`, `analyze_vm_extensions.py`, `analyze_native_metadata.py`, `analyze_native_coverage.py`, `analyze_auth_drop.py`, `analyze_deferred_auth.py`, `analyze_engine_control.py`, `analyze_frame_timing.py`, `analyze_event_queues.py`, `analyze_auth_announcements.py`, `analyze_game_cvars.py`, `analyze_cvar_changes.py`, `analyze_auth_queries.py`, `analyze_selector_bounds.py`, `analyze_chat_paths.py`, `analyze_chat_delivery.py`, `analyze_chat_expansion.py`, `analyze_chat_items_time.py`, `analyze_chat_locations.py`, `analyze_chat_crosshair.py`, `analyze_native_spatial.py`, and `analyze_native_collision.py` in that order. Python 3.9+ and **LLVM objdump** with x86 ELF/COFF/Mach-O support are required. This pass used Apple LLVM 17.0.0 on macOS arm64; it disassembled x86 images without running them. Upstream references are downloaded only if absent, checked against the committed SHA-256 manifest, and cached under `.cache/research/upstream/`. A clone also needs `git lfs pull` to obtain the derived artifacts/extracted members.
|
| 50 |
|
| 51 |
Generated JSON/JSONL and compressed evidence are regenerated in place; keep prose notes in Markdown. Rerunning the four analysis stages reproduced every generated report and gzip artifact byte-for-byte on the same toolchain. gzip timestamps and stored filenames are fixed. Different objdump versions can produce different disassembly text, so tool version and uncompressed evidence hashes are recorded. Reports are UTF-8 normal Git text, with individual files below the storage policy's 2 MiB limit.
|
| 52 |
|
|
|
|
| 98 |
[Crosshair substitution geometry and results](CHAT_CROSSHAIR.md) adds RE-281–283: bounded origin/basis/endpoint arithmetic, exact trace arguments and status/temp-object/fallback routes. Reproduce with `python3 scripts/analyze_chat_crosshair.py`; check with `python3 scripts/test_chat_crosshair.py`. Engine trig/collision and complete output/runtime/platform conformance remain open.
|
| 99 |
|
| 100 |
[Native trig, spatial services and VM pointers](NATIVE_SPATIAL.md) adds RE-285–287: confirmed paired Linux service mappings/float transport, bounded VM_ArgPtr branches and native trace/PVS/area-query graphs. Reproduce with `python3 scripts/analyze_native_spatial.py`; check with `python3 scripts/test_native_spatial.py`. Complete geometry, layout, exceptional arithmetic and all-platform runtime contracts remain open.
|
| 101 |
+
|
| 102 |
+
[Native entity collision filters and decisions](NATIVE_COLLISION.md) adds RE-289–291: paired first-lane filter/result probes, result-field relationships and model/transform paths. Reproduce with `python3 scripts/analyze_native_collision.py`; check with `python3 scripts/test_native_collision.py`. Full collision geometry/lane composition/runtime/platform behavior remains open.
|
docs/research/native-collision.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
scripts/analyze_binaries.py
CHANGED
|
@@ -14,7 +14,7 @@ def main():
|
|
| 14 |
'analyze_deferred_auth.py', 'analyze_engine_control.py', 'analyze_frame_timing.py',
|
| 15 |
'analyze_event_queues.py', 'analyze_auth_announcements.py', 'analyze_game_cvars.py',
|
| 16 |
'analyze_cvar_changes.py', 'analyze_auth_queries.py', 'analyze_selector_bounds.py',
|
| 17 |
-
'analyze_chat_paths.py', 'analyze_chat_delivery.py', 'analyze_chat_expansion.py', 'analyze_chat_items_time.py', 'analyze_chat_locations.py', 'analyze_chat_crosshair.py', 'analyze_native_spatial.py'):
|
| 18 |
subprocess.run([sys.executable, str(Path('scripts')/script)], check=True)
|
| 19 |
|
| 20 |
|
|
|
|
| 14 |
'analyze_deferred_auth.py', 'analyze_engine_control.py', 'analyze_frame_timing.py',
|
| 15 |
'analyze_event_queues.py', 'analyze_auth_announcements.py', 'analyze_game_cvars.py',
|
| 16 |
'analyze_cvar_changes.py', 'analyze_auth_queries.py', 'analyze_selector_bounds.py',
|
| 17 |
+
'analyze_chat_paths.py', 'analyze_chat_delivery.py', 'analyze_chat_expansion.py', 'analyze_chat_items_time.py', 'analyze_chat_locations.py', 'analyze_chat_crosshair.py', 'analyze_native_spatial.py', 'analyze_native_collision.py'):
|
| 18 |
subprocess.run([sys.executable, str(Path('scripts')/script)], check=True)
|
| 19 |
|
| 20 |
|
scripts/analyze_native_collision.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Recover native clip filtering and finite trace-selection decision slices."""
|
| 3 |
+
import gzip
|
| 4 |
+
import itertools
|
| 5 |
+
import json
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
import re
|
| 8 |
+
from analyze_chat_crosshair import bits,number
|
| 9 |
+
from analyze_engine_control import control_flow,windows
|
| 10 |
+
from analyze_qvm import write_json
|
| 11 |
+
from analyze_vm_extensions import native_functions,verified
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def probe(graph,start,stops,memory,registers,limit=128):
|
| 15 |
+
"""Restricted integer gates/finite COMISS, with supplied native word memory."""
|
| 16 |
+
ins={x['address']:x for x in graph['instructions']};order={x['address']:i for i,x in enumerate(graph['instructions'])}
|
| 17 |
+
m=dict(memory);regs=dict(registers);pc=start;equal=ge=False;trace=[]
|
| 18 |
+
def value(arg):
|
| 19 |
+
if arg.startswith('$'):return int(arg[1:],0)&0xffffffff
|
| 20 |
+
if arg.startswith('%'):
|
| 21 |
+
if arg not in regs:raise ValueError('unknown collision register')
|
| 22 |
+
return regs[arg]
|
| 23 |
+
match=re.fullmatch(r'(0x[0-9a-f]+)?\((%\w+)\)',arg)
|
| 24 |
+
if not match or match[2] not in regs:raise ValueError('unsupported collision address')
|
| 25 |
+
address=regs[match[2]]+int(match[1] or '0',0)
|
| 26 |
+
if address not in m:raise ValueError('unknown collision word')
|
| 27 |
+
return m[address]
|
| 28 |
+
def store(arg,v):
|
| 29 |
+
if arg.startswith('%'):regs[arg]=v&0xffffffff;return
|
| 30 |
+
match=re.fullmatch(r'(0x[0-9a-f]+)?\((%\w+)\)',arg)
|
| 31 |
+
if not match or match[2] not in regs:raise ValueError('unsupported collision destination')
|
| 32 |
+
m[regs[match[2]]+int(match[1] or '0',0)]=v&0xffffffff
|
| 33 |
+
while pc not in stops:
|
| 34 |
+
if pc not in ins or len(trace)>=limit:raise ValueError('collision probe escaped bound')
|
| 35 |
+
x=ins[pc];trace.append(pc);op=x['opcode'];args=x['operands'].split('#')[0].strip()
|
| 36 |
+
i=order[pc];pc=graph['instructions'][i+1]['address'] if i+1<len(graph['instructions']) else None
|
| 37 |
+
if op in ['movl','movss']:
|
| 38 |
+
a,b=args.split(', ');store(b,value(a))
|
| 39 |
+
elif op in ['cmpl','testl']:
|
| 40 |
+
a,b=args.split(', ');left,right=value(a)&0xffffffff,value(b)&0xffffffff
|
| 41 |
+
equal=(left==right) if op=='cmpl' else (left&right)==0
|
| 42 |
+
elif op=='comiss':
|
| 43 |
+
a,b=args.split(', ');source,destination=number(value(a)),number(value(b))
|
| 44 |
+
equal=source==destination;ge=destination>=source
|
| 45 |
+
elif op in ['je','jne','jmp','jae']:
|
| 46 |
+
taken=op=='jmp' or (equal if op=='je' else not equal if op=='jne' else ge)
|
| 47 |
+
if taken:pc=int(args.split()[0],16)
|
| 48 |
+
else:raise ValueError('unsupported collision probe opcode '+op)
|
| 49 |
+
return {'trace':trace,'stop':pc,'memory':m,'registers':regs}
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def sites(graph):
|
| 53 |
+
rows=graph['instructions']
|
| 54 |
+
start=next(i for i,x in enumerate(rows) if x['opcode']=='movl' and x['operands']=='0x74(%rbx), %eax')
|
| 55 |
+
passed=next(x['address'] for x in rows[start:] if x['opcode']=='movl' and x['operands']=='0x1b0(%rbp), %r9d')
|
| 56 |
+
# Skip destination comes from the explicit candidate-number comparison.
|
| 57 |
+
cmp=next(i for i in range(start,len(rows)) if rows[i]['opcode']=='cmpl' and rows[i]['operands']=='(%r12), %eax')
|
| 58 |
+
skipped=int(rows[cmp+1]['operands'].split()[0],16)
|
| 59 |
+
merge_start=next(x['address'] for x in rows if x['opcode']=='movl' and x['operands']=='0x1030(%rsp), %r10d')
|
| 60 |
+
fraction=next(i for i,x in enumerate(rows) if x['opcode']=='comiss' and x['operands']=='0x44(%rbx), %xmm0')
|
| 61 |
+
if rows[fraction+1]['opcode']!='jae':raise ValueError('fraction decision changed')
|
| 62 |
+
return {'filter_start':rows[start]['address'],'filter_pass':passed,'skip':skipped,
|
| 63 |
+
'merge_start':merge_start,'replace':rows[fraction+2]['address']}
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def main():
|
| 67 |
+
out={'native':{},'limitations':'Verified static Linux ELF64 and restricted supplied-memory instruction '
|
| 68 |
+
'probes, not original runtime/native callee execution. Entity/owner/content fields and trace result '
|
| 69 |
+
'words/fractions are supplied fixtures. Float probes reject nonfinite values. Full copy/merge, '
|
| 70 |
+
'collision brush/patch/capsule geometry, producer invariants and other-platform parity remain open.'}
|
| 71 |
+
for label in ['linux--Quake3-UrT-Ded.x86_64','linux--Quake3-UrT.x86_64']:
|
| 72 |
+
r=json.loads(Path(f'docs/research/native/{label}.json').read_text())
|
| 73 |
+
verified((Path('.cache/UrbanTerror43')/r['source']['path']).read_bytes(),r['source']['sha256'])
|
| 74 |
+
a=r['artifacts']['disassembly.txt'];fs=native_functions(verified(gzip.open(a['path'],'rb').read(),a['uncompressed_sha256']).decode())
|
| 75 |
+
names=['SV_ClipMoveToEntities','CM_TraceThroughBrush','CM_BoxTrace','CM_TempBoxModel','CM_InlineModel']
|
| 76 |
+
graphs={n:control_flow(fs[n]) for n in names};g=graphs['SV_ClipMoveToEntities'];s=sites(g)
|
| 77 |
+
clip,entity,array,stack=0x10000,0x20000,0x30000,0x40000
|
| 78 |
+
registers={'%rbx':clip,'%rbp':entity,'%r12':array,'%rsp':stack}
|
| 79 |
+
filters=[]
|
| 80 |
+
for passing,owner,pass_owner,contents in itertools.product([1023,7,5,9],[1023,5,9,-1],[-1,9],[0,1,2,3]):
|
| 81 |
+
m={clip+0x74:passing,entity+0x200:owner,array:7,stack+0x2c:pass_owner,
|
| 82 |
+
clip+0x78:1,entity+0x1cc:contents}
|
| 83 |
+
t=probe(g,s['filter_start'],[s['filter_pass'],s['skip']],m,registers)
|
| 84 |
+
filters.append({'pass_entity':passing,'candidate_index':7,'candidate_field200':owner,
|
| 85 |
+
'supplied_pass_related_word':pass_owner,'content_mask':1,'candidate_contents':contents,
|
| 86 |
+
'accepted_at_model_gate':t['stop']==s['filter_pass'],'probe':t})
|
| 87 |
+
merges=[]
|
| 88 |
+
for status0,status4,fraction,old_fraction in itertools.product([0,1],[0,1],[0,.25,.5,1],[.5,1]):
|
| 89 |
+
m={stack+0x1030:status0,stack+0x1034:status4,stack+0x1038:bits(fraction),
|
| 90 |
+
clip+0x3c:0,clip+0x40:0,clip+0x44:bits(old_fraction),entity:7}
|
| 91 |
+
t=probe(g,s['merge_start'],[s['replace'],s['skip']],m,registers)
|
| 92 |
+
merges.append({'candidate_status0':status0,'candidate_status4':status4,'candidate_fraction':fraction,
|
| 93 |
+
'current_fraction':old_fraction,'replace_at_copy_gate':t['stop']==s['replace'],'probe':t})
|
| 94 |
+
out['native'][label]={'source':r['source'],'disassembly_sha256':a['uncompressed_sha256'],
|
| 95 |
+
'graphs':graphs,'sites':s,'filter_probes':filters,'merge_probes':merges,
|
| 96 |
+
'trace_result_windows':windows(fs['CM_Trace'],[r'0xcc\(',r'0xd0\(',r'0xd4\(',r'0xfc\(',r'<CM_TraceThrough'],before=3,after=6)}
|
| 97 |
+
write_json(Path('docs/research/native-collision.json'),out)
|
| 98 |
+
print('Recovered paired entity filters, model paths and 320 collision decision probes')
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
if __name__=='__main__':main()
|
scripts/test_native_collision.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Native collision decision and static producer/model fixtures."""
|
| 3 |
+
import json
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
import unittest
|
| 6 |
+
from analyze_native_collision import probe
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class NativeCollisionTests(unittest.TestCase):
|
| 10 |
+
@classmethod
|
| 11 |
+
def setUpClass(cls):cls.r=json.loads(Path('docs/research/native-collision.json').read_text())
|
| 12 |
+
|
| 13 |
+
def test_filter_matrix_matches_raw_comparisons(self):
|
| 14 |
+
for n in self.r['native'].values():
|
| 15 |
+
rows=n['filter_probes'];self.assertEqual(len(rows),128)
|
| 16 |
+
for x in rows:
|
| 17 |
+
expected=(x['candidate_contents']&x['content_mask'])!=0 and (x['pass_entity']==1023 or
|
| 18 |
+
(x['pass_entity']!=x['candidate_index'] and x['pass_entity']!=x['candidate_field200']
|
| 19 |
+
and x['candidate_field200']!=x['supplied_pass_related_word']))
|
| 20 |
+
self.assertEqual(x['accepted_at_model_gate'],expected)
|
| 21 |
+
self.assertEqual(x['probe']['stop'],n['sites']['filter_pass'] if expected else n['sites']['skip'])
|
| 22 |
+
|
| 23 |
+
def test_pass_sentinel_bypasses_owner_checks_but_not_contents(self):
|
| 24 |
+
for n in self.r['native'].values():
|
| 25 |
+
rows=[x for x in n['filter_probes'] if x['pass_entity']==1023]
|
| 26 |
+
self.assertTrue(all(x['accepted_at_model_gate']==bool(x['candidate_contents']&x['content_mask']) for x in rows))
|
| 27 |
+
rows=[x for x in n['filter_probes'] if x['pass_entity']==7]
|
| 28 |
+
self.assertTrue(all(not x['accepted_at_model_gate'] for x in rows))
|
| 29 |
+
|
| 30 |
+
def test_fraction_strictness_and_status_side_effects(self):
|
| 31 |
+
for n in self.r['native'].values():
|
| 32 |
+
self.assertEqual(len(n['merge_probes']),32)
|
| 33 |
+
for x in n['merge_probes']:
|
| 34 |
+
self.assertEqual(x['replace_at_copy_gate'],x['candidate_fraction']<x['current_fraction'])
|
| 35 |
+
m=x['probe']['memory']
|
| 36 |
+
self.assertEqual(m[str(0x10000+0x3c)],x['candidate_status0'])
|
| 37 |
+
# Status zero's nonzero branch skips the second status test.
|
| 38 |
+
self.assertEqual(m[str(0x10000+0x40)],int(not x['candidate_status0'] and x['candidate_status4']))
|
| 39 |
+
equal=[x for x in n['merge_probes'] if x['candidate_fraction']==x['current_fraction']]
|
| 40 |
+
self.assertTrue(all(not x['replace_at_copy_gate'] for x in equal))
|
| 41 |
+
|
| 42 |
+
def test_model_routes_and_geometry_arguments_retained(self):
|
| 43 |
+
for n in self.r['native'].values():
|
| 44 |
+
graph=n['graphs']['SV_ClipMoveToEntities'];lines='\n'.join(x['line'] for x in graph['instructions'])
|
| 45 |
+
for marker in ['<CM_InlineModel>','<CM_TempBoxModel>','<CM_TransformedBoxTrace>',
|
| 46 |
+
'0x1b0(%rbp)','0x1b4(%rbp)','0x1c0(%rbp)','0x1e8(%rbp)',
|
| 47 |
+
'0x1f4(%rbp)','testb\t$0x4, 0x1a9(%rbp)','0xa0(%rbp)']:
|
| 48 |
+
self.assertIn(marker,lines)
|
| 49 |
+
self.assertEqual(sum(c['label']=='CM_TransformedBoxTrace' for c in graph['calls']),3)
|
| 50 |
+
|
| 51 |
+
def test_status_copy_preserves_prior_word_and_candidate_number(self):
|
| 52 |
+
for n in self.r['native'].values():
|
| 53 |
+
lines='\n'.join(x['line'] for x in n['graphs']['SV_ClipMoveToEntities']['instructions'])
|
| 54 |
+
for marker in ['0x1064(%rsp)','0x40(%rbx)','orl','0x6c(%rbx)']:
|
| 55 |
+
self.assertIn(marker,lines)
|
| 56 |
+
brush='\n'.join(x['line'] for x in n['graphs']['CM_TraceThroughBrush']['instructions'])
|
| 57 |
+
for marker in ['movl\t$0x1, 0xcc(%rdi)','movl\t$0x1, 0xd0(%rdi)',
|
| 58 |
+
'movl\t$0x0, 0xd4(%rdi)','0xfc(%rdi)']:
|
| 59 |
+
self.assertIn(marker,brush)
|
| 60 |
+
|
| 61 |
+
def test_probe_rejects_missing_words_unknown_op_and_limits(self):
|
| 62 |
+
graph={'instructions':[{'address':1,'opcode':'movl','operands':'0x0(%rbx), %eax'},
|
| 63 |
+
{'address':2,'opcode':'je','operands':'0x3'}]}
|
| 64 |
+
with self.assertRaises(ValueError):probe(graph,1,[3],{}, {'%rbx':0})
|
| 65 |
+
with self.assertRaises(ValueError):probe(graph,1,[3],{0:1},{'%rbx':0},limit=1)
|
| 66 |
+
bad={'instructions':[{'address':1,'opcode':'nop','operands':''}]}
|
| 67 |
+
with self.assertRaises(ValueError):probe(bad,1,[3],{}, {})
|
| 68 |
+
|
| 69 |
+
def test_float_comparison_rejects_nan(self):
|
| 70 |
+
graph={'instructions':[{'address':1,'opcode':'comiss','operands':'%xmm0, %xmm1'},
|
| 71 |
+
{'address':2,'opcode':'jae','operands':'0x3'}]}
|
| 72 |
+
with self.assertRaises(ValueError):probe(graph,1,[3],{}, {'%xmm0':0x7fc00000,'%xmm1':0})
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
if __name__=='__main__':unittest.main()
|