docs: overnight contracts — #330 headless collision, AP-159 S1B box-admit; morning-gate skeleton
Campaign S night shift. #330's contract pins the scoping facts so the implementer inherits measurements instead of re-deriving them: the builder is already presentation-free logic (the only App coupling is one identity-guard parameter), headless has full content (_content.Dats + prepared PhysicsDataCache), shadow-sync already runs in Runtime once a shadow exists, and the no-window inbound route is host-disjoint per AD-64 so registration wired there cannot double-register on the graphical host. The local-player ProvenShapeless pin is explicitly OUT — fixing it blind risks the K-series gates. AP-159's S1B contract maps the pseudo-C line ranges for the part-array find_transit_cells overload, the box-vs-cell BSP traversal, and flags the adjacent overload's Binary Ninja signature artifact for the mandatory pseudocode step to resolve. House rule carried: a new traversal in two representations ships with an exact differential referee, and the direction assertion (membership strictly shrinks) is a test, not an assumption. Also settled at scoping, evidence in the S1A brief: AP-157's CylHeight half is a NON-divergence — CObjCell::find_cell_list's cylsphere overload @0x0052b9f0 copies localtoglobal(low_pt) + radius per cylsphere, capped at 10, and never reads height; acdream's base-point cylinder flood is exactly retail's behaviour. Register correction follows with S1A's measurement numbers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
8c97084289
commit
04b794ad7c
3 changed files with 284 additions and 0 deletions
50
docs/plans/2026-08-07-morning-gate-checklist.md
Normal file
50
docs/plans/2026-08-07-morning-gate-checklist.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# Morning gate — 2026-08-07 (one sitting)
|
||||
|
||||
**Status: DRAFT — the overnight session updates this as work lands. Do not
|
||||
run it until this banner says READY.**
|
||||
|
||||
Everything below folds the deferred visual gates into one sitting, ordered so
|
||||
travel between sites doubles as #339 reproduction attempts. Launch is
|
||||
prepared by the overnight session; **the client is NOT launched until you're
|
||||
back** (your instruction).
|
||||
|
||||
## 0. Launch (the lead runs this when you say go)
|
||||
|
||||
Main checkout, absolute path, probes armed. The first `[step-h]` line in the
|
||||
log must print the assembly path under
|
||||
`C:\Users\erikn\source\repos\acdream\src\` — that is the wrong-binary guard,
|
||||
now part of every capture.
|
||||
|
||||
## 1. Already verified last night — nothing owed
|
||||
|
||||
- **#32 local edge-slide** — you passed it at the Rithwic cliff ("Yes works
|
||||
now"). Closed.
|
||||
- **#338 step heights** — headline refuted by measurement; no production
|
||||
change happened, so there is nothing to look at. Closed (AD-68 records the
|
||||
benign residual).
|
||||
|
||||
## 2. The sitting
|
||||
|
||||
| # | What | Where / how | Pass looks like |
|
||||
|---|---|---|---|
|
||||
| G1 | *(placeholder — filled in by the overnight landings: #330 is
|
||||
automated-gate-only and needs no eyes; S1/S2/S3/S4/S5 rows appear here
|
||||
exactly as far as the night got)* | | |
|
||||
|
||||
## 3. Free riders during travel
|
||||
|
||||
- **#339 portal-space hang** — every portal you take is a reproduction
|
||||
attempt; if you get stuck again, the log captures the readiness flags and
|
||||
the session becomes the diagnosis session. Nothing to do actively.
|
||||
|
||||
## 4. Deliberately NOT in this sitting
|
||||
|
||||
- **C5c's connected-gate batch** (~1 hr: D-1 scenarios, AP-136 six-step park,
|
||||
route-7, two-client, nine-stop soak) — standing debt by your direction;
|
||||
say the word any morning and it becomes its own sitting. The probe family
|
||||
stays in the tree until it runs.
|
||||
- **AP-156 scale question** — retail does NOT scale cross-cell flood spheres;
|
||||
we do. Matching retail here means adopting a retail bug whose direction is
|
||||
walk-through for shrunk objects. Your call, explicitly, before any code
|
||||
changes: keep our over-inclusive scaling (safe, divergent) or match retail
|
||||
(faithful, under-inclusive for scale<1). One sentence from you settles it.
|
||||
123
docs/research/2026-08-07-330-contract.md
Normal file
123
docs/research/2026-08-07-330-contract.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
# #330 contract — headless live-entity collision registration
|
||||
|
||||
**Date:** 2026-08-07 (overnight session). **Scoped by:** the session lead.
|
||||
**Implementer:** one Sonnet agent against THIS contract. **Review:** dual Opus
|
||||
(retail-conformance + architecture) after implementation.
|
||||
|
||||
## The defect
|
||||
|
||||
`ShadowShapeBuilder.FromSetup` — the only producer of live-entity collision
|
||||
shapes — has exactly one production caller, `AcDream.App`'s
|
||||
`LiveEntityCollisionBuilder`, wired through the graphical materializer.
|
||||
`AcDream.Headless` references only Runtime/Content/Core, so **no headless code
|
||||
path ever builds or registers a collision shape for a server-spawned entity**.
|
||||
A bot has static landblock collision but walks through every NPC, player, and
|
||||
spawned object. Filed at the AP-22 blast-radius survey
|
||||
(`docs/research/2026-08-06-ap22-contract.md` §5).
|
||||
|
||||
## Facts established at scoping (do not re-derive)
|
||||
|
||||
1. `LiveEntityCollisionBuilder` + `LiveEntityDefaultPoseResolver` are already
|
||||
presentation-free logic: their imports are Core, Core.Net
|
||||
(`WorldSession.EntitySpawn`), Core.World (`WorldEntity`), DatReaderWriter.
|
||||
The ONLY App coupling is the `LiveEntityRecord exactRecord` identity-guard
|
||||
parameter on `Build(...)` and the `AcDream.App.World` using it drags in.
|
||||
2. The headless host HAS all required content:
|
||||
`HeadlessSessionWorldProjection` holds `_content.Dats` (full
|
||||
`DatCollection` — `Setup` and MotionTable DBObjs resolvable) and
|
||||
`_content.PreparedCollision` → per-landblock `PhysicsDataCache` with flat
|
||||
GfxObj physics (`GetFlatGfxObj`).
|
||||
3. Once a shadow IS registered, movement follows automatically:
|
||||
`RuntimeRemotePhysicsUpdater` (Runtime, both hosts) already calls
|
||||
`ShouldSynchronizeShadow` at line ~844 on its tick. Registration is the
|
||||
whole gap.
|
||||
4. The no-window inbound entity route is
|
||||
`RuntimeLiveEntitySessionController` (`src/AcDream.Runtime/Session/`),
|
||||
constructed by `HeadlessSessionHost` (~line 682). Per AD-64 the graphical
|
||||
host runs a PARALLEL App-side route (`LiveEntitySessionController` →
|
||||
`DatLiveEntityProjectionMaterializer`, which calls
|
||||
`_collisionBuilder.Build(...)` at ~line 832 and
|
||||
`LiveEntityCollisionBuilder.Register` at ~line 840). Wiring registration
|
||||
into the Runtime controller therefore CANNOT double-register on the
|
||||
graphical host. Unification of the two routes is #324, NOT this task.
|
||||
5. The graphical local-player disposition
|
||||
(`SessionPlayerComposition` ~line 573) chooses
|
||||
`RegisteredAuthoredPayload` vs `ProvenShapeless` by
|
||||
`PhysicsEngine.ShadowObjects.HasLogicalOwner(key.LocalEntityId)`. The
|
||||
headless pin (`HeadlessSessionHost` ~line 637) hardcodes
|
||||
`ProvenShapeless`.
|
||||
|
||||
## Scope — IN
|
||||
|
||||
**A. Hoist, no behaviour change.** Move
|
||||
`src/AcDream.App/Physics/LiveEntityDefaultPoseResolver.cs` and
|
||||
`src/AcDream.App/Physics/LiveEntityCollisionBuilder.cs` (with
|
||||
`LiveEntityCollisionRegistration`) to `src/AcDream.Runtime/Physics/`,
|
||||
namespace `AcDream.Runtime.Physics`. Change `Build(...)`'s
|
||||
`LiveEntityRecord exactRecord` parameter to presentation-free primitives:
|
||||
`uint expectedServerGuid, ulong expectedGeneration, WorldEntity
|
||||
expectedEntity` — the guard's three comparisons keep IDENTICAL semantics
|
||||
(guid match, generation match, reference-equality on the entity). Adjust
|
||||
visibility so App and Headless both reach it (match how neighbouring Runtime
|
||||
types are exposed to App; prefer `public` over new InternalsVisibleTo
|
||||
entries). Update every App call site; `git grep LiveEntityCollisionBuilder`
|
||||
first and check each hit — several are comments.
|
||||
|
||||
**B. No-window registration route.** Give
|
||||
`RuntimeLiveEntitySessionController` an OPTIONAL collision registrar seam
|
||||
(null = today's behaviour, which the graphical host keeps). Constructed by
|
||||
`HeadlessSessionHost` from `_content.Dats` + the prepared collision source +
|
||||
`Runtime.EntityObjects.Physics`:
|
||||
- On the controller's accepted-spawn/materialization commit (find the exact
|
||||
seam by reading the controller — the same acceptance the graphical
|
||||
materializer keys on), resolve the entity's `Setup` DBObj and default pose,
|
||||
call the hoisted `Build(...)` with the setup's OWN part ids as
|
||||
`effectivePartGfxObjIds` (headless has no appearance system — note this in
|
||||
a code comment as a deliberate headless/graphical difference: an
|
||||
appearance-swapped BSP part collides as the default part on headless), and
|
||||
`Register` into the engine's `ShadowObjects`.
|
||||
- On despawn/teardown, remove the registration the same way the graphical
|
||||
path does; the K-series ownership-ledger convergence tests must stay green.
|
||||
- On scale/appearance updates: headless has no appearance route; scale
|
||||
arrives via the spawn — if the controller processes a scale-changing
|
||||
update, re-register (check whether such a path exists; if none does, write
|
||||
that down in the code comment rather than inventing one).
|
||||
|
||||
**C. Tests (DAT-free, deterministic — model:
|
||||
`Issue333BroadphaseReachFilterTests`).** In `tests/AcDream.Runtime.Tests/`
|
||||
(or Headless.Tests if the fixture fits better — pick ONE):
|
||||
1. Drive the no-window route with a synthetic accepted spawn whose Setup
|
||||
resolves to a sphere shape via `PhysicsDataCache.RegisterGfxObjForTest`(or
|
||||
the Setup-level equivalent the existing tests use). Assert the entity
|
||||
appears in `ShadowObjects.GetObjectsInCell` for its cell, then
|
||||
`ResolveWithTransition` a 0.48 m mover into it and assert the move is
|
||||
blocked/adjusted — the end-to-end "bot cannot walk through an NPC" fact.
|
||||
2. Teardown row: despawn removes the shadow registration.
|
||||
3. SABOTAGE-VERIFY in-session: disable the registrar wiring (pass null) and
|
||||
confirm test 1 reds; restore; record the sabotage result in your report.
|
||||
|
||||
## Scope — OUT (do not touch)
|
||||
|
||||
- The local-player `ProvenShapeless` pin (fact 5). Registering the local
|
||||
player's own shadow is a separate seam (`LocalPlayerShadowSynchronizer`)
|
||||
with its own lifecycle; fixing it blind tonight risks the K-series gates.
|
||||
It stays pinned; the session lead files the residual.
|
||||
- #324 (route unification), any graphical-host behaviour, any
|
||||
divergence-register or ISSUES edit (the session lead does those).
|
||||
- `ShadowObjectRegistry` / `ShadowShapeBuilder` internals.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- `dotnet build AcDream.slnx -c Release` green.
|
||||
- Full suite `dotnet test AcDream.slnx -c Release -m:1` green — report the
|
||||
exact totals.
|
||||
- The new tests pass, and the sabotage check was performed and reddened.
|
||||
- NOTHING committed — leave the working tree for review.
|
||||
|
||||
## Process rules
|
||||
|
||||
Work ONLY in `C:\Users\erikn\source\repos\acdream` (absolute paths — there
|
||||
are other checkouts; running or building anything by relative path from the
|
||||
wrong cwd cost this project a void verdict yesterday). Do NOT spawn
|
||||
subagents. If a fact above contradicts what you read in the source, STOP and
|
||||
report the contradiction instead of improvising around it.
|
||||
111
docs/research/2026-08-07-ap159-s1b-contract.md
Normal file
111
docs/research/2026-08-07-ap159-s1b-contract.md
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
# AP-159 / #335 contract (Campaign S slice S1B) — port the INDOOR box-admit half of retail's part-array `find_transit_cells`
|
||||
|
||||
**Date:** 2026-08-07 (overnight). **Scoped by:** the session lead.
|
||||
**Implementer:** one Sonnet agent against THIS contract. **Review:** dual
|
||||
Opus after. **Severity honest-check:** LOW — over-inclusive only (extra
|
||||
indoor broadphase candidates, never a missed one). It is in scope tonight
|
||||
because it is the last half of AP-156's traversal residual and Campaign S's
|
||||
S1; if any step below turns out larger than described, STOP and report
|
||||
rather than compressing quality.
|
||||
|
||||
## The divergence
|
||||
|
||||
acdream's indoor cell-membership walk for multi-part BSP objects
|
||||
(`CellTransit.BuildShadowCellSetFromParts`, indoor arm) admits a neighbouring
|
||||
EnvCell on a **sphere**-vs-portal-plane test (`FindTransitCellsSphere` over
|
||||
`BuildBspPartSpheres` output). Retail's part-array
|
||||
`CEnvCell::find_transit_cells` uses the sphere only as a **cheap reject**;
|
||||
the ADMITTING test is **box**-vs-plane, followed by a **box-vs-cell BSP**
|
||||
gate in the destination cell. Full disassembly-backed spec: the #335 entry in
|
||||
`docs/ISSUES.md` (steps 1–4 with addresses) — that entry is the oracle of
|
||||
record; this contract adds the pseudo-C line map.
|
||||
|
||||
## Oracle map (grep-named-first is already done — these are the exact places)
|
||||
|
||||
`docs/research/named-retail/acclient_2013_pseudo_c.txt`:
|
||||
|
||||
- **Part-array overload** `CEnvCell::find_transit_cells(this, count,
|
||||
CPhysicsPart** parts, CELLARRAY*)` @0x0052cae0 — lines **310127–310257**.
|
||||
Per portal x per part: sphere cheap-reject (`physics_sphere` centre through
|
||||
`Position::localtolocal`, eps = `F_EPSILON + radius` @0x0052cb65) -> box
|
||||
admit (`CPhysicsPart::GetBoundingBox` @0x0050d600 -> `BBox::LocalToLocal`
|
||||
@0x005b1e60 -> `Plane::intersect_box` @0x005aa170 @0x0052cc05) -> if
|
||||
portal-side differs: `other_cell_id == 0xFFFFFFFF` sets leads-outside,
|
||||
else `CCellPortal::GetOtherCell` (threading `do_not_load_cells`) then
|
||||
box-vs-destination-BSP `CCellStruct::box_intersects_cell` @0x00533910
|
||||
(line 317675: one-liner into `BSPTREE::box_intersects_cell_bsp`
|
||||
@0x005398b0 -> `BSPNODE::box_intersects_cell_bsp` @0x0053c880, body at
|
||||
line **325993**) gating the add @0x0052cc5a; after all portals,
|
||||
leads-outside runs `add_all_outside_cells` @0x0052ccea.
|
||||
- **Adjacent overload** @0x0052c820 (lines **309968–310126**) also contains a
|
||||
box refinement block (@0x0052c76c–0x0052c7d2). Its Binary Ninja signature
|
||||
(`CSphere const* arg4`) is inconsistent with its body indexing parts —
|
||||
a known BN artifact class. Your MANDATORY pseudocode step (below)
|
||||
disentangles the two overloads BEFORE any C# is written; if your reading
|
||||
contradicts the #335 entry's step list, STOP and report.
|
||||
- `Plane::intersect_box` @0x005aa170 — line 439037. Note its result contract
|
||||
(the caller accepts `== 3 || == side`).
|
||||
- `BSPNODE::box_intersects_cell_bsp` @0x0053c880 — line 325993. This is the
|
||||
box-vs-cell traversal you must port TWICE (graph + flat, below).
|
||||
|
||||
## Deliverables, in order
|
||||
|
||||
**D0 — pseudocode doc FIRST** (`docs/research/2026-08-07-ap159-pseudocode.md`):
|
||||
the part-array overload and the box-vs-BSP traversal, translated to readable
|
||||
pseudocode with the addresses inline, per the repo's mandatory workflow. The
|
||||
overload-signature confusion above must be resolved here. No C# before this
|
||||
file exists.
|
||||
|
||||
**D1 — the box primitives, in BOTH representations + referee.**
|
||||
`BSPQuery` (graph) and `FlatBspQuery` (production flat) each gain the
|
||||
box-vs-cell-BSP traversal (`BoxIntersectsCellBsp`), and `Plane`-vs-box gains
|
||||
`intersect_box` semantics wherever the shared math lives. House rule from
|
||||
Slices I4/I5: a NEW traversal in two representations ships with an **exact
|
||||
differential referee test** — same inputs through both, assert identical
|
||||
verdicts over (a) synthetic cells covering each BSP node type and (b) a sweep
|
||||
of installed-DAT EnvCells (follow `FlatBspQueryDifferentialTests` patterns).
|
||||
|
||||
**D2 — rewire the indoor arm.** `BuildShadowCellSetFromParts`'s indoor arm
|
||||
follows retail's order exactly: per portal x per part sphere cheap-reject ->
|
||||
box admit -> other-cell resolution with `do_not_load_cells` -> destination
|
||||
`box_intersects_cell` gate -> leads-outside flag -> `AddAllOutsideCellsFromParts`
|
||||
after the portal loop. The box data ALREADY EXISTS: `ShadowPartBox`
|
||||
(`worldParts`) is passed in today and used only by the outdoor rectangle. Do
|
||||
NOT touch the outdoor arm, the AD-49 seed-time rectangle, or the static
|
||||
prune. The outdoor **building bridge** (`CheckBuildingTransit`, retail
|
||||
`check_building_transit` @0x0052c5d0) has the same sphere-as-admit defect:
|
||||
port it ONLY if it reuses D1's primitive with a small diff; otherwise leave
|
||||
it and report it as the explicit remainder.
|
||||
|
||||
**D3 — conformance + direction tests.**
|
||||
1. A synthetic two-EnvCell fixture where a part's SPHERE overlaps the portal
|
||||
plane but its BOX does not: pre-fix the neighbour is admitted, post-fix it
|
||||
is not. Sabotage-verify: re-widen the admit to the sphere test and the
|
||||
test must redden.
|
||||
2. The inverse guard: a part whose box DOES cross admits — unchanged.
|
||||
3. Direction assertion over an installed-DAT sweep: the post-fix indoor
|
||||
membership set for every swept object is a SUBSET of the pre-fix set
|
||||
(over-inclusion strictly shrinks; any ADDED cell is a defect). Report
|
||||
counts: objects swept, cells removed, cells added (must be 0).
|
||||
|
||||
**D4 — measurement in the report:** from D3.3, the installed-DAT population
|
||||
whose membership actually changed, with three worst examples (object,
|
||||
cells before/after). If the population is ZERO, say so plainly — that is a
|
||||
valid outcome and the register row then records "ported, no installed data
|
||||
affected".
|
||||
|
||||
## Scope — OUT
|
||||
|
||||
- The AP-156 SCALE residual (retail doesn't scale flood spheres; we do) —
|
||||
reserved for the user's explicit decision. Do not change scaling anywhere.
|
||||
- The outdoor arm, `AddAllOutsideCellsFromParts`, the 3x3 sphere path,
|
||||
anything in `ShadowObjectRegistry`.
|
||||
- Register/ISSUES edits — the session lead does those.
|
||||
|
||||
## Acceptance
|
||||
|
||||
Build green; FULL suite `dotnet test AcDream.slnx -c Release -m:1` green with
|
||||
exact totals reported; D3.1 sabotage performed and reported verbatim; nothing
|
||||
committed. Work ONLY in `C:\Users\erikn\source\repos\acdream` with absolute
|
||||
paths; do NOT spawn subagents. If any oracle reading contradicts the #335
|
||||
entry or this contract, STOP and report the contradiction.
|
||||
Loading…
Add table
Add a link
Reference in a new issue