docs(physics): pin Slice I collision oracle
This commit is contained in:
parent
f1a8d36682
commit
624e1119ca
3 changed files with 827 additions and 0 deletions
276
docs/plans/2026-07-25-modern-runtime-slice-i.md
Normal file
276
docs/plans/2026-07-25-modern-runtime-slice-i.md
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
# Modern runtime Slice I — flat collision assets and zero-allocation physics
|
||||
|
||||
**Status:** I0 COMPLETE — retail oracle, representative graph fixtures, and
|
||||
four-mover allocation baselines fixed; I1 reusable scratch is next
|
||||
**Parent:** `2026-07-24-modern-runtime-architecture.md`, Slice I
|
||||
**Purpose:** remove parsed DAT object graphs and steady collision allocations
|
||||
without changing one retail collision decision, float, comparison, or traversal
|
||||
order.
|
||||
|
||||
## 1. Non-negotiable boundaries
|
||||
|
||||
1. Retail collision math is frozen. This slice changes storage and scratch
|
||||
ownership only.
|
||||
2. Every float read from DAT is copied bit-for-bit. The bake performs no
|
||||
normalization, quantization, coordinate conversion, `double` round-trip, or
|
||||
plane reconstruction.
|
||||
3. Positive/negative child order, polygon order within every leaf, early
|
||||
returns, epsilon comparisons, and contact-selection tie behavior remain
|
||||
exact.
|
||||
4. The current object-graph traversal remains an executable oracle until the
|
||||
flat traversal passes mass differential and connected gates.
|
||||
5. Physics `Transition` reuse lands only after reset-completeness,
|
||||
cross-mover-identity, failure, and reentrancy tests prove that no state can
|
||||
leak between resolves.
|
||||
6. No GL/App dependency enters Core. Flat collision records live in
|
||||
`AcDream.Core`; serialization and package access live in `AcDream.Content`;
|
||||
the bake tool produces them offline.
|
||||
7. The active G4 render cutover is independent. Its exact visual rollback
|
||||
remains:
|
||||
|
||||
```text
|
||||
git revert ef1d263337997bb030eadb7b8e71d73dc659907a
|
||||
```
|
||||
|
||||
## 2. Target data architecture
|
||||
|
||||
The runtime collision representation is immutable, array-backed, and addressed
|
||||
by integer index:
|
||||
|
||||
```text
|
||||
FlatCollisionAsset
|
||||
├── FlatPhysicsBsp
|
||||
│ ├── FlatBspNode[] child indices; -1 = no child
|
||||
│ ├── int[] leaf polygon-index stream
|
||||
│ ├── FlatPolygon[] plane, cull mode, id, vertex range
|
||||
│ └── Vector3[] verbatim polygon vertices
|
||||
├── FlatCellContainmentBsp split planes + positive/negative indices
|
||||
├── FlatSetupCollision cyl/sphere records + verbatim dimensions
|
||||
└── EnvCellTopology portals, visible cells, seen-outside
|
||||
```
|
||||
|
||||
Node order is deterministic pre-order. The flattener records the original
|
||||
positive child before the negative child and retains each source leaf's
|
||||
polygon list order. Traversal follows indices using the same recursive
|
||||
control flow as the current retail port; “flat” does not mean a new collision
|
||||
algorithm.
|
||||
|
||||
Per-EnvCell world transform remains publication state, not baked geometry.
|
||||
CellStruct collision geometry can therefore alias by exact source identity;
|
||||
cell-specific portals, visibility, `seen_outside`, and placement remain a
|
||||
separate small payload/publication record.
|
||||
|
||||
## 3. Execution slices
|
||||
|
||||
### I0 — oracle inventory and fixed evidence
|
||||
|
||||
1. Grep named retail before touching traversal:
|
||||
- `BSPTREE::find_collisions`
|
||||
- `BSPNODE::find_walkable`
|
||||
- `BSPLEAF::find_walkable`
|
||||
- `BSPNODE::sphere_intersects_*`
|
||||
- `BSPNODE::point_inside_cell_bsp`
|
||||
- `CTransition::find_transitional_position`
|
||||
- `CTransition::init_path`
|
||||
- `CPhysicsObj::transition`
|
||||
2. Cross-reference ACE and the existing acdream pseudocode/trajectory notes.
|
||||
3. Write one collision-layout pseudocode note that separates:
|
||||
source storage, traversal order, mutable query scratch, and returned
|
||||
side-effects.
|
||||
4. Inventory every production call into `BSPQuery`, every field read from
|
||||
`PhysicsBSPNode`, `CellBSPNode`, `ResolvedPolygon`, `CellPhysics`,
|
||||
`GfxObjPhysics`, and `SetupPhysics`, and every raw DAT graph retained by
|
||||
`LandblockBuild`/`PhysicsDatBundle`.
|
||||
5. Capture representative installed-DAT fixtures:
|
||||
- outdoor/no-BSP;
|
||||
- Facility Hub and cottage indoor cells;
|
||||
- staircase/ramp and cellar-lip cases;
|
||||
- a multipart door/building shell;
|
||||
- projectile/thin-wall geometry;
|
||||
- cells with asymmetric/null BSP children;
|
||||
- leaves with multiple polygons and equal-distance candidates.
|
||||
6. Record allocation baselines for player, remote, projectile, and camera
|
||||
resolve loops.
|
||||
|
||||
Gate: source inventory is complete, fixtures reproduce through the current
|
||||
graph path, and no behavior code changed.
|
||||
|
||||
### I1 — reusable transition and query scratch
|
||||
|
||||
1. Add explicit `ResetForReuse` methods to `ObjectInfo`, `CollisionInfo`,
|
||||
`SpherePath`, and `Transition`.
|
||||
2. Reset every scalar, nullable, property backing field, diagnostic counter,
|
||||
collision GUID list, sphere element, walkable reference, backup value, and
|
||||
transient flag. Retain only storage identity whose contents are completely
|
||||
reset.
|
||||
3. Give each `PhysicsEngine` one owned transition lease. The normal
|
||||
update-thread path rents, initializes, resolves, snapshots the value-only
|
||||
result, and returns in `finally`.
|
||||
4. Make reentrancy explicit and tested. Never hand one mutable transition to
|
||||
two concurrent/nested resolves and never silently share session scratch.
|
||||
5. Reuse walkable vertex arrays only when their exact logical length matches;
|
||||
allocate only on a true size change. No larger-capacity array may leak stale
|
||||
vertices into polygon math.
|
||||
6. Convert short-lived `BSPQuery.CollisionSphere` reference objects to
|
||||
value/ref scratch while preserving each mutation/copy boundary.
|
||||
7. Add structural reflection tests that poison every resettable member, reset,
|
||||
and compare its complete value graph with a fresh instance while asserting
|
||||
retained buffer identities.
|
||||
8. Differential-run fresh-transition and reused-transition engines across
|
||||
success, collision, failure, placement, grounded, airborne, sliding,
|
||||
step-up/down, two-sphere, projectile, and camera cases.
|
||||
|
||||
Gate: bit-identical `ResolveResult` and body side effects, no state leakage
|
||||
when alternating hostile fixtures between different mover IDs, and zero
|
||||
steady transition/query-scratch allocation. Close issue #237 only here.
|
||||
|
||||
### I2 — immutable flat collision schema and flattener
|
||||
|
||||
1. Add Core-only flat record types with explicit index/range contracts.
|
||||
2. Flatten physics and containment BSPs iteratively during preparation while
|
||||
emitting deterministic pre-order node arrays.
|
||||
3. Copy planes, spheres, vertices, setup dimensions, and collision metadata
|
||||
verbatim. Tests compare every float via `SingleToInt32Bits`.
|
||||
4. Resolve each leaf polygon ID to a direct polygon index at preparation time.
|
||||
Missing IDs are corruption, not a runtime fallback.
|
||||
5. Validate:
|
||||
- child indices/ranges;
|
||||
- acyclic/reachable tree shape;
|
||||
- polygon and vertex ranges;
|
||||
- source-vs-flat node, child, polygon, and float identity;
|
||||
- deterministic output independent of dictionary enumeration.
|
||||
6. Keep source graph and flat asset together only in test/shadow fixtures.
|
||||
|
||||
Gate: round-trip structural equality over synthetic edge cases and installed
|
||||
DAT samples; no traversal cutover yet.
|
||||
|
||||
### I3 — package serialization, bake, and prepared collision source
|
||||
|
||||
1. Append new `PakAssetType` values; never renumber the existing mesh values.
|
||||
2. Bump `CurrentBakeToolVersion` because a complete production package now
|
||||
includes collision assets.
|
||||
3. Add strict little-endian serializers with checked counts/ranges, exact float
|
||||
bits, cancellation, CRC coverage, corruption rejection, and trailing-byte
|
||||
rejection.
|
||||
4. Add a typed `IPreparedCollisionSource` over the existing memory-mapped pak.
|
||||
It shares package/catalog lifetime but does not overload the render-only
|
||||
`ObjectMeshData` API.
|
||||
5. Bake:
|
||||
- GfxObj physics BSP + polygons + visual bounds;
|
||||
- Setup cylinder/sphere/dimension records;
|
||||
- CellStruct physics BSP + containment BSP + physics/portal polygons;
|
||||
- EnvCell topology records.
|
||||
6. Alias only byte-identical immutable collision payloads. Cell-specific
|
||||
topology and world placement cannot be aliased merely because geometry is.
|
||||
7. Extend full-bake determinism, two-thread equivalence, corruption,
|
||||
cancellation, publish-transaction, and catalog-completeness tests.
|
||||
|
||||
Gate: two independent bakes are byte-identical; every collision key required
|
||||
by the canonical route is present; a failed/cancelled bake cannot replace the
|
||||
last good pak.
|
||||
|
||||
### I4 — flat traversal shadow implementation
|
||||
|
||||
1. Port each current entry point line-for-line against integer-index nodes:
|
||||
- point-in-cell;
|
||||
- sphere/cell overlap;
|
||||
- walkable search;
|
||||
- six-path moving collision dispatcher;
|
||||
- static sphere/polygon overlap;
|
||||
- time-of-impact overlap.
|
||||
2. Share only polygon-level math that is storage-independent. Do not
|
||||
“simplify” recursion, branch ordering, or early returns.
|
||||
3. Preserve mutation semantics for valid-position spheres, hit polygon, path,
|
||||
contact plane, slide normal, walk interpolation, and transition state.
|
||||
4. Add a differential harness that executes old and flat queries from cloned
|
||||
transition/body inputs and compares:
|
||||
- bool/enum result;
|
||||
- all output vectors/planes by float bits;
|
||||
- selected polygon ID;
|
||||
- path/collision/object side effects;
|
||||
- cell membership and final `ResolveResult`.
|
||||
5. Sweep a large installed-DAT sample with randomized points, spheres,
|
||||
movement vectors, insert modes, orientations, scales, and boundary values.
|
||||
6. Require explicit fixtures for null children, on-plane equality, radius
|
||||
equality, equal candidate distances, multi-polygon leaf order, and deep
|
||||
trees.
|
||||
|
||||
Gate: zero differential mismatch. Any mismatch blocks cutover; no tolerance
|
||||
band is permitted for deterministic scalar results.
|
||||
|
||||
### I5 — dual publication and connected shadow gate
|
||||
|
||||
1. Extend collision preparation so a `LandblockBuild` carries immutable flat
|
||||
assets and small placement/topology records rather than reparsing them on
|
||||
the update thread.
|
||||
2. During the shadow phase, publish both current and flat views under one
|
||||
landblock generation/receipt.
|
||||
3. Keep the current graph path authoritative. Run sampled flat queries from
|
||||
cloned inputs and emit a deterministic mismatch artifact without affecting
|
||||
gameplay.
|
||||
4. Verify load, pending-to-loaded, demotion, rehydrate, same-location revisit,
|
||||
removal, cancellation, and session reset release both views exactly once.
|
||||
5. Run connected login, indoor/outdoor portal churn, doors, ramps, jumping,
|
||||
projectiles, camera collision, and graceful reconnect.
|
||||
|
||||
Gate: zero shadow mismatch and no retained collision owner after landblock or
|
||||
session teardown.
|
||||
|
||||
### I6 — production cutover and graph removal
|
||||
|
||||
1. Flip canonical `PhysicsDataCache` records and every `BSPQuery` production
|
||||
call to flat assets in one bisectable commit.
|
||||
2. Keep the graph route as an automated referee for the first cutover gate,
|
||||
with an exact revert command recorded in this plan and project memory.
|
||||
3. After automated and user correctness acceptance, remove:
|
||||
- production `PhysicsBSPTree`/`CellBSPTree` retention;
|
||||
- production polygon dictionaries and vertex DBObj graphs;
|
||||
- raw collision graphs from streaming build/publication payloads;
|
||||
- obsolete graph adapters and referee code.
|
||||
4. Live-DAT tooling may parse source graphs only to produce/compare flat
|
||||
assets; gameplay production never falls back from a missing/corrupt prepared
|
||||
collision asset.
|
||||
|
||||
Gate: trajectories and collision fixtures remain bit-identical, package-only
|
||||
startup succeeds, and retained-memory accounting shows no parsed collision
|
||||
DBObj graph after stable world reveal.
|
||||
|
||||
### I7 — allocation, soak, and documentation closeout
|
||||
|
||||
1. Measure capped and uncapped resolve allocation separately for player,
|
||||
remotes, projectiles, and camera.
|
||||
2. Run the canonical connected lifecycle/reconnect route and the nine-stop R6
|
||||
portal soak.
|
||||
3. Repeat indoor door/ramp/cellar, jump/fall, combat/projectile, and camera-wall
|
||||
gates.
|
||||
4. Under RDP, accept correctness and lifecycle evidence only; defer absolute
|
||||
GPU/frame-time acceptance to a physical-display run.
|
||||
5. Update architecture, WorldBuilder inventory, milestones, roadmap, issue
|
||||
ledger, divergence register, and both physics/render memory digests.
|
||||
|
||||
Final gate:
|
||||
|
||||
- full Release build and solution tests;
|
||||
- zero deterministic old-vs-flat mismatch;
|
||||
- zero steady transition/query-scratch allocation;
|
||||
- no raw collision graph in production stable state;
|
||||
- connected portal/reconnect/interaction routes pass;
|
||||
- exact cutover rollback recorded before user verification.
|
||||
|
||||
## 4. Commit order
|
||||
|
||||
Each unit is independently buildable and reversible:
|
||||
|
||||
1. `docs(physics): pin Slice I collision oracle and fixtures`
|
||||
2. `perf(physics): reuse reset-complete transition scratch`
|
||||
3. `feat(physics): define deterministic flat collision assets`
|
||||
4. `feat(content): bake and read flat collision assets`
|
||||
5. `feat(physics): add differential flat BSP traversal`
|
||||
6. `feat(streaming): shadow-publish flat collision assets`
|
||||
7. `perf(physics): cut production traversal to flat assets`
|
||||
8. `refactor(physics): remove parsed collision graphs`
|
||||
9. `docs(physics): close Slice I evidence and roadmap`
|
||||
|
||||
The exact production-cutover commit and its `git revert <sha>` command are
|
||||
added to this plan and `claude-memory/project_physics_collision_digest.md`
|
||||
immediately when step 7 lands, before any visual/correctness gate.
|
||||
345
docs/research/2026-07-25-slice-i-collision-layout-oracle.md
Normal file
345
docs/research/2026-07-25-slice-i-collision-layout-oracle.md
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
# Slice I collision layout and scratch oracle
|
||||
|
||||
**Status:** I0 fixed evidence
|
||||
**Scope:** storage layout, traversal order, scratch lifetime, production
|
||||
retention, and allocation baseline for
|
||||
[`2026-07-25-modern-runtime-slice-i.md`](../plans/2026-07-25-modern-runtime-slice-i.md).
|
||||
This note does not authorize a collision-math change.
|
||||
|
||||
## 1. Sources and authority
|
||||
|
||||
Named retail is the behavior oracle:
|
||||
|
||||
| Mechanism | Retail symbol | Address | Named pseudo-C |
|
||||
|---|---|---:|---:|
|
||||
| scratch acquisition | `CTransition::makeTransition` | `0x0050B150` | 272873 |
|
||||
| scratch release | `CTransition::cleanupTransition` | `0x00509DC0` | 271938 |
|
||||
| transition reset | `CTransition::init` | `0x00509DD0` | 271946 |
|
||||
| collision reset | `COLLISIONINFO::init` | `0x00509D60` | 271910 |
|
||||
| path reset | `SPHEREPATH::init` | `0x0050C330` | 273903 |
|
||||
| path initialization | `SPHEREPATH::init_path` | `0x0050CE20` | 274359 |
|
||||
| movement loop | `CTransition::find_transitional_position` | `0x0050BDF0` | 273613 |
|
||||
| object entry point | `CPhysicsObj::transition` | `0x00512DC0` | 280904 |
|
||||
| six-path BSP dispatch | `BSPTREE::find_collisions` | `0x0053A440` | 323725 |
|
||||
| cell point predicate | `BSPNODE::point_inside_cell_bsp` | `0x0053C1F0` | 325508 |
|
||||
| cell sphere predicate | `BSPNODE::sphere_intersects_cell_bsp` | `0x0053C260` | 325546 |
|
||||
| walkable internal node | `BSPNODE::find_walkable` | `0x0053CC80` | 326211 |
|
||||
| walkable leaf | `BSPLEAF::find_walkable` | `0x0053D6F0` | 326793 |
|
||||
| polygon overlap | `BSPNODE::sphere_intersects_poly` | `0x0053CA30` | 326091 |
|
||||
| solid overlap | `BSPNODE::sphere_intersects_solid` | `0x0053CAF0` | 326130 |
|
||||
| solid/polygon overlap | `BSPNODE::sphere_intersects_solid_poly` | `0x0053CD50` | 326246 |
|
||||
|
||||
Interpretation aids:
|
||||
|
||||
- ACE `master` commit
|
||||
`65f092dd02505c04532b701c48d11af117cbc815`:
|
||||
`Physics/Transition.cs`, `SpherePath.cs`, `ObjectInfo.cs`,
|
||||
`Collision/CollisionInfo.cs`, and `Physics/BSP/{BSPTree,BSPNode,BSPLeaf}.cs`.
|
||||
- Chorizite DatReaderWriter `master` commit
|
||||
`c5359870963fecb55c9b90e8143a92fc7fe88e11`; acdream consumes package
|
||||
`2.1.7`. Its `PhysicsBSPNode` and `CellBSPNode` unpackers establish the
|
||||
source child-presence and stream order.
|
||||
- Existing acdream translations:
|
||||
[`transition_pseudocode.md`](transition_pseudocode.md),
|
||||
[`2026-06-24-obstruction-ethereal-pseudocode.md`](2026-06-24-obstruction-ethereal-pseudocode.md),
|
||||
[`2026-07-05-ccylsphere-collision-family-pseudocode.md`](2026-07-05-ccylsphere-collision-family-pseudocode.md),
|
||||
[`2026-07-07-csphere-collision-family-pseudocode.md`](2026-07-07-csphere-collision-family-pseudocode.md),
|
||||
and the physics digest.
|
||||
|
||||
Retail wins if an interpretation aid disagrees. Slice I keeps the current graph
|
||||
implementation executable until every flat query is compared against it. A
|
||||
pre-existing graph-vs-retail mismatch, if found, is adjudicated separately; it
|
||||
must not be hidden inside the layout cutover.
|
||||
|
||||
## 2. Retail scratch lifetime
|
||||
|
||||
`CTransition::makeTransition` is not a heap constructor per movement call:
|
||||
|
||||
```text
|
||||
on first use:
|
||||
construct static CTransition transit[10]
|
||||
|
||||
level = transition_level
|
||||
if level >= 10:
|
||||
return null
|
||||
|
||||
transition = &transit[level]
|
||||
CTransition::init(transition)
|
||||
transition_level++
|
||||
return transition
|
||||
```
|
||||
|
||||
`CTransition::cleanupTransition` only decrements `transition_level`. This is a
|
||||
ten-deep, stack-disciplined scratch arena. Nested transitions receive distinct
|
||||
records and release in LIFO order. The object graph and embedded arrays retain
|
||||
their storage identity; `init` resets the logical state before each lease.
|
||||
|
||||
Retail reset pseudocode:
|
||||
|
||||
```text
|
||||
CTransition::init:
|
||||
object_info.object = null
|
||||
object_info.state = 0
|
||||
object_info.targetID = 0
|
||||
SPHEREPATH::init(sphere_path)
|
||||
collision_info.last_known_contact_plane_valid = false
|
||||
collision_info.contact_plane_valid = false
|
||||
collision_info.sliding_normal_valid = false
|
||||
collision_info.collision_normal_valid = false
|
||||
collision_info.num_collide_object = 0
|
||||
clear last_collided_object and adjacent collision result fields
|
||||
collision_info.contact_plane_cell_id = 0
|
||||
|
||||
SPHEREPATH::init:
|
||||
num_sphere = 0
|
||||
begin_cell/begin_pos/curr_cell/check_cell = null
|
||||
insert_type = TRANSITION
|
||||
step_down/step_up/collide = false
|
||||
hits_interior_cell/bldg_check/obstruction_ethereal = false
|
||||
backup_cell = null
|
||||
walkable_allowance = 0
|
||||
walkable = null
|
||||
check_walkable/cell_array_valid/neg_step_up/neg_poly_hit = false
|
||||
placement_allows_sliding = true
|
||||
```
|
||||
|
||||
The acdream reset must cover additional modern representations of the same
|
||||
logical state: cell IDs instead of pointers, orientations, carried block
|
||||
origin, retained vertex buffers, diagnostic counters, GUID lists, target and
|
||||
self IDs, physics-state flags, water bits, and value-type planes/vectors.
|
||||
Retained storage may survive only if its logical contents are reset completely.
|
||||
|
||||
## 3. Immutable source storage
|
||||
|
||||
### Physics BSP
|
||||
|
||||
The source graph fields consumed by collision are:
|
||||
|
||||
```text
|
||||
PhysicsBSPNode:
|
||||
Type
|
||||
SplittingPlane
|
||||
PosNode, NegNode
|
||||
LeafIndex
|
||||
Solid
|
||||
BoundingSphere (origin + radius)
|
||||
Polygons[] (ordered ushort ids)
|
||||
```
|
||||
|
||||
DatReaderWriter decodes child presence from `BSPNodeType`. When both children
|
||||
exist, positive is read before negative. A flat representation therefore uses
|
||||
explicit signed child indices (`-1` = absent) and preserves source preorder:
|
||||
node, positive subtree, negative subtree.
|
||||
|
||||
### Cell-containment BSP
|
||||
|
||||
```text
|
||||
CellBSPNode:
|
||||
Type
|
||||
SplittingPlane
|
||||
PosNode, NegNode
|
||||
LeafIndex
|
||||
```
|
||||
|
||||
Containment trees do not carry physics leaf polygons or bounding spheres. They
|
||||
must remain a separate schema from the physics BSP rather than wasting fields
|
||||
or accidentally applying physics-node traversal rules.
|
||||
|
||||
### Resolved polygons
|
||||
|
||||
Runtime physics reads:
|
||||
|
||||
```text
|
||||
ResolvedPolygon:
|
||||
Id
|
||||
Plane (normal + D)
|
||||
SidesType
|
||||
NumPoints
|
||||
Vertices[] in source order
|
||||
```
|
||||
|
||||
The flat asset stores one polygon table and one contiguous vertex stream.
|
||||
Every polygon has an explicit `(vertexStart, vertexCount)` and every leaf has an
|
||||
explicit `(polygonIndexStart, polygonIndexCount)`. Polygon IDs are resolved to
|
||||
indices during preparation. Missing IDs are corrupt prepared content, never a
|
||||
runtime dictionary fallback.
|
||||
|
||||
Every float is copied through its `SingleToInt32Bits` representation. Planes
|
||||
are copied, not reconstructed from vertices.
|
||||
|
||||
### Setup and cell payload
|
||||
|
||||
`SetupPhysics` consumes ordered `CylSpheres`, ordered `Spheres`, `Height`,
|
||||
`Radius`, `StepUpHeight`, and `StepDownHeight`.
|
||||
|
||||
`CellPhysics` currently combines immutable geometry with publication state:
|
||||
physics BSP, resolved physics polygons, cell-containment BSP, world and inverse
|
||||
world transforms, ordered portals, resolved portal polygons, visible-cell IDs,
|
||||
and `SeenOutside`. The transforms and topology are per EnvCell placement.
|
||||
Immutable CellStruct collision geometry may alias only when its bytes and
|
||||
source identity agree; placement/topology may not be aliased merely because
|
||||
geometry matches.
|
||||
|
||||
## 4. Load-bearing traversal order
|
||||
|
||||
### Walkable search
|
||||
|
||||
```text
|
||||
find_walkable(node, path, mutableSphere, hitPoly, movement, up, changed):
|
||||
if node.boundingSphere does not intersect mutableSphere:
|
||||
return
|
||||
|
||||
distance = signedDistance(node.splittingPlane, mutableSphere.center)
|
||||
threshold = mutableSphere.radius - 0.0002
|
||||
|
||||
if distance >= threshold:
|
||||
visit positive only
|
||||
else if distance <= -threshold:
|
||||
visit negative only
|
||||
else:
|
||||
visit positive
|
||||
visit negative
|
||||
|
||||
find_walkable(leaf, ...):
|
||||
if no polygons or leaf bounds miss:
|
||||
return
|
||||
for polygon in leaf.polygons IN STORED ORDER:
|
||||
if polygon.walkable_hits_sphere(...)
|
||||
and polygon.adjust_sphere_to_plane(...):
|
||||
changed = true
|
||||
hitPoly = polygon
|
||||
```
|
||||
|
||||
The second child sees the sphere position mutated by the first child. A later
|
||||
successful polygon replaces `hitPoly`. Consequently child order, leaf order,
|
||||
and reference-style sphere mutation are behavior, not implementation detail.
|
||||
|
||||
### Six-path collision dispatch
|
||||
|
||||
`BSPTREE::find_collisions` evaluates in this order:
|
||||
|
||||
1. placement or obstruction-ethereal: solid test, primary sphere then secondary;
|
||||
2. `check_walkable`;
|
||||
3. `step_down`;
|
||||
4. pending `collide`: walkable search and adjustment;
|
||||
5. existing contact: primary collision and step-up, then secondary slide/negative hit;
|
||||
6. airborne/default: primary collision/set-collide, then secondary hard collision.
|
||||
|
||||
The dispatcher observes and mutates `SpherePath`, `ObjectInfo`, and
|
||||
`CollisionInfo`. The flat port must preserve every early return and the
|
||||
primary-before-secondary order.
|
||||
|
||||
### Cell containment
|
||||
|
||||
Point and radius-aware containment are distinct entry points.
|
||||
`sphere_intersects_cell_bsp` expands the radius by exactly `0.01`. Null-child,
|
||||
on-plane, and near-plane behavior is pinned by the current conformance tests and
|
||||
must be compared bit-for-bit during I4. No generic “BSP traversal helper” may
|
||||
merge containment, walkable, solid, and collision traversal; their null-child
|
||||
and return semantics differ.
|
||||
|
||||
## 5. Mutable query state and returned side effects
|
||||
|
||||
The query scratch is:
|
||||
|
||||
- `ObjectInfo`: state, step heights, scale, ethereal/step-down state, mover
|
||||
physics state, target ID, self ID, gravity state, and velocity-killed result.
|
||||
- `CollisionInfo`: live and last-known contact planes including cell/water
|
||||
metadata, sliding and collision normals, environment hit, stationary-fall
|
||||
count, adjustment offset, collision GUID list, last collision GUID, and
|
||||
diagnostic write count.
|
||||
- `SpherePath`: all local/global/current spheres, four positions and
|
||||
orientations, current/check/backup cells, carried block origin, movement
|
||||
offset, step-up/down state, mutable current/last walkable state, negative-hit
|
||||
state, insertion mode, placement policy, and building/interior/ethereal flags.
|
||||
- per-query `CollisionSphere` values used as reference-mutated valid positions.
|
||||
|
||||
Returned behavior includes more than `ResolveResult`: body contact and
|
||||
last-contact state, walkable polygon, stationary-fall/transient flags, sliding
|
||||
normal, killed velocity, selected hit polygon, collision GUIDs, and swept cell
|
||||
membership. Differential tests must compare these side effects as well as the
|
||||
boolean/enum return and final position.
|
||||
|
||||
## 6. Production graph-call inventory
|
||||
|
||||
| Entry point | Production consumers |
|
||||
|---|---|
|
||||
| `PointInsideCellBsp` | `World/Cells/EnvCell`; `CellTransit` point/portal membership; `Transition.CheckOtherCells` diagnostics and admission |
|
||||
| `SphereIntersectsCellBsp` | `CellTransit` candidate, current-cell, and transit membership; `Transition.CheckOtherCells` |
|
||||
| `FindCrossedEdge` | precipice slide and nearest-inside-edge handling in `SpherePath`/`Transition` |
|
||||
| `FindWalkableSphere` | indoor walkable-plane probe in `Transition.TryFindIndoorWalkablePlane` |
|
||||
| `FindCollisions` | EnvCell physics BSP, static/multipart GfxObj BSP, and building-shell BSP in `Transition` |
|
||||
|
||||
The legacy dictionary/`VertexArray` overloads and static
|
||||
`SphereIntersectsPoly*` entry points currently serve tests and diagnostics, not
|
||||
the production movement call graph. I6 removes them from production ownership
|
||||
only after their required tooling users are explicit.
|
||||
|
||||
## 7. Parsed graph retention inventory
|
||||
|
||||
The current worker `LandblockBuildFactory` creates a `PhysicsDatBundle` holding
|
||||
raw `LandBlockInfo`, `EnvCell`, `Environment`, `Setup`, and `GfxObj` DBObjs in
|
||||
per-build dictionaries. It also opportunistically populates
|
||||
`PhysicsDataCache` while gathering entity and scenery bounds.
|
||||
|
||||
On the update thread, `LandblockPhysicsPublisher`:
|
||||
|
||||
1. resolves EnvCell → Environment → CellStruct from that raw bundle;
|
||||
2. calls `CacheCellStruct`, retaining physics BSP, containment BSP, raw polygon
|
||||
dictionaries/vertex arrays, resolved polygons, and topology;
|
||||
3. calls `CacheGfxObj`, retaining GfxObj physics BSP, raw polygon/vertex graphs,
|
||||
resolved polygons, and bounds;
|
||||
4. reads Setup graphs for collision shapes, buildings, and multipart statics.
|
||||
|
||||
`LandblockStaticPresentationPublisher` also reads raw Setup light definitions.
|
||||
`LandblockStreamResultCost` currently counts bundle dictionary entries but not
|
||||
the complete transitive DBObj graph. I3 must replace collision publication with
|
||||
prepared immutable payloads without removing the separately required render or
|
||||
lighting data.
|
||||
|
||||
## 8. Fixed fixture ledger
|
||||
|
||||
| Required shape | Existing fixed evidence |
|
||||
|---|---|
|
||||
| outdoor/no BSP | `PhysicsEngineTests`, `CellMarchLandblockPreservationTests`, projectile terrain fixtures |
|
||||
| Facility Hub indoor | `Issue137CorridorSeamReplayTests` (`0x8A02016E↔0x8A02017A`), `Issue180CorridorSweepHysteresisReplayTests` (`0x8A020164`) |
|
||||
| Holtburg cottage indoor | `ThresholdPortalCrossingReplayTests`, `CameraCornerSealReplayTests`, `Fixtures/flap-doorway/*` |
|
||||
| staircase/ramp | `BSPStepUpFixtures`, `Issue185OutdoorStairsSeamReplayTests` |
|
||||
| cellar lip | `CellarLipWedgeTests`, `CellarUpTrajectoryReplayTests`, `Fixtures/cellar-lip/*`, `Fixtures/issue98/*` |
|
||||
| multipart door/building shell | `DoorCollisionApparatusTests`, `DoorBugTrajectoryReplayTests`, GfxObj fixture `0x01000A2B` |
|
||||
| projectile/thin wall | `ProjectilePhysicsStepperTests.RegisterThinBspWall` |
|
||||
| asymmetric/null children | `SphereIntersectsCellBspTests`, `PhysicsEngineAdjustPositionTests`, real DAT containment fixtures |
|
||||
| multi-polygon/equal candidate order | `BSPQueryTests`, `IndoorWalkablePlaneTests`, `BSPStepUpFixtures`; I2 adds explicit flat-order structural cases before flattening gates |
|
||||
|
||||
I0 graph-path fixture command:
|
||||
|
||||
```powershell
|
||||
dotnet test tests\AcDream.Core.Tests\AcDream.Core.Tests.csproj -c Release --no-build `
|
||||
--filter "FullyQualifiedName~BSPQueryTests|FullyQualifiedName~SphereIntersectsCellBspTests|FullyQualifiedName~CellarLipWedgeTests|FullyQualifiedName~CellarUpTrajectoryReplayTests|FullyQualifiedName~CameraCornerSealReplayTests|FullyQualifiedName~ProjectilePhysicsStepperTests|FullyQualifiedName~DoorCollisionApparatusTests|FullyQualifiedName~Issue137CorridorSeamReplayTests|FullyQualifiedName~ThresholdPortalCrossingReplayTests|FullyQualifiedName~Issue185OutdoorStairsSeamReplayTests|FullyQualifiedName~CylSphereFamilyTests|FullyQualifiedName~SphereCollisionFamilyTests|FullyQualifiedName~TransitionAllocationBaselineTests"
|
||||
```
|
||||
|
||||
Result on 2026-07-25: **111 passed, 0 skipped, 0 failed** against the current
|
||||
object-graph route and installed retail DATs.
|
||||
|
||||
## 9. Allocation baseline
|
||||
|
||||
`TransitionAllocationBaselineTests` warms 256 calls, measures 4,096 Release
|
||||
resolves on one thread, and exercises a fixed BSP wall through the four
|
||||
production mover call shapes:
|
||||
|
||||
| Mover | Current graph path |
|
||||
|---|---:|
|
||||
| player, two spheres | 4,448 B/resolve |
|
||||
| remote, two spheres | 4,448 B/resolve |
|
||||
| projectile, one 5 cm sphere | 6,848 B/resolve |
|
||||
| camera/viewer, one 30 cm sphere | 2,512 B/resolve |
|
||||
|
||||
The projectile crosses the same distance using a much smaller sphere, causing
|
||||
more retail substeps and therefore more per-step query-scratch allocation.
|
||||
This is why allocation must be measured by mover family rather than quoting one
|
||||
average.
|
||||
|
||||
I1's gate is zero steady allocation attributable to transition construction,
|
||||
walkable buffer cloning, and `CollisionSphere` query scratch after warmup.
|
||||
It does not claim that every optional diagnostic/capture path is allocation
|
||||
free.
|
||||
Loading…
Add table
Add a link
Reference in a new issue