docs: file the uphill-resolve blockage (#331) and the headless remote-DR gap (#332)

Two findings from the AD-10 work that are out of its scope and are filed
rather than absorbed. Neither is caused by AD-10 and neither is affected by
its deletion.

#331 — PhysicsEngine.ResolveWithTransition returns ok=False and the
unchanged input position for EVERY uphill step on the synthetic
constant-gradient terrain ramp, while the identical downhill step succeeds
and produces a correct slope-following result. Probed and ruled out:
gradient (fails at 2.9 degrees as at 31), step size, cell boundaries
(five start positions with the cell id recomputed), and Z seating. It is
not an axis bug either — inverting the ramp so it rises along +Y makes +Y
the failing direction, so the failure tracks the slope.

Filed with severity UNKNOWN on purpose. Players demonstrably walk uphill
in acdream and the local player runs the same call, so either production
terrain differs from what the fixture publishes (AddLandblock only, no
flat-collision statics) or something in the live arguments does. That was
not traced, and guessing which would be exactly the kind of inference this
campaign keeps getting burned by. The issue names the one probe that
decides it.

It surfaced because an uphill counterpart to the AD-10 tracking test was
written, PASSED, and was then found vacuous — the body never moved, so it
"stayed on the surface" by standing still. That test was dropped rather
than shipped. Any future uphill assertion against that harness is vacuous
the same way until this is resolved, which is reason enough to record it
even if production is fine.

#332 — Headless bots appear to have no remote dead-reckoning at all.
RuntimeRemotePhysicsUpdater has exactly one production instantiation,
AcDream.App/Physics/RemotePhysicsUpdater.cs:46, and src/AcDream.Headless/
never names it or RemoteMotion. Remote entities on that host would move
only at UpdatePosition cadence. Filed as an observation for the headless
owner to judge, adjacent to #330 but a separate mechanism.

#332 also records the reasoning trap it exposes, because it inverts the
C5b lesson rather than repeating it: RemoteMotionCombiner is in Core and
RuntimeRemotePhysicsUpdater is in Runtime, so "therefore headless runs it"
is the natural correction to C5b's graphical-only survey — and it is
wrong. Assembly placement is not reachability; the instantiation census
is. AD-10 designed no headless gate for exactly this reason, and a passing
one would have been vacuous evidence.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-06 09:37:47 +02:00
parent fb454b748c
commit 2223ed1745

View file

@ -24,6 +24,131 @@ What does NOT go here:
- Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending. - Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending.
- Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed. - Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed.
## #332 — Headless bots appear to have no remote dead-reckoning at all
**Status:** OPEN (observation, not yet established as a defect)
**Severity:** for the headless owner to judge — it depends entirely on what
headless bots are for.
**Filed:** 2026-08-06, from the AD-10 blast-radius census.
**Adjacent to #330** (headless registers no live-entity collision), but a
separate mechanism.
### The evidence
`RuntimeRemotePhysicsUpdater` — the per-tick owner that advances every remote
entity between server `UpdatePosition` bursts (interpolation catch-up, root
motion, gravity, the collision sweep) — has exactly ONE production
instantiation:
```
$ grep -rn "new RuntimeRemotePhysicsUpdater" --include=*.cs src/ tests/
src/AcDream.App/Physics/RemotePhysicsUpdater.cs:46 <- only production site
tests/AcDream.Runtime.Tests/Physics/... <- fixtures only
```
`src/AcDream.Headless/` contains no reference to `RemoteMotion`,
`RemotePhysicsUpdater`, or `OrdinaryPhysicsUpdater`. The class is `internal` to
`AcDream.Runtime` and reaches production only through `InternalsVisibleTo` into
`AcDream.App`. So on the headless host remote entities would move only at
`UpdatePosition` cadence — roughly 5 Hz teleport-stride — with no interpolation
between bursts.
### Why it is filed rather than fixed
Whether this matters depends on what headless bots need to observe. A bot that
only reads positions from the wire may not care; one that makes decisions from
observed remote motion, or that is used as a second client in a two-client
visual gate, would.
### The reasoning trap it exposes, worth recording on its own
`RemoteMotionCombiner` is in `AcDream.Core` and `RuntimeRemotePhysicsUpdater` is
in `AcDream.Runtime`, so "therefore headless runs it too" is the available
inference — and it is false. This is the C5b lesson running in the opposite
direction: C5b's survey missed `AcDream.Headless` by only walking the graphical
host's call graph, and the natural correction ("check Core and Runtime, those
are shared") produces the wrong answer here. **Assembly placement is not
reachability; the instantiation census is.** AD-10's closeout deliberately
designed NO headless gate for that reason — a passing headless run would have
been vacuous evidence.
---
## #331`ResolveWithTransition` refuses ALL uphill motion on a constant-gradient terrain ramp (fixture-or-production unresolved)
**Status:** OPEN
**Severity:** UNKNOWN until the fixture-versus-production question below is
settled. If it reproduces on DAT terrain it is severe and affects the local
player as much as remotes; if it is confined to the synthetic fixture it is a
test-infrastructure defect that silently voids any uphill assertion written
against that fixture — which is how it was found.
**Filed:** 2026-08-06, while measuring AD-10 (commits `fe6ee877`, `886333a2`).
**Not caused by AD-10, and unaffected by its deletion** — the behaviour is
identical with the pre-sweep slope projection enabled and disabled.
### What was measured
Against `tests/AcDream.Runtime.Tests/Physics/RemoteRampHarness.cs` — a single
synthetic landblock whose terrain is one constant-gradient plane, published via
`PhysicsEngine.AddLandblock` with empty `CellSurface[]` and `PortalPlane[]`
`PhysicsEngine.ResolveWithTransition` returns `ok=False` and the UNCHANGED input
position for every uphill step, while the identical downhill step returns
`ok=True` with a correct slope-following result.
```
from=<96, 96.11029, 57.53382> to=<96, 96.01029, 57.53382> (uphill)
=> pos=<96, 96.11029, 57.53382> ok=False onWalkable=False
from=<96, 96.11029, 57.53382> to=<96, 96.21029, 57.53382> (downhill)
=> pos=<96, 96.18382, 57.489704> ok=True onWalkable=True
```
The downhill answer is right in detail: the XY advance is 0.0735 m for a 0.1 m
request, exactly the `cos^2(theta)` shortening `Transition.AdjustOffset`'s
away-plane arm produces at this gradient (see AD-65). So terrain IS being seen
and the sweep IS projecting; only the uphill direction fails.
Ruled out by probe, all with `sphereRadius: 0.48`, `stepUpHeight: 0.4`,
`body:` supplied and a valid walkable contact plane at tick start:
- **Not gradient-dependent.** Fails at 0.05 (2.9 degrees) exactly as at 0.6
(31 degrees). A 2.9-degree slope is ordinary terrain.
- **Not step-size-dependent.** Fails for a 0.1 m and a 0.5 m request.
- **Not a cell-boundary artifact.** Fails from (96,96) — which is exactly on
both cell boundaries — and equally from (100,100), (100,96), (96,100) and
(110,110) with the cell id recomputed for each.
- **Not a Z-seating artifact.** Fails with the body lifted 0.05 m off the
surface as well as seated on it.
- **Not an axis bug — it follows UPHILL.** With the ramp inverted so it rises
along +Y instead of descending, it is +Y that returns `ok=False` and -Y that
succeeds. The failing direction tracks the slope, not the coordinate.
### Why this is not obviously a production defect
Players demonstrably walk uphill in acdream, and the local player runs the same
`ResolveWithTransition`. So either production terrain differs from what this
fixture publishes in some way that matters, or something in the live call
arguments does. The fixture publishes terrain through `AddLandblock` only and
registers no flat-collision statics; production goes through
`LandblockPhysicsContentBuilder.PublishStaticCollision`. That difference was
NOT traced end to end and is the first thing to check.
### How it was found, and why it matters regardless
An uphill counterpart to the AD-10 surface-tracking test was written and
PASSED — vacuously. The body never moved, so it "stayed on the surface" by
standing still. The test was dropped rather than shipped. Any future assertion
about uphill movement written against this harness will be vacuous in the same
way until this is resolved, which is reason enough to fix or document it even
if production turns out to be fine.
### Next step
Re-run the same two-position probe against a real DAT-published landblock (the
bake/pak path, or a live capture with `ACDREAM_PROBE_RESOLVE=1` while walking
uphill) and compare `ok`. That single comparison decides severity.
---
## #330 — The headless host registers no live-entity collision at all: a bot walks through every NPC and every server-spawned object ## #330 — The headless host registers no live-entity collision at all: a bot walks through every NPC and every server-spawned object
**Status:** OPEN **Status:** OPEN