Both static publication paths emit an authored Setup Sphere as a base-anchored Cylinder (r, 2r) while the live path emits a Sphere for the same data — different narrow-phase dispatch and a route-dependent collision difference for the same object. The fix mirrors FromSetup's step-3 emission at both sites; the contract's first test is the route-independence property asserted shape-for-shape, and the dispatch test picks geometry where cap-hit and curve-hit verdicts differ so the sphere verdict is observable, not inferred. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
72 lines
3.3 KiB
Markdown
72 lines
3.3 KiB
Markdown
# S2 contract (Campaign S) — AP-155: static publication must emit authored Spheres as Spheres
|
||
|
||
**Date:** 2026-08-07 (overnight). **Scoped by:** the session lead.
|
||
**Implementer:** one Sonnet agent. **Review:** dual Opus.
|
||
|
||
## The divergence (AP-155, narrowed to its surviving half)
|
||
|
||
Both static publication paths emit an authored Setup `Sphere` as a
|
||
height-capped **Cylinder**:
|
||
|
||
- `src/AcDream.App/Streaming/LandblockPhysicsPublisher.cs` (~1018–1036)
|
||
- `src/AcDream.Content/LandblockPhysicsContentBuilder.cs` (~672–690)
|
||
|
||
Identical code in both: `ShadowShape.Cylinder(radius = r*scale,
|
||
cylHeight = 2r, base = origin*scale - r*ẑ rotated)`. The LIVE path
|
||
(`ShadowShapeBuilder.FromSetup`, step 3) emits `ShadowShape.Sphere` for the
|
||
same authored data. Consequences: (a) narrow-phase dispatch differs
|
||
(CCylSphere tests vs CSphere tests — a mover meets a flat cap where retail
|
||
meets a curved surface), and (b) the SAME object collides differently
|
||
depending on whether it arrived as a landblock static or a live spawn —
|
||
the route-dependence Campaign S's plan calls out. Retail's
|
||
`CPhysicsObj::FindObjCollisions` dispatches Spheres to
|
||
`CSphere::intersects_sphere` — there is no cylinder substitution anywhere
|
||
in retail for this branch.
|
||
|
||
## The fix
|
||
|
||
Replace both emissions with `ShadowShape.Sphere`, mirroring
|
||
`FromSetup` step 3's emission EXACTLY — same origin/scale composition, same
|
||
guards (`Radius <= 0` skip stays). Read FromSetup first and copy its
|
||
semantics rather than inventing; if FromSetup's sphere emission differs from
|
||
what a plain mirror would produce here (e.g. BoundsCenter handling), STOP
|
||
and report the difference instead of choosing.
|
||
|
||
Then check the ripple: `BuildFloodSpheres`' sphere branch (uncapped) now
|
||
sees these as Spheres — flood behaviour matches the live path, which is the
|
||
point. `RetailSphereCap` applies to the Cylinder branch only (AP-156's
|
||
retirement of the 10-cap on non-cyl branches) — verify with a test, not by
|
||
reading alone.
|
||
|
||
## Tests
|
||
|
||
1. Parity: for a representative Setup with authored Spheres, the static
|
||
publication's registered shapes are shape-for-shape identical (type,
|
||
local position, radius, scale) to `FromSetup`'s output for the same
|
||
inputs. This is the route-independence property, asserted directly.
|
||
2. Dispatch: a mover resolve against a statically-published sphere object
|
||
takes the Sphere narrow phase (assert via outcome on a diagonal approach
|
||
that distinguishes cap-hit from curve-hit: pick geometry where cylinder
|
||
and sphere verdicts differ, assert the sphere verdict).
|
||
3. Sabotage: restore the Cylinder emission at ONE site; tests 1 and 2 both
|
||
redden; restore.
|
||
4. Existing static publication and content-builder suites stay green.
|
||
|
||
## Measurement (report, not gate)
|
||
|
||
Count, over installed landblocks already swept by existing content tests
|
||
(reuse their enumeration), how many static entities carry sphere-only
|
||
Setups — the affected population. Three example object ids.
|
||
|
||
## Scope — OUT
|
||
|
||
Register/ISSUES edits (session lead). `ShadowShapeBuilder`,
|
||
`ShadowObjectRegistry` internals. The live path. Anything about
|
||
SortingSphere (AP-157 is measured and deferred).
|
||
|
||
## Acceptance
|
||
|
||
Full suite `dotnet test AcDream.slnx -c Release -m:1` green, totals
|
||
reported; sabotage reported verbatim; nothing committed. Only
|
||
`C:\Users\erikn\source\repos\acdream`, absolute paths, no subagents;
|
||
contradictions → STOP and report.
|