From ce0bfce1cfe608948453ca2b49970ea7e5c12c10 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 7 Aug 2026 01:00:11 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20S2=20contract=20=E2=80=94=20AP-155's=20?= =?UTF-8?q?static=20sphere-as-cylinder=20emission,=20both=20sites=20pinned?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../2026-08-07-s2-static-sphere-contract.md | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 docs/research/2026-08-07-s2-static-sphere-contract.md diff --git a/docs/research/2026-08-07-s2-static-sphere-contract.md b/docs/research/2026-08-07-s2-static-sphere-contract.md new file mode 100644 index 00000000..5d3996be --- /dev/null +++ b/docs/research/2026-08-07-s2-static-sphere-contract.md @@ -0,0 +1,72 @@ +# 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.