docs(research): handoff — collision-object-detection vs retail deep-dive ("1 fix for all collision")
Next-session brief: build retail's collision-inclusion model (PhysicsState/ ETHEREAL/HAS_PHYSICS_BSP predicate + per-cell shadow-list registration + the building-shell/terrain/EnvCell channels), map acdream's per-channel ad-hoc filters against it, enumerate deviations, and design ONE unified retail-faithful mechanism to replace them. Motivated by this session's #146/#147 — both were the same shape (a per-channel filter diverging from retail). Brainstorm-gated, report-first, no guess-patches. Includes the apparatus inventory + a paste-ready prompt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2b48310c2a
commit
bc4f71b932
1 changed files with 161 additions and 0 deletions
|
|
@ -0,0 +1,161 @@
|
|||
# Handoff — collision-object-detection vs retail: a unifying audit ("1 fix for all collision") — 2026-06-24
|
||||
|
||||
**Branch/worktree:** `claude/thirsty-goldberg-51bb9b`. Named-retail + references live in the MAIN repo
|
||||
`C:\Users\erikn\source\repos\acdream\`.
|
||||
|
||||
## The ask (user, 2026-06-24)
|
||||
|
||||
> "Deep-dive in how we detect what objects have collision compared to retail. I expect/know there are
|
||||
> deviations vs retail. Thorough checking of how retail checks what objects have collision and how it
|
||||
> deviates from our implementation. This should be **1 fix for all collision**."
|
||||
|
||||
So: **NOT another whack-a-mole symptom fix.** Build the retail model of *"which objects collide, and how
|
||||
the engine decides that"*, map acdream's model against it, enumerate every deviation, and design **one
|
||||
unified, retail-faithful collision-inclusion mechanism** that replaces acdream's scattered per-channel
|
||||
hand-rolled filters. This is a research → brainstorm → multi-commit phase. **Brainstorm-gate it**
|
||||
(`superpowers:brainstorming`) before writing the port. **No guess-patches** — this is the DO-NOT-RETRY
|
||||
collision area; grep named-retail FIRST, decompile, pseudocode, port, conformance-test.
|
||||
|
||||
## Why now — the pattern the 2026-06-24 session exposed
|
||||
|
||||
This session fixed three collision bugs, and **two of them were the *same shape*: an ad-hoc per-channel
|
||||
filter that diverges from retail's unified model.**
|
||||
|
||||
| Commit | Issue | The deviation it revealed |
|
||||
|---|---|---|
|
||||
| `afd5f2a` | #138-B | Avatar relocated into a dead landblock during PortalSpace (lifecycle, not inclusion) |
|
||||
| `49d743f` | #146 | **Building collision shell cached at a stale `_liveCenter` frame** — terrain re-bases per apply, the building cache didn't. Streaming-relative-frame staleness. |
|
||||
| `9743537` | #147 | **Portal-less buildings (city/perimeter walls) skipped** by `if (building.Portals.Count == 0) continue;` — a *transit-feature* filter that wrongly gated *collision*. We used "has a doorway" to decide "is solid". |
|
||||
|
||||
#147 is the smoking gun for the user's intuition: **acdream decides "does this object collide" with
|
||||
per-channel attributes that have nothing to do with retail's actual predicate.** There are almost
|
||||
certainly more of these. The goal is to replace the lot with retail's real rule.
|
||||
|
||||
## The retail model — what to research (the oracle)
|
||||
|
||||
Retail (`docs/research/named-retail/acclient_2013_pseudo_c.txt` — grep by `class::method`) decides
|
||||
collision in two layers. Map BOTH precisely:
|
||||
|
||||
### A. The inclusion predicate — "does this object participate in collision at all?"
|
||||
- **`PhysicsState` flags** on `CPhysicsObj` (grep `acclient.h` for the `PhysicsState` enum). The
|
||||
load-bearing ones:
|
||||
- **`ETHEREAL`** → no collision (pass-through). Confirm acdream honors this on EVERY channel, not just
|
||||
interaction-pick. (See `[[project_interaction_pipeline]]` ETHEREAL-alone exemption — that's
|
||||
*selection*, not *collision*; verify the collision side.)
|
||||
- **`HAS_PHYSICS_BSP` / `HAS_PHYSICS_BSP_PS`** → the object collides via its BSP; otherwise via a
|
||||
cylinder/sphere. This is the binary dispatch (`[[feedback_retail_binary_dispatch]]`,
|
||||
`BspOnlyDispatch` flag `0x00010000`).
|
||||
- Other gates to check: `STATIC`, `REPORT_COLLISIONS`, `IGNORE_COLLISIONS`, `MISSILE`, `NODRAW` (NODRAW
|
||||
is render-only — confirm it does NOT gate collision), `SCRIPTED_COLLISION`.
|
||||
- **The object's physics shape**: does it have a `CPhysicsBSP` (collision BSP) vs a `CylSphere`? Retail
|
||||
uses whatever the dat/weenie says — it does NOT fabricate collision from a render mesh AABB. (acdream's
|
||||
`IsPhantomGfxObjSource` / mesh-AABB-fallback is a known divergence — #101.)
|
||||
|
||||
### B. The registration / dispatch model — "where does the engine look for colliders?"
|
||||
- **`CPhysicsObj::FindObjCollisions`** (the per-tick collider) — picks BSP-only vs cyl+sphere on the
|
||||
flag above, iterates the **per-cell `shadow_object_list`**.
|
||||
- **`CObjCell::add_shadows_to_cells` / `find_cell_list` / `calc_cross_cells`** — an object registers a
|
||||
*shadow* into every cell its sphere overlaps; the per-cell list is the broad phase. (acdream ported
|
||||
this as the A6.P4/BR-7 `ShadowObjectRegistry` flood — see `[[project_physics_collision_digest]]`.)
|
||||
- **`CBuildingObj::find_building_collisions`** (`0x006b5300`) — the building **shell** BSP, tested on
|
||||
`part_array->parts[0]`, **independent of the portal list**. (This session's #147 fix aligned acdream
|
||||
to this.) `CLandBlock::init_buildings` (`0x0052fd80`) builds one building per origin landcell.
|
||||
- **Terrain**: `CLandCell::find_env_collisions` (terrain polys) + the eye-side cull.
|
||||
- **EnvCell (indoor)**: `CEnvCell::find_env_collisions` (`0x0052c100`) — the cell's own BSP; **no
|
||||
building leg** (acdream matches this at `TransitionTypes.cs:2807`).
|
||||
|
||||
**Deliverable from this layer:** a single table — for each object class (terrain, building shell,
|
||||
EnvCell shell, static stab, scenery, server weenie/door/NPC/item, missile) — *what makes it collide in
|
||||
retail* (which flags, which shape, which registration), with the named-retail function + address.
|
||||
|
||||
## The acdream model — the code map to audit against the table
|
||||
|
||||
acdream has **separate channels with different inclusion filters**. Walk each and record its de-facto
|
||||
"does it collide" predicate, then diff against retail's:
|
||||
|
||||
1. **Terrain** — `PhysicsEngine.SampleTerrainWalkable` / `TransitionTypes.cs:2229` (`// no terrain loaded
|
||||
→ allow pass-through`, `:2247`). Re-bases per apply via `AddLandblock(origin)` (so no #146-style
|
||||
staleness). Filter: terrain residency only.
|
||||
2. **Buildings** — `TransitionTypes.FindBuildingCollisions` (`:2805`), `PhysicsDataCache.CacheBuilding`
|
||||
(`:441`), the cache loop (`GameWindow.cs` ~`:6958`). Post-session: re-bases per apply (#146) +
|
||||
includes portal-less (#147). Remaining filter: `ModelId == 0 → inert` (`:2813`); the `[bldg-channel]`
|
||||
probe (`ACDREAM_PROBE_BUILDING`, logs `bldOrigin`) is the live lens.
|
||||
3. **Shadow objects** (statics, scenery, server weenies) — `Core/Physics/ShadowObjectRegistry.cs`
|
||||
(per-cell flood, BR-7). Registration sites: `GameWindow.cs` `ShadowObjects.Register*` (~`:7196–7526`),
|
||||
`RefloodLandblock` (`:7595`), `RegisterMultiPart` (`:3835`). Server weenies register via
|
||||
`OnLiveEntitySpawnedLocked` → Register. **Filters to scrutinize for divergence:**
|
||||
- `LandblockLoader.IsSupported` (GfxObj/Setup mask only) — does it match retail's "has a physics
|
||||
shape"? What about `0x0D`/other types?
|
||||
- **mesh-AABB fallback** / `IsPhantomGfxObjSource` (#101) — acdream fabricates a collision box from
|
||||
the render mesh when no physics BSP exists. **Retail does not.** Prime deviation.
|
||||
- cyl-vs-BSP dispatch (A6.P7, `Transition.BspOnlyDispatch`) — verify it matches `HAS_PHYSICS_BSP_PS`.
|
||||
- editor-marker degrade-hide (#136) — those are render-hidden; confirm collision treatment matches.
|
||||
- ETHEREAL: does the live-spawn path exclude ETHEREAL weenies from shadow registration?
|
||||
4. **EnvCell (indoor)** — cell BSP via the transition; `CellTransit` per-cell iteration.
|
||||
5. **The streaming-relative frame** — #146 proved a cached collision transform can go stale on recenter.
|
||||
**Audit every cached collision transform** (shadow objects? EnvCell? portal planes?) for the same
|
||||
staleness; or fold into the #145 cell-relative-frame port so collision frames can NEVER go stale.
|
||||
|
||||
## Suspected deviations to confirm/refute (seed list)
|
||||
|
||||
- **Attribute-as-filter category errors** (the #147 pattern): grep the registration/collision paths for
|
||||
any `continue`/skip gated on a NON-collision property (portals, name, render flags, degrade, item
|
||||
type). Each is a candidate divergence.
|
||||
- **mesh-AABB phantom collision** (#101) — retail-absent; should it exist at all?
|
||||
- **ETHEREAL not honored on the collision side** of one or more channels.
|
||||
- **`IsSupported`/type filters** excluding objects retail collides with (or including ones it doesn't).
|
||||
- **Stale cached collision transforms** beyond buildings (#146 class).
|
||||
- **Static vs dynamic registration mismatch** — landblock stabs/scenery vs server weenies taking
|
||||
different inclusion paths with different rules.
|
||||
|
||||
## The unifying target ("1 fix for all collision")
|
||||
|
||||
One retail-faithful **collision descriptor per object**, derived once from its `PhysicsState` + physics
|
||||
shape (BSP vs cyl, or none = ethereal/no-collide), and **one registration rule** (overlap-based per-cell
|
||||
shadow lists) used by **every** channel — replacing the per-channel ad-hoc filters. Concretely the
|
||||
output is likely: (a) a single `ObjectCollides(state, shape)` predicate matching retail's flag logic,
|
||||
(b) a single registration path all object sources funnel through, (c) deletion of the mesh-AABB phantom
|
||||
fallback and the attribute-as-filter skips. Expect multi-commit; gate with brainstorming; conformance-
|
||||
test against named-retail golden values + the existing replay harnesses (`CellarUp*`, `HouseExitWalk*`,
|
||||
`CornerFlood*`, the new `Issue147ArwicBuildingsDumpTests` fixture).
|
||||
|
||||
## Method (mandatory — CLAUDE.md workflow)
|
||||
|
||||
1. `superpowers:brainstorming` to scope the phase + agree the unified model BEFORE code.
|
||||
2. **Grep named-retail FIRST** for every function in the retail-model table; decompile via Ghidra MCP
|
||||
(`patchmem.gpr`, port 8081) for any single function that's noisy in the bulk text.
|
||||
3. Cross-reference **ACE** `Physics/Common` (PhysicsObj, ObjCell, shadow lists) + **ACViewer**
|
||||
`Physics/Common` + **holtburger** `spatial/physics.rs` for the client-side read.
|
||||
4. Write pseudocode (`docs/research/*_pseudocode.md`), port faithfully, conformance-test, then one
|
||||
comprehensive user visual gate.
|
||||
|
||||
## Apparatus already in place (use it, don't rebuild)
|
||||
- `ACDREAM_PROBE_BUILDING` → `[bldg-channel]` (now logs `bldOrigin`) + `[entity-source]` (BSP vs Cylinder
|
||||
classification per static — directly shows what collision shape each object got).
|
||||
- `ACDREAM_CAPTURE_RESOLVE=<path>` → per-player-resolve JSONL (grounded / contactPlane / collisionNormal
|
||||
per frame). Heavy (FPS) — short targeted runs only; the `analyze_*` streaming-tally pattern works
|
||||
(255k records in seconds).
|
||||
- `ACDREAM_PROBE_ENT` / `EntityVanishProbe` → avatar draw-set lifecycle (#138-B).
|
||||
- `Issue147ArwicBuildingsDumpTests` → the dat-dump pattern for `LandBlockInfo.Buildings`/`.Objects`
|
||||
(portal counts, model ids); clone it for any landblock/object-class inventory.
|
||||
- Live client: user `notan` / `MittSnus81!`, ACE `127.0.0.1:9000`; graceful close before rebuild.
|
||||
|
||||
## Context — what shipped this session (the motivation)
|
||||
`afd5f2a` #138-B avatar-vanish, `49d743f` #146 building-frame re-base, `9743537` #147 portal-less-wall
|
||||
collision. ISSUES #146 DONE, #147 walls DONE (terrain-3%-grounded residual re-scoped LOW — verify it's
|
||||
even real before acting; see #147). The deep #145 cell-relative-frame port is still its own future phase
|
||||
and is the natural home for "collision frames can never go stale."
|
||||
|
||||
## Paste-ready prompt for the next session
|
||||
|
||||
> Deep-dive + unifying fix: **how retail decides which objects have collision, vs acdream.** Read this
|
||||
> handoff (`docs/research/2026-06-24-collision-object-detection-vs-retail-deepdive-handoff.md`) and the
|
||||
> physics digest (`claude-memory/project_physics_collision_digest.md`) FIRST. Goal: build retail's
|
||||
> collision-inclusion model (the `PhysicsState`/ETHEREAL/HAS_PHYSICS_BSP predicate + the per-cell
|
||||
> shadow-list registration + the building-shell/terrain/EnvCell channels — grep named-retail, cite
|
||||
> addresses), map acdream's per-channel filters against it (terrain / FindBuildingCollisions /
|
||||
> ShadowObjectRegistry / EnvCell / mesh-AABB phantom / IsSupported / the cyl-BSP dispatch), enumerate
|
||||
> every deviation, and propose ONE unified retail-faithful collision-inclusion mechanism to replace the
|
||||
> ad-hoc filters. **Report-first / brainstorm-gated, no guess-patches** (DO-NOT-RETRY collision area).
|
||||
> Recent precedent (the per-channel filter divergences this is meant to end): #146 (`49d743f`),
|
||||
> #147 (`9743537`).
|
||||
Loading…
Add table
Add a link
Reference in a new issue