3321 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9671af0273 |
fix(physics): S2 — static publication emits authored Spheres as Spheres (AP-155 narrowed)
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Both static sites (LandblockPhysicsPublisher, the headless-only LandblockPhysicsContentBuilder) emitted an authored Setup Sphere as a base-anchored Cylinder of radius r and height 2r. The live path emits a Sphere for the same data, so the same object collided differently by arrival route, and the narrow phase met a flat cap where retail meets a curved surface. Both sites now mirror ShadowShapeBuilder.FromSetup's Sphere block exactly. Combined Opus review: PASS. Its numeric verification of the dispatch test's geometry (head-sphere clearance 0.201 m for the true sphere; the cylinder counterfactual inside by 0.10 m XY with the Z band overlapping) is what makes the discrimination claim more than a sabotage anecdote, and its F8 finding is applied: the test now carries a POSITIVE control — aiming straight through the boulder's centre must block — so a membership/seed regression can no longer masquerade as a curve-hit pass. F4 applied: CylHeight is asserted, not inferred (the C4 lesson). F3 applied: the deleted Quaternion.Inverse base composition is recorded as internally coherent for the old cylinder's world-Z axis — the defect was the shape TYPE, not that rotation math. The review also verified the deleted-cylinder blast radius: the F2 overlay's drawn span is IDENTICAL for both shapes (old [c-r, c+r], new [c-r, c+r]); the flood sphere's centre rises by exactly r, which cannot change outdoor membership (XY rectangle) and lands the indoor half on Session B's dungeon gate alongside S1B; and the sphere-branch flood is now pinned uncapped by a genuine eleventh-shape A/B test. PublishStaticCollision — the headless static path — gains its first test ever. AP-155 is NARROWED, not deleted (review F13): the has-BSP source split (entity.MeshRefs vs setup.Parts + AnimPartChanged) survives and keeps the row active. The shared-primitive-emitter refactor that would make route independence a compile-time property is the filed follow-up (review F20). Population: 3,506 of 5,935 installed Setups, structurally equal to AP-157's third-branch count (byte-identical classifier — three independent routes agree: 3,605 - 99 = 3,506). Clean-room suite at implementation: 11,253 passed / 6 skipped / 0 failed on landed S1B. Post-review-hardening: Publisher tests 25/25, Content tests 2/2, both green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b3e43d22c9 |
fix(physics): S1B — indoor cell membership admits on the part BOX, as retail does (#335, AP-159 narrowed)
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
CellTransit.FindTransitCellsBox ports CEnvCell::find_transit_cells'
part-array overload @0x0052cae0 line-for-line: per-portal x per-part
order, the sphere cheap-reject at F_EPSILON+radius, the box admit whose
"Straddle or crossing-side" rule is exactly retail's `eax != side` under
the PDB Sidedness enum, leads-outside placed AFTER the admit, the
unconditional unloaded-neighbour hint without the sphere overload's
re-test, the destination box_intersects_cell gate with its deliberate
no-break, and add_all_outside_cells after the loop. The box-vs-cell BSP
traversal lands in BOTH representations behind the flat-authoritative
dispatcher with a graph referee whose 20,000 installed comparisons are
pinned by assertion (review F5), zero mismatch.
Dual Opus review: PASS on both lenses. The mandatory D0 pseudocode pass
caught that the contract's own supplementary note misattributed the box
block to the sphere overload — it belongs to a SECOND
check_building_transit overload @0x0052c680, whose portal-side
convention is INVERTED and whose admit differs; the pseudocode doc now
records that trap plus two byte confirmations made at review:
which_side @0x00444720 is strictly > eps for POSITIVE, and
intersect_box's in-plane early exit returns CROSSING(3)
(jp @0x005aa1bc -> mov eax,3), settling review items b1/b2 for the
future bridge porter. The bridge itself stays unported as AP-159's
explicit remainder.
The review also retired #335's severity premise honestly: "over-
inclusive only, never a missed one" is wrong at production shape ratios,
where the box (whole-vertex AABB) legitimately exceeds the sphere
(physics-polygon root sphere). Measured, both populations: rigged
(box << sphere) — 1,520 placements, 978 cells removed, 0 added;
production-ratio (box >= sphere) — 950 placements, 20 removed, 1 ADDED
through the loaded-neighbour gate, which is retail's direction, not a
defect. The no-op guard (review F4) asserts removal is nonzero so an
unwired admit cannot pass silently.
Process note: the implementer authored against this session's worktree
at
|
||
|
|
1b2580be4c |
docs: S6 contract — PerfectClip containment, with the rows' premise corrected at scoping
AP-83/AP-91 claim no current mover sets PerfectClip. False: the camera probe sets it, faithfully to retail's camera flags. The containment proof therefore has a real question — whether the viewer exemptions or the camera sweep's shape cut every chain to the ACE-derived TOI tails — and an honest fallback if they do not: the rows get rewritten with the camera named as the live population rather than 'unreachable'. Guard either way, so a future flag change cannot silently start executing math nobody re-verified. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e1beb7d31f |
docs: S4b contract — port the retail resting pair (tangent walkable rest + bare push-out + AD-69)
The suspect line is pinned at scoping: our ValidateWalkable measures the sphere's VERTICAL bottom against the plane (planted rest, perp = r*N.z) while our AdjustSphereToPlane is already a faithful tangent port — we mix the two geometries today and the planted one wins on terrain. D0 byte-pins retail's validate_walkable distance basis with an explicit STOP if it refutes the premise; D2 retests the #341 harness flip ten ways under the mechanism's stability prediction; the gate teaches the user that slightly hovering feet on steep slopes is the CORRECT retail look. Queued behind S1B and S2 — one physics slice in flight at a time. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
eec50bbd24 |
docs: #341 decision — user directs porting the retail pair (tangent rest + bare trigger, one slice)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4885629fbd |
docs: #341 — Ghidra cross-check confirms the tangent-rest solve, BN's garbled denominator restored
The divide-by-itself artifact in the BN text resolves in Ghidra to t = (dist -/+ r) / dot(N, stepDir): a ray-vs-plane interpolation to perpendicular-distance == radius. Two independent decompilers agree on the mechanism the #341 resolution rests on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6439b9d48e |
docs: #341 mechanism RESOLVED — trigger and resting geometry are one family
CPolygon::adjust_sphere_to_plane @0x00538210 solves the walkable placement for perpendicular distance == radius: retail rests the sphere TANGENT to the slope, which makes its bare-radius push-out trigger structurally inert — and explains why our planted rest (perp = r*N.z) makes our substituted trigger inert here by the same algebra. Each engine's trigger matches its own resting geometry; the live A/B's 84% fire rate is what mixing retail's trigger with our placement produces. AD-66 is therefore not a standalone row: the faithful unit is the pair (tangent placement + bare trigger), ported together or divergent together. The visible corollary of the retail pair — feet floating by r*(sec(theta)-1) on slopes, up to ~20 cm near the walkable limit — is why the decision is queued for the user's eyes rather than taken silently under the retail-first default. From capture to mechanism in one morning: the user's two-minute slope run plus one decomp read did what the overnight harness could not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ca0fad1482 |
docs: #341 live A/B — the user's slope run shows the bare-radius trigger would fire on 84% of grounded slope ticks
3,870 player resolves captured at Rithwic with ACDREAM_CAPTURE_RESOLVE. Of 2,955 contact-seeded ticks the RETAINED trigger fired zero times — the current push-out is inert in ordinary play — while retail's bare trigger would have fired on 2,471 (84%), lifts 2-88 mm, p50 27 mm. The body rests at r*N.z every grounded tick, so landing AD-66 alone would engage the push on virtually every slope step and fight the foot planting: the oscillation the original substitution was written against. The exposed question sits upstream: our placement plants the sphere vertically (perpendicular r*N.z); if retail's walkable contact rests tangent (perpendicular r), retail's bare trigger is inert in retail exactly as ours is here, and the trigger cannot be ported without the placement geometry. That retail-side verification is now the apparatus session's first task, and it also gives the harness flip a mechanical suspect: settle-state order dependence, not physics. Measured from the user's own two-minute run rather than argued — the cheapest decisive instrument of the campaign so far. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
addb5657c3 |
docs: C5c's owed connected-gate batch ran and user-passed — placement campaign fully closed
The six-part sitting ran 2026-08-07 on the post-S4 binary: nine-stop tour, portal repetition, the cancelled-teleport vanish-and-return, equipped-item teleport, two-client observation, and the fresh-process logout/relaunch cycle. Log evidence: 19 reveal generations, 17 materializations, one cancel correctly superseded by its immediate replacement, zero hangs, zero wait-cues. The graceful-logout path cleared the ACE session instantly and login returned to the exact last location. Recorded honestly rather than roundly: the placement probe families were not armed during the sitting, so route-7's thin cause=propagate evidence was not thickened and 4b-3's cause=cellless case remains unexercised with an unestablished trigger. Both close as user-passed with thin log evidence; the symptom-side gates are the regression net. The probe-family strip is unblocked and queued behind the in-flight Campaign S slice for build-slot reasons. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6b490aab8b |
docs: morning gate G1 user-passed — AD-65 downhill slope feel accepted
'Slopes feels good' at the downhill/diagonal/jump-landing gate, on a launch whose capture self-verified the binary identity and the authored step heights. The overnight program's one owed visual gate is closed; Campaign S continues at S1B. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d73125d3b0 |
fix(physics): S4/AD-65 — the away-from-plane response snaps to the surface, as retail does
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Campaign S slice S4, the half that landed. Retail's CTransition:: adjust_offset @0x0050a370 branches on dot(offset, contactPlane.N) at 0x0050a4fa: moving INTO the plane subtracts the normal component (0x0050a529), moving AWAY calls Plane::snap_to_plane @0x00509c50 — which preserves X and Y and re-solves ONLY Z so the offset lies in the plane (the d terms cancel algebraically), no-op under the 0.000199999995f |N.z| epsilon. acdream ran the orthogonal projection in BOTH directions, shrinking downhill XY travel by cos^2(theta): 25% at 30 degrees, 50% at 45 — AD-65's recorded shortfall, now retired. The combined Opus review independently re-derived the algebra, the branch polarity, the epsilon's bit-identity (17b75139), and the sabotage magnitude (the re-instated projection yields X = 0.75 = cos^2 30 exactly), and verified the delta is 4 non-comment lines with the into-plane arm, the crease arm, and both no-plane arms untouched. Its blast-radius sweep found the away arm exercised but NOT discriminated by any pre-existing test — every one asserts lower bounds the snap over-satisfies — so the two new exact-value tests are the only discriminating coverage, recorded in the test's class doc, and the felt 33-100% downhill speed-up is the morning gate's one row. AD-66 (the push-out's bare radius) is WITHHELD: byte-confirmed twice, implemented, then pulled after the same clean-room binaries measured contradictory absorbed-tick outcomes flipping with nothing but test assert shape — issue #341 carries the observation matrix and the apparatus plan; its two exact-value tests are [Skip]-ed; the retained substitution's rationale is restored at the site per review F1, with the review's remaining findings (F2/F3/F4/F5/F6) applied and F8 filed as #342. AD-69 filed: the same block omits retail's get_block_offset seam-frame correction, deferred to the AD-66 relanding for attributability. #340 filed: a fifth load-sensitive flake. Review verdict: PASS. AD-65 is provably unable to reach the #341 anomaly's code path (the absorb scenario takes the crease arm). Clean-room suite: 11,239 passed / 6 skipped / 0 failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4721838916 |
docs: cancel Campaign S slice S3 — planned on a misreading of AP-84
The slice text claimed a door's collision stays where the shut door was. AP-84's own row refutes the scenario: an open door is ETHEREAL (#150) and bypasses collision entirely, so a door only ever collides in its registered default pose — the approximation is behaviourally equivalent for the entire known BSP-part weenie population, and the row's risk column already carries the revisit trigger for animated non-ethereal BSP movers if they ever appear. The campaign's governing lesson says register rows are leads that must be re-measured; the dual of that is that a CORRECT row must not be 'fixed'. The plan itself committed the reading failure this time. No fix, no gate, no door row in the morning sitting. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
55b07f6a62 |
refactor(physics): hoist the live-entity collision builder to Runtime (#330 groundwork)
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
LiveEntityCollisionBuilder and LiveEntityDefaultPoseResolver move from AcDream.App.Physics to AcDream.Runtime.Physics with no behaviour change — diff-verified byte-identical shape math by both review lenses. The Build signature's App-record parameter is replaced by presentation-free primitives with identical guard semantics, INCLUDING the FinalPhysicsState read the contract had missed and the implementer surfaced rather than dropped. Visibility stays internal: Runtime's existing InternalsVisibleTo grants already cover every consumer, so the implementation's public widening is reverted per the architecture review's finding 11. The registration WIRING is deliberately WITHHELD. Both Opus lenses failed it, converging: a shadow registered at spawn freezes there (RuntimeRemotePhysicsUpdater is Runtime-homed but App-driven — nothing headless ticks it), so a walking NPC becomes a phantom obstacle at its spawn point while the real NPC still passes through the bot; three of five shadow-lifetime edges leaked (pickup leaves a permanent invisible collider, supersession orphans a duplicate, generation reset never unregisters and the K-ledger convergence oracle only checks retained shadows AFTER disposal clears them); and headless cannot resolve BSP collision assets at all, so doors and chests would still be walk-through. The frozen-shadow root was the SESSION LEAD's contract error (fact 3), not the implementer's. #330 stays OPEN, rewritten as the seven-point scope map the reviews produced — the honest overnight deliverable is that map, not a half-mechanism carrying new divergences. Suite 11,235 passed / 4 skipped / 0 failed (the withheld seam's two tests account for the delta from the implementation run's 11,237). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5629e2cb12 |
docs: checklist — S5 closed with no gate owed; record the register-corruption find
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5a317460c5 |
docs: restore AD-56 — the same revert that zombied AD-55 had silently DELETED this row
Applying feedback_register_revert_resurrection to its own discovery:
auditing every row in a8a7d64b's register hunk found the inverse defect
in the same diff. The revert resurrected retired AD-55 AND deleted
then-active AD-56 (the plumb-fall freeze adaptation, split out of TS-4
by the commit being reverted). Nothing ever restored it, and its
condition came back to life when TS-4's Path-6 shortcut deletion
re-landed for real at Slice 2B — so for eight days a live, test-pinned,
deliberately-documented behaviour had no register row, which is 'a bug
twice over' by the register's own rule.
Restored verbatim from
|
||
|
|
a8e40cb62c |
docs: re-retire AD-55 — its retirement was resurrected by an unrelated revert
The Sledding constant has been the byte-confirmed cos(10 deg) = 0.98480775f in production since |
||
|
|
eed2128743 |
docs: morning checklist — record the four eyes-free overnight closures
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ce0bfce1cf |
docs: S2 contract — AP-155's static sphere-as-cylinder emission, both sites pinned
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> |
||
|
|
2edfc70467 |
docs: S4 contract — AdjustOffset's AD-65/AD-66, snap_to_plane pinned from the binary
snap_to_plane @0x00509c50 semantics extracted at scoping: XY preserved, Z re-solved so the offset lies in the plane, no-op under the 0.0002 |N.z| epsilon — versus our orthogonal projection, which is exactly the cos-squared downhill shortfall AD-65 recorded. Branch polarity pinned from the test ah,0x41 idiom at 0x0050a4fa: into-plane subtracts, away-from-plane snaps. AD-66's port carries a mandatory regression scenario: the original substitution was empirically motivated (uphill contact-flap), so the contract requires that exact scenario as a test and a full STOP if the faithful port genuinely reds it — re-filing the divergence as deliberate is the session lead's call, not the implementer's tune. Priority note: S4 jumps ahead of S1B in the overnight queue on felt value — 25-50% downhill XY shortfall is daily-feel, while S1B's over-inclusion is zero-felt fidelity. The campaign's membership-before-query ordering is about masking, and an over-inclusive residual masks nothing downstream. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
52aea775b9 |
test(physics): AP-157 measured — CylHeight half retired, sorting-sphere half proven collision-unreachable; AD-55 byte-decoded
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Campaign S S1A, both outcomes the measure-first rule exists for. AP-157's CylHeight half is RETIRED as a non-divergence: retail's own cylsphere overload (CObjCell::find_cell_list @0x0052b9f0) copies localtoglobal(low_pt) + radius per cylsphere, capped at 10, and never reads height — retail collapses a cylsphere to a base-point sphere exactly as acdream does. The sorting-sphere half measured REAL against retail's registration set — 1,812 of 3,343 evaluated Setups (54%) fail containment at 1 mm, worst shortfall 18.135 m — and then PROVEN collision-unreachable: for this branch the flood spheres and the collision-test geometry are the same per-part Sphere list, so every omitted cell is one the entity's test geometry cannot reach, and retail's wider sorting-sphere registrations are narrow-phase rejects on retail too. Fix deferred to the next bake-schema revision rather than performing Slice I3 surgery for zero behavioural delta. The measurement test stays in the tree as the permanent record (population cross-checked against the dispatch test's independently-committed constants: 3,506 = 3,605 - 99). AD-55 is byte-decoded and RESOLVED against our constant: the binary loads qword [0x007c6b28] = pi/18 exactly and executes FCOS — retail's Sledding flatness threshold is cos(10 deg) = 0.984808. Our 0.99999536f is cos(0.17453 DEGREES): the radian literal misread as degrees, which makes the object-friction arm unreachable on real terrain (nothing is flatter than 0.175 deg). Evidence note carries the full instruction listing and the polarity of the test ah,0x41 / jp idiom; the one-line fix + conformance test is S5, queued behind the running implementation slice for build-slot reasons. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
04b794ad7c |
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> |
||
|
|
8c97084289 |
docs: close #338 — headline refuted by full-capture statistics; AD-68 files the real residual
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
The three-site probe answered it in one run: prepare and publish carry the authored 0.600/1.500 to the publication candidate, and resolve receives exactly those values for the entire session after one early 0.400 reading. Re-reading the ORIGINAL 337-support.log with statistics instead of an eyeball: authored pair 111,248 lines, 0.400 pair 358. The filing was built on an early line of a 255k-line capture; the alleged mechanism (values never wired to the mover) does not exist. The 358 are AD-68, now registered: GetSetupMoverShape's placeholder (empty spheres -> legacy capsule, 0.4/0.4 steps) during an entity's async Setup-residency window, plus the local player's own seconds-long window between controller construction and publication-candidate adoption. Retail loads synchronously and has no such window. Left as-is deliberately: shrinking it is streaming work. The filing still paid for itself: three false doc-comment claims corrected in PlayerMovementController (retail '~0.4 m' twice, and an ApplyStepHeights writer that never existed anywhere in the tree — replaced with the real writer chain), retail's actual fallback pinned at 0.04 (CTransition::step_up @0x0050b655), and the resolve probe now prints the mover id, because the early 0.400 was most plausibly a REMOTE player — remotes also carry IsPlayer — and the guid rule (feedback_probe_identity_attribution) exists precisely to stop that misread. No production behaviour changed; nothing for the morning gate. AD section 50 -> 51. Suite 11,234 / 4 / 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1dc81710f3 |
docs: close #32 — local edge-slide user-passed; AD-67 filed for the kept cell-id write
Both halves of #32 are now closed: remote at |
||
|
|
9b9bb6515f |
docs: the #32 'fix failed' verdict is VOID — the tested binary never contained the fix
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Two checkouts, one relative launch path. Edits and builds ran in the main repo; every client launch ran from a PowerShell shell whose cwd was still the session worktree, so 'dotnet run --project src\AcDream.App\...' executed the worktree's 08-06 22:35 binary — #333 present, #32 fix, InitContactPlane and every #338 probe absent. Byte-proof both ways: 0 occurrences of the fix strings in the worktree's Core.dll, both present in the main repo's. Everything the previous entry concluded is therefore void: the byte-identical capture was the OLD code re-running (expected), the three probe silences were one fact (the instrumented binary never ran), and the 26,358-write attribution table is pre-fix baseline data of the old binary only. #32's fix returns to UNTESTED, with no evidence against it. The verification that was supposed to catch this confirmed the wrong binary: the DLL byte-check ran against the OTHER checkout's bin. So the self-report now prints typeof(PhysicsDiagnostics).Assembly.Location as its second line — binary identity becomes a recorded fact inside every capture instead of an inference from file timestamps afterwards. Memory updated with the multi-checkout rule: absolute launch paths, verify each shell's cwd before the first launch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ee4d328408 |
docs: #32's setter split is necessary but NOT sufficient — and the instrument is not trustworthy yet
The
|
||
|
|
375cc0f950 |
docs: file #339 — stuck in portal space, destination reveal never becomes ready
Captured live 2026-08-07 with the raw log attached rather than summarised. Generation 2 to cell 0x3032001C: render, composites and collision are all False at begin and still all False at cancel, so complete and world-visible never fire and the five-second wait cue sits there until the client is closed. Filed rather than chased, per user direction. Two things recorded because they will otherwise be assumed: the same destination succeeded TWICE in the previous session, so it is intermittent rather than a broken landblock; and while it is mechanically very likely unrelated to the #32 contact-plane change landed minutes earlier (different subsystem, different thread), it fired on the first run after it, so the entry says to A/B against a binary without #32 before ruling it out rather than asserting independence. Also flags, without assuming either way, that #280's D-1 was an unrecoverable portal hang with the same visible symptom — this is either that regressing or a second mechanism wearing its face. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
332045c7ad |
fix(physics): split set_contact_plane from init_contact_plane (#32 local edge-slide)
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Measured live at Rithwic 2026-08-06 with ACDREAM_DUMP_EDGE_SLIDE=1. Six branch2/steep-cliffslide events, every one reporting curN=(-0.954,0.000,0.301) lastN=(-0.954,0.000,0.301) angle=0.0000 apply=False, outcome degenerate-cross/last-known. That is decision-table row 1 of the research doc, verbatim. CTransition::cliff_slide @0x0050a6d0 takes its slide direction from cross(steep contact normal, last_known_contact_plane.N) — it needs the surface the mover was STANDING ON as the second vector. acdream's CollisionInfo.SetContactPlane latched the last-known group on every call, so by the time cliff_slide ran, last-known had already been overwritten with the steep face itself: the cross product of a vector with itself, which is zero. Degenerate direction, no slide, walk off the cliff. Retail's COLLISIONINFO::set_contact_plane @0x00509d80 is 22 bytes and writes the CONTACT group only; the last-known group has four writers, none of them that function. So the four writes are DELETED and a new InitContactPlane mirrors CTransition::init_contact_plane @0x0050e850, writing both — the start-of-transition seed, where there is no earlier surface to remember. Only check_contact's SUCCESS branch calls it. The other eleven call sites keep the narrowed setter. This is a port, not a suppression: no guard, no grace period, no flag. The user's own A/B was the discriminator: Neftet's block plateaus hold (188 branch3/precipice-slide events, all before the teleport) while Rithwic's terrain cliff fails (6 branch2 events, all after). I had predicted the opposite — that terrain would be the flat-normal case — and position plus timeline corrected me, not reasoning. NEW DISCRIMINATING TEST, because the suite had none. It was green both before and after the production change, so nothing in it defended this behaviour. Issue32LastKnownContactPlaneTests seeds a walkable plane, asserts a steep mid-transition contact leaves it intact, and asserts the resulting cross product is non-degenerate. Sabotage-verified: restore the four writes and both discriminating rows fail while the InitContactPlane control keeps passing — the pair separates 'the latch is gone' from 'nothing writes last-known at all'. Two existing tests corrected rather than deleted. PhysicsSetPositionTests.FailedCheck_MapsCollisionHandlerResultToRetailError passed BECAUSE of the latch (the file the research named); its hook now populates both groups explicitly, since it asserts report plumbing, not setter semantics. RetailEdgeResponseOrderingTests.TransitionalInsert_ DegenerateCliffSlideOk_ContinuesOuterRetry was predicted to fail and did not — it now passes for a DIFFERENT reason (last-known absent rather than clobbered, which retail also answers with OK_TS). Its comment described the deleted behaviour and is corrected to say so, and to say it does not discriminate this fix. Also repairs the #338 probe. Its first placement in PlayerMovementController printed nothing across 11,523 live log lines — the wrong one of two resolve call sites — so it moves to PhysicsEngine.ResolveWithTransition where every caller passes through, filtered to the player. The dead site is removed rather than left in place; a probe that never fires is worse than none. The flag test now precedes the interpolated string: building it eagerly cost 128 B per resolve with the probe OFF, which Slice I1's zero-allocation gate caught. Suite 11,234 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
45d7154712 |
probe(physics): ACDREAM_PROBE_STEP_HEIGHTS — three readings along #338's chain
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
The live reading of 0.400 says the controller held its default at that instant, not why. Two very different causes produce it: the Setup-derived prepare/publish path never runs for the local player, or it runs and a later writer clobbers the result. Fixing without knowing which is a coin flip. One reading at each hop — prepare (Setup value computed and scaled), publish (assigned to the controller), resolve (what the resolver is actually handed) — with a decision table on the flag mapping each pattern to its cause, including the 0.000 case that would mean a null Setup took the retail dummy path. Edge-triggered per site, so the per-tick resolve site prints once per distinct pair and cannot drown the two one-shot sites it exists to be compared against. Prepare prints the raw authored pair beside the scaled one, so a surprise separates wrong-Setup from wrong-scale without a second run. Lives in PhysicsDiagnostics per code-structure rule 5 rather than as per-call-site env reads. Zero cost when off. Suite 11,231 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d5dd0b554b |
docs: research #32's local half — the last-known contact plane is clobbered mid-transition
Report-only investigation of the local player running off cliff edges. No production or test code changed. Retail's rim slide is CTransition::cliff_slide (0x0050a6d0), whose direction is cross(steep contact normal, last_known_contact_plane.N) — it needs the surface the mover was standing on as its second vector. Disassembly of the PDB-paired binary shows COLLISIONINFO::set_contact_plane (0x00509d80, 22 bytes) writes only contact_plane_valid / contact_plane / contact_plane_is_water; the last-known group has exactly four semantic writers in retail, all outside the per-substep collision response (CTransition::init_contact_plane 0x0050e850, init_last_known_contact_plane 0x0050e8e0, the validate_transition tail 0x0050ad07, and the clears). acdream's CollisionInfo.SetContactPlane latches the last-known group on every write, at all 13 call sites. The step-down probe's own steep plane therefore overwrites the ground reference before EdgeSlideAfterStepDownFailed reads it, CliffSlide's cross product goes to zero, its degenerate OK return displaces nothing, and TransitionalInsert's retry accepts the candidate hanging over the drop. The latch dates to |
||
|
|
801ff5fd44 |
docs: #338 answered — retail DOES read the authored step height, so this is real
The gating question is closed against the decomp rather than reasoned from our source. CTransition::step_up @0x0050b610 defaults step_up_height to 0.0399999991f and substitutes object_info.step_up_height when (state & 2); step_down has the same shape at 0x0050b852 and reads the authored value unconditionally at 0x0050c232. Two things fall out. Retail's fallback is 0.04, not 0.4 — our value matches neither the fallback nor the authored 0.600/1.500. And state bit 0x2 is OnWalkable, so retail applies the authored height only while standing on walkable ground. We already port that gate faithfully in Transition.DoStepUp, including the stepDownHeight = oi.StepUpHeight assignment that reads oddly but is exactly what retail passes. The gate is not the defect; only the value fed into it is. The local player is the only affected population: its controller fields initialise to 0.4f, while remotes and live entities get Setup-derived values. The property's doc comment names PlayerModeController. ApplyStepHeights as the authoritative writer — that method does not exist anywhere in the tree; the identifier appears once, in the comment. Deliberately NOT fixed. A real writer does exist further out, and RuntimeSetPositionMoverPreparation does compute the Setup-derived value, so the plumbing is there. Whether it runs for the local player or runs and is overwritten is unproven — the probe reading 0.400 says the controller held its default, not why. Setting the field without knowing which path won would be a coin flip. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
677f9a1628 |
docs: origin is a lagging mirror, not a stale local ref — correct the correction
The previous fix said the local origin/main ref had not been written since April and was three months stale. It was not stale: a fresh git fetch origin returned it unchanged at |
||
|
|
0ce54a5c4a |
merge: #333/#334 Neftet collision — the query-site broadphase is deleted
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Closes #333, #334 and #337. The Neftet plateau defect — wedged at the
top, jumps sinking into the mesh, corpses falling through — was one
mechanism presenting as three symptoms: Transition.FindObjCollisionsInCell
measured the mover's distance to a shadow entry's part ORIGIN and compared
it against the BSP ROOT BOUNDING SPHERE's radius, two points 23.556 m apart
for that rock. Geometry deep inside the real sphere was discarded before
BSPQuery ever ran.
Deleted rather than re-centred: retail has no distance pre-filter at all,
disassembled from the PDB-paired binary rather than read from Binary Ninja.
Cell membership is retail's broad phase.
Also lands #334's outdoor extent walk (retail's find_bbox_cell_list),
retires AP-158, files #338, and opens Campaign S for the twelve remaining
collision-domain items — now the active work ahead of the M4 vendor
slices per user direction that physics and collision come first.
The ACDREAM_PROBE_SUPPORT / ACDREAM_WIRE_MESH / ACDREAM_PROBE_REACH probe
families are deliberately retained: C5c's connected gates still owe the
instrumentation they provide.
Gate: clean-room full solution suite at
|
||
|
|
bec5c69daf |
docs: correct the C5c handoff's "nothing is pushed" line — it was false
The handoff's opening said "Nothing is pushed — the branch does not exist on the remote, and there are 388+ unpushed commits ahead of origin/main." All three clauses were wrong, in the direction that would most alarm a successor into thinking the campaign could be lost. main and github/main are both |
||
|
|
a069094770 |
docs: open Campaign S — collision shape & response fidelity
Plans the twelve remaining collision-domain items. Slice boundaries come from file coupling, not from how the symptoms group: AP-157, the AP-156 residual and AP-159/#335 all edit ShadowObjectRegistry (two of them the same function), so they are ONE slice against a pinned contract per feedback_dont_parallelize_coupled_plan_slices. Ordering is membership-before-query, because we just watched a membership fix (AP-156) be made invisible by a query-site defect (AP-158) directly downstream of it. Every slice opens with a measurement that can cancel it. In this domain over the last two weeks: AP-155's recorded direction was inverted, AP-156's risk column was wrong (and that is why #334 hid inside it), AP-22 described an unreachable branch, and #331's headline claim was refuted. One row in four was materially wrong about its own population, direction, or existence. Two items are deliberately reclassified. AP-83/AP-91 are NOT fixes — the x87 PerfectClip tails do not decompile, so there is no retail text to port; the honest deliverable is proving the branch unreachable. AD-65's "live lead for #269" framing is retracted: #269 closed 2026-07-31 and AD-65's sign is opposite to that symptom. #32 and #338 are pre-work, not slices — both are blocked on a measurement far cheaper than the fix, and #32 is the outstanding half of the user's original two-bug report, so it does not sit behind six slices. Live gates batched into four sessions; #330 needs none and is the parallel track for whenever a gate is blocking. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3171f43002 |
docs: file #338 — player steps at 0.400 where Setup 0x02000001 authors 0.600/1.500
Spotted in the #337 [support] capture and deliberately left out of that fix so the fix stayed falsifiable. Filed with what is NOT established attached: whether retail reads the authored Setup field at all is the first question, and the entry says to grep named-retail before touching anything. The #337 lineage already burned two diagnoses reasoned from source. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ea83b043df |
fix(physics): delete the query-site broadphase reach filter (#333, closing #337)
Transition.FindObjCollisionsInCell discarded a shadow candidate when |currPos - obj.Position| > sphereRadius + obj.Radius + movement.Length() + 2f obj.Position is the part ORIGIN; obj.Radius is the physics-BSP ROOT BOUNDING SPHERE's radius, measured about a centre AP-156 established is frequently metres from that origin (376 of 973 installed physics-BSP parts sit further from their part origin than half their own radius, worst 20.762 m). Geometry deep inside the real bounding sphere was therefore thrown away before BSPQuery ever ran: solid near the origin, permeable in a bounded shell beyond it. For the Neftet rock 0xC8766009 / gfx=0x01004751 the two points are 23.556 m apart, which is #337 — wedged on the plateau, jumps sinking into the mesh, corpses falling through. A live capture recorded 7,225 rejections on that one owner, every single one with wouldAcceptAtCenter=True. Deleted rather than re-centred. Retail has no distance pre-filter, disassembled from the PDB-paired v11.4186 binary (CodeView GUID 9e847e2f-777c-4bd9-886c-22256bb87f32) rather than read from Binary Ninja: CObjCell::find_obj_collisions @0x0052b750 walks shadow_object_list and calls CPhysicsObj::FindObjCollisions (0x0052b78b) UNCONDITIONALLY; its only early-out is insert_type == INITIAL_PLACEMENT_INSERT (0x0052b759). CPhysicsObj::FindObjCollisions @0x0050f050 contains no float compare at all. CPartArray::FindObjCollisions @0x00518180 is a bare do/while over parts, and CPhysicsPart::find_obj_collisions @0x0050d8d0 is two null checks plus a call. Retail's only spatial rejection is the BSP node bounding-sphere test inside the walk — correctly centred, which is exactly what the deleted filter was not. Re-centring it (carry BoundsCenter on ShadowEntry) would have preserved an invention retail does not have, including a +2f slack and a movement.Length() term with no retail counterpart, and left a second reach budget to be tuned forever. Retail's own cross-cell slack constant is F_EPSILON = 0.0002 m, not 2 m. The method's comment claimed the filter was "the analog of the part sorting-sphere early-outs inside retail's CPhysicsObj::FindObjCollisions — response-neutral, pure perf". Both halves were false and cost #333 and #337; it is replaced by the disassembly above. Gate: Issue333BroadphaseReachFilterTests drives the production path end-to-end (ResolveWithTransition -> FindObjCollisionsInCell -> CollisionTraversal) on a DAT-free fixture so it runs everywhere, as a discriminating pair. Sabotage-verified: restore the pre-check and OffCentreBspFloorStopsAFallingMover reaches z=37.800 — exactly the unobstructed fall, blockedAtLeastOnce=False — while CentredBspFloorStopsAFallingMover keeps passing. Without the control a fixture unable to fall would pass the first test for the wrong reason. Issue337's skipped TheBroadphaseAdmitsTheSurfaceTheMoverIsStandingOn asserted the now-deleted predicate and could never have gone green; it is rewritten as installed-DAT evidence pinning BOTH halves of the diagnosis and is no longer skipped. Perf measured, not assumed (Release, synthetic all-BSP cell, per ResolveWithTransition): at 38 candidates — the live maximum — 10.61 us -> 16.68 us (1.57x); at a deliberately unreachable 200, 17.34 -> 39.48 us (2.28x); ~0.16 us per additional candidate tested. Over 19,701 live [reach-q] samples the in-cell count is p50 = 9, p99 = 32, max 38. The ACDREAM_PROBE_REACH rejectedReach column is kept and is now structurally 0, so a post-fix capture stays comparable with the pre-fix one; dropping it would make the two incomparable. AP-158 retired (110 active AP rows). #333 and #337 closed pending the user's live acceptance at Neftet. Solution suite 11,231 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5a1eeace73 |
docs(physics): #337 diagnosed — it is #333's query-site broadphase, not the mesh
Report-only. No production code changed. The collision mesh is present, correctly shaped, correctly placed in the world, and the BSP traversal reaches every part of it. The mover never gets as far as the query. FindObjCollisionsInCell's per-object broadphase measures the mover's distance to the shadow entry's Position — the part ORIGIN — and compares it against obj.Radius, which is the physics-BSP ROOT BOUNDING SPHERE's radius. For 0xC8766009 those two points are 23.556 m apart, so a mover standing on its plateau is inside the real bounding sphere by ~20 m of margin and is still rejected. Same defect AP-156 fixed in the flood and #334 fixed in the registration extent walk, left in place at the query site. Measured, not inferred. An offline replay against the installed DAT reconstructs all eleven landblock-0x8766 owners and matches the live [geom] placement exactly (0xC8766002 at (84.699,100.082,13.000) yaw -45.00 vs the log's objPos + bspCentreOffset). At the position the client fell through, the production swept query returns a hit on poly 31 at 0.037-0.366 m while the filter rejects the candidate: distToOrigin=60.434 > maxReach=59.697, distance to the bounding-sphere CENTRE 37.083 m against a 56.909 m radius. The live capture recorded that rejection 7,225 times with the probe's own wouldAcceptAtCenter=True on every one. Bounded because the dead zone is the shell between maxReach and the true sphere, up to ~23.5 m thick on the far side. movement.Length() is a budget term: a 0.25 m walking step gives shortfall +0.60, a 0.72 m step +0.14, and ~0.86 m passes — which is exactly why jumping over the spot works, walking into it does not, and a corpse falls through. Three hypotheses refuted by measurement, not by argument: - "the rock's own mesh never collides" — true of 0xC8766002 and it is INNOCENT; its geometry is 22.8 m from the wedge and it has zero brute-force hits over a 12,493-point lattice covering the plateau. It is a candidate only because it is a 130x147 m owner. The rock actually walked on is 0xC8766009. - wrong world transform — the offline placement reproduces the runtime exactly, and a uniform displacement cannot produce a bounded pocket. - BSP traversal hole — a referee ran the production walk against brute force at 7,770 on-surface probes across all eleven owners plus 137,423 lattice points. Mismatch 0 everywhere. A 0.5 m hole map also shows continuous upward-facing coverage across the whole wedge region. [geom]'s verdict=coincident was never able to decide this: LogGeometry compares the physics box against the visual box in the object's OWN LOCAL FRAME, so it proves shape agreement and says nothing about world placement. Recorded in the doc so the next reader does not re-trust it. Retail has no per-object distance filter on the BSP branch. Verified instruction-by-instruction with cdb against the PDB-paired v11.4186 binary: CPartArray::FindObjCollisions @0x00518180 is 14 instructions of bare do/while over parts[i]; CPhysicsPart::find_obj_collisions @0x0050d8d0 is 17 instructions of two null checks plus the call to CGfxObj::find_obj_collisions @0x00534700. No compare, no float math in either. The in-tree comment calling the filter a retail analog and response-neutral is wrong on both counts. The support=object cpNz=1.0000 readings inside the rock are not the rock: ValidateTransition:6076 is retail's stationary-fall failsafe manufacturing a flat plane through the sphere bottom, and :5997 is the LastKnownContactPlane restore holding a stale plane. Both are retail-correct responses to a stuck body, and they are why the client believes it is standing while ACE rejects the position. Preferred fix is to delete the pre-check for BSP entries and correct the comment; fallback is to measure to the bounding-sphere centre, which also needs BoundsCenter carried on ShadowEntry. Neither is landed. The reproducer was confirmed to FAIL when un-skipped, with the numbers above — this campaign has caught eleven green tests covering nothing, so a fixture that cannot distinguish the bug is worse than none. Gates: bin/obj deleted, Release build 0 errors, Core suite 4,287 passed / 2 skipped / 0 failed (baseline 4,286/1 plus three new dumps and the one deliberately skipped reproducer). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
49a7e90652 |
probe(physics): ACDREAM_PROBE_SUPPORT + ACDREAM_WIRE_MESH — separate #337's three candidates
The user is wedged at the top of Neftet rock plateaus, jumps sink into the mesh, and a corpse falls straight through. ACDREAM_PROBE_REACH already ruled out its own domain: blocked=0, every candidate tested-ok. Three candidates remain — terrain support, a collision mesh not where its visual is, or the transition wedging on an unobstructed path. ACDREAM_PROBE_RESOLVE alone cannot separate them. It prints a three-value contact-plane token, no plane normal, no plane height, no terrain sample and no plane provenance, so all three produce the same line. Two additions: [support] — one line per resolve for EVERY body, not just the player. A corpse is a plain physics body with no player-specific logic, so its fall-through is the cheapest available control on "movement code vs geometry data", and it is invisible to any player-filtered probe. The line samples the outdoor terrain INDEPENDENTLY at the body's own out-XY and prints the contact plane's own height at that same XY. Two heights at one point make support=terrain / object / none a measurement rather than an inference, and cpSrc= names the site that asserted the plane so provenance and classification cross-check. [geom] — once per GfxObj that comes near a mover: the object's physics-BSP vertex cloud against its visual mesh AABB in the same local frame, through the same prepared accessors the resolver queries. verdict=coincident REFUTES the working hypothesis for that object outright; no-physics-bsp / empty-physics-bsp / displaced / extent-mismatch each name a specific data defect. Built to refute, not to confirm — two diagnoses on this defect's lineage have already been refuted by measurement. ACDREAM_WIRE_MESH upgrades the existing F2 overlay, which drew a broadphase proxy cylinder for BSP objects and so could not answer the question at all, to the real physics-BSP polygon edges (cyan) beside the visual mesh box (magenta) and the terrain surface (yellow). Own class per code-structure rule 1. The provenance latch lives on PhysicsDiagnostics, not on CollisionInfo. Two fields there first — the obvious home — broke the flat/graph differential referee and the scratch-reset poison test, both of which compare CollisionInfo member-for-member. Teaching either to skip a member is a one-line green fix that puts a permanent hole in a referee whose whole job is comparing everything. Captured as feedback_probe_state_off_compared_types. Seven tests cover the support classifier's boundaries: a wrong classifier does not fail to answer, it answers confidently wrong. Gates: Release build 0 errors; complete suite 11,225 passed / 4 skipped / 0 failed from a cleaned tree — baseline 11,218/4/0 plus exactly the seven new tests, skips unchanged. Issue #337 filed with the symptom set, what is ruled out, and a table of what each possible output means. All of this is TEMPORARY and recorded for stripping with the physics-probe family. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
13fcf38138 |
fix(physics): port retail's find_bbox_cell_list outdoor extent walk (#334)
acdream had never implemented retail's SECOND cell-membership algorithm.
CPhysicsObj::calc_cross_cells @0x00515230 tests HAS_PHYSICS_BSP_PS at
0x00515285 and jumps (0x0051528f jne 0x515305) to find_bbox_cell_list
@0x00510fc0 for a BSP-bearing object; everything below that jump is the
OTHER algorithm, CObjCell::find_cell_list, and that is all we had. Every
object, BSP-bearing or not, was routed through it.
That path's outdoor expansion is a HARD CAP of one cell in each direction.
CellTransit.AddAllOutsideCells computes minRad = radius, maxRad = 24 - radius
and adds at most the eight neighbours of the sphere's own cell, so for any
radius >= 12 m both boundary tests are unconditionally true and the result is
exactly 3x3. Widening the radius or adding a second sphere is mechanically
incapable of adding a tenth cell. The user's live probe measured the
consequence directly: standing inside a Neftet formation, inCell=2 exempt=2
reached=0 -- the geometry was not a candidate at all.
The port. AddAllOutsideCellsFromParts is CLandCell::add_all_outside_cells
@0x00533360 plus add_cell_block @0x005331d0: base landcell from the FIRST
part's own adjust_to_outside, baseX/baseY within-block, each part's authored
CGfxObj::gfx_bound_box re-fit through all eight corners
(BBox::LocalToGlobal @0x005b2120), floor(v / square_length) where
square_length = 0x7c920c = 24.0f, four accumulators seeded to zero, ONE
rectangle unioned across all parts, FILLED, in GLOBAL lcoords so it crosses
landblocks freely, clamped only to [0, 0x7f8).
BuildShadowCellSetFromParts is find_bbox_cell_list's worklist.
RegisterMultiPart dispatches on the same flag retail does, and
BuildFloodSpheres' BSP arm is deleted rather than left unreachable.
Disassembled from the PDB-paired 2013-09-06 binary, not read from Binary
Ninja: BN mis-renders four separate constructs inside add_all_outside_cells
alone -- a dropped `and eax,0xffff` on baseX, a neg/sbb/and select shown as
identically zero, a wrong get_landcell argument, and both x87 flag tests as
`unimplemented {test ah}`.
ShadowPartGeometry pairs the BSP root sphere with the authored box so no
resolver can answer one and leave the other call site to synthesize a
substitute -- the AP-156 invariant applied a second time, since that split is
what produced AP-156 and then this. The box comes from
FlatGfxObjVisualBounds, already computed by exactly CGfxObj::init_end's
algorithm and already in the prepared package: no bake change, no DAT re-read.
Cost, measured over the installed DATs before any code was written: 1,258
physics-BSP GfxObjs, cells/object p50 4, p90 4, p99 12, max 49. The port is
CHEAPER than the old 3x3 = 9 for 98.97% of them. Row totals (shapes x cells)
over all 1,031 landblocks with BSP owners fall 97,173 -> 15,607 (0.161x);
dense Arwic 0xC6A9 falls 342 -> 43. One landblock more than doubles.
Precondition confirmed before pinning any expected cell set: 0x010046D8's box
is 96 m x 96 m about cell (2,2) = 0x87640013, which independently corroborates
the 3x3-centred-there diagnosis, and its rectangle does contain 0x87640011 and
0x87640019 -- the two cells the probe measured empty.
Register: AP-156's outdoor half CLOSED and its risk column CORRECTED (it read
"extra broadphase candidates, never a missed one", which generalised the indoor
direction to the whole row and is why #334 sat inside it unnoticed). AP-159 +
issue #335 file the unported indoor arm; AD-49 records the seed-time rectangle.
Issue #336 files a fourth load-sensitive test flake seen once during the gate.
Ten tests, every one sabotage-verified in both directions across eight
mutations (dispatch, 8-corner refit, floor-vs-truncation, union-vs-per-part,
map clamp, adjust guard, landblock clamp, box-path-for-everything). The
strongest is an installed-DAT replay of the user's own probe evidence.
Suite 11,208 -> 11,218 passed / 4 skipped / 0 failed; the +10 is exactly the
new tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
f0588725cf |
docs: #334 measured in game — a landblock-spanning object is registered by one sphere
The reach-filter theory I proposed is REFUTED by measurement, and the real cause is found. Probe evidence committed as 334-neftet-probe.log (8,401 lines, ACDREAM_PROBE_REACH at |
||
|
|
b61f5fd4fc |
probe(physics): ACDREAM_PROBE_REACH — discriminate #334's three candidates
#334 is "large static formations can be walked through on flat ground, and
jumping over them drops you inside" (Neftet, user-reported, pre-existing —
reproduced on
|
||
|
|
d4e956b4e2 |
docs: file #334 — large static formations lose collision at their boundaries
User-reported in live play at Neftet: solid on approach, permeable at a
boundary between two formations, and no floor above — jumping over lands you
through.
NOT a regression from today's collision work, and that was established by
measurement rather than argument: a client was built at
|
||
|
|
e6457cc849 |
fix(physics): close the AP-156 fix review — real containment oracle, type-level invariant, AP-158
Both review lenses PASSED; this is the cleanup, not a rescue. Evidence:
docs/research/2026-08-06-ap156-review-closure.md (the review itself is
committed alongside it as the received artifact).
R1 — the load-bearing containment test could not fail. Its truth and flood
values were two hand-copies of the same expression over the same part set,
so the shortfall was algebraically identically zero for any DAT input. The
oracle is now PHYSICS-POLYGON VERTICES — a different DAT field from the
bounding sphere the builder emits, so the two sides can genuinely disagree.
Sabotage-verified three ways after full cleans: dropping the bounds centre
in production reddens it (428 Setups, worst 35.869 m on 0x0200129A, matching
an independent out-of-repo sweep exactly); dropping only the scale on the
centre reddens it (326); and corrupting the TEST's own bounds oracle reddens
it (467) where under the shipped oracle that same corruption was invisible
by algebra. Renamed accordingly. A6's stale "cap control" comment corrected:
that loop is the test's own uncapped re-implementation and cannot observe a
cap regression — the cap is covered in Core.
R2 — the population was understated. 172 is AP-152's DISPATCH population;
AP-156's is 530 BSP-bearing Setups, of which 525 have a flood sphere move
and 428 fail vertex containment before the fix (412 at a 1 cm tolerance —
the review's figure; the gap is 16 Setups between 1.4 mm and 10 mm, real
geometry). 0 fail after, at any tolerance down to zero. Corrected in the
AP-156 row, the section-3 header, the C5c handoff and two test docstrings.
Dated review artifacts are left as written — "170 of 172" was correct for
what they measured, and rewriting evidence to match a later measurement
loses provenance.
A1 — BoundsCenter = default reopened at the type what the commit closed at
the seam. Dropping the default alone would NOT have closed the review's own
scenario (a copied Cylinder call site would write Vector3.Zero explicitly
and stay green), so ShadowShape's constructor is now private and BSP shapes
are built only through ShadowShape.Bsp(..., FlatCollisionSphere localBounds),
which takes radius and centre as ONE value and scales them together. There
is no expression a caller can write that carries one and drops the other.
22 construction sites converted; the same sabotage now reddens 5 Core tests
where the review's sabotage A reached 4, because both BSP producers share
one scaling path.
A2 — #333 is real and bigger than filed, and its retail question is
answered. I disassembled CObjCell::find_obj_collisions @0x0052b750 from the
PDB-paired binary myself (check_exe_pdb.py MATCH) rather than inheriting the
claim: its only early-out is sphere_path.insert_type == INITIAL_PLACEMENT_
INSERT, then it calls FindObjCollisions on every unparented non-self shadow
object UNCONDITIONALLY. Retail has NO distance pre-filter, so acdream's
"+ movement + 2f" reach filter is an invention with no register row — filed
as AP-158, carrying the disassembly, the F_EPSILON = 0.0002 m contrast, and
the measured blast radius (118 of 477 unique installed physics-BSP GfxObjs
exceed its ~2.5 m budget, 46 exceed 5 m). Active AP rows 109 -> 110.
Recorded prominently in three places a reader will hit: TALL PROPS MAY SHOW
NO VISIBLE CHANGE UNTIL #333 LANDS, and a null result at the connected gate
is EXPECTED, not evidence against AP-156.
LOW items. R3: the comment claiming the cited evidence justified the whole
cap line is corrected, but int.MaxValue on the sorting-sphere branch stays —
capping at 1 would take Spheres[0], and retail's one sphere is
CSetup::sorting_sphere, a different DAT field; capping keeps the wrong field
AND flips the substitution under-inclusive (#98/#168 direction). AP-157
already owns it. R4: acdream scales the flood sphere where retail's
find_transit_cells never reads gfxobj_scale — added as a second residual on
AP-156. R5: retail's slack constant carried into AP-158 and #333. A3: the
per-call delegate allocation is back to a cached field, still derived from
the single bounds resolver. A5: noted; b52967de's message cannot be amended.
Gates: all 44 bin/obj deleted before every verdict-deciding build, each test
run gated on a verified "Build succeeded" in the same invocation. Release
build 0 errors / 21 pre-existing warnings. Complete suite 11,208 passed /
4 skipped / 0 failed — reconciles exactly with the
|
||
|
|
e2b2d04cb5 |
docs: file #333 — the broadphase reach filter has AP-156's defect at the query site
Found while fixing AP-156 and deliberately NOT bundled into it: the shadow broadphase at TransitionTypes.cs:3756-3764 measures `currPos - obj.Position` against `sphereRadius + obj.Radius + movement + 2f`, where `obj.Position` is the PART ORIGIN but `obj.Radius` is the BSP root bounding sphere's radius, measured about that sphere's own centre. The same discarded origin, one layer down. It admits a real contact only when the sphere's centre is within about `movement + 2` metres of the part origin. For Setup 0x02000255 that offset is 9.911 m against a budget near 2.5 m, so a mover touching the upper half of the prop is discarded before BSPQuery runs. This is newly load-bearing: before AP-156 those objects were mostly not in the cell at all, so the filter never got to reject them. AP-156 puts them in the right cells and this becomes the next gate. It is the first place to look if the connected session finds a tall prop that still does not block. Filed rather than fixed because it is a different code path with an unanswered retail question — the `+ 2f` slack and the movement term look like acdream's own broadphase rather than a port of anything in CPhysicsObj::FindObjCollisions @0x0050f050, in which case it needs a divergence row of its own before it is touched. Bundling it would also make AP-156's connected gate un-attributable, which is exactly the fault that split AP-155. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
b52967def3 |
fix(physics): AP-156 — flood the BSP sphere where the geometry is, not at the part origin
The AP-152 retail review (docs/research/2026-08-06-ap152-review-retail.md) FAILED ` |
||
|
|
4abd1b5eb7 |
fix(physics): AP-152 — dispatch collision shapes BSP-first, at emission and at the cell flood
The register row predicted "catching or stopping on a doorway sill". That
symptom could not have been occurring. `Transition.BspOnlyDispatch`
(TransitionTypes.cs:1348, landed 2026-05-25 as A6.P7) already skipped both
primitive branches (:3911, :3954) whenever the target's wire PhysicsState
carries HAS_PHYSICS_BSP_PS, and ACE sets that bit from CSetup.HasPhysicsBSP
for every affected Setup. The extra primitive was never tested for collision.
The live defect was CELL MEMBERSHIP. The same shape list feeds
`ShadowObjectRegistry.BuildFloodSpheres`, which had no such guard and
preferred Cylinders over everything whenever any Cylinder existed — retail's
SECOND priority applied ahead of its first. For the 73 CylSphere+BSP Setups
acdream therefore flooded shadow cells from the cylinder and never from the
slab: an object absent from cells it physically occupies, which is the
#98 / #168 symptom class, not the door-collision class the row named.
Retail, re-disassembled from the PDB-paired binary (v11.4186, CodeView GUID
9e847e2f-777c-4bd9-886c-22256bb87f32, check_exe_pdb.py MATCH) rather than
taken from Binary Ninja, which drops flag tests:
CPhysicsObj::FindObjCollisions @0x0050f050
0x0050f165 test dword [esi+0xa8], 0x10000
0x0050f16f je 0x50f1a2 ; clear -> primitive dispatch
0x0050f18d call 0x518180 ; CPartArray::FindObjCollisions
0x0050f19d jmp 0x50f2b0 ; UNCONDITIONAL, past BOTH primitive loops
; (CylSphere 0x50f1a2, Sphere 0x50f21d)
0x0050f1d6 jae 0x50f317 ; CylSphere loop exhausted -> RETURN
0x0050f22f je 0x50f31b ; zero Spheres -> RETURN seeded OK_TS
CPhysicsObj::calc_cross_cells @0x00515230
0x00515285 test dword [esi+0xa8], 0x10000
0x0051528f jne 0x515305 -> CPhysicsObj::find_bbox_cell_list @0x00510fc0
0x005152d1 call 0x52b9f0 ; cylsphere branch, below the jump
0x005152fb call 0x52b990 ; sorting-sphere branch, below the jump
Priority at both consumers: BSP -> CylSphere -> Sphere -> nothing. BSP wins.
Every address above was resolved back to its symbol by exact lookup in
named-retail/symbols.json.
Changes:
* `ShadowShapeBuilder.FromSetup` gains a step-0 dispatch gate. Steps 1 and 2
are skipped entirely when any part's EFFECTIVE GfxObj carries a physics
BSP. The gate and step 3 now share one `EffectivePartGfxObjId` helper, so
they cannot read different identities — a gate on `setup.Parts` would,
after an ObjDesc swap, suppress the primitives while step 3 emitted
nothing and `Build` returned null, deleting the entity's collision.
Emission order is unchanged. This also removes acdream's undeclared
reliance on the server sending the flag: the gate is derived from the
parts, exactly as CPartArray::CacheHasPhysicsBSP @0x00518110 derives it.
* `ShadowObjectRegistry.BuildFloodSpheres` now applies calc_cross_cells'
own order: BSP, else Cylinder, else everything. Given the gate above this
is a no-op for every shape list acdream produces (FromSetup is now
exclusive; both landblock-static publishers already emit homogeneous
lists), so the measured membership delta remains attributable to the
gate alone. It is kept for the same reason BspOnlyDispatch is kept: retail
genuinely dispatches here, and it guards a future additive producer.
`Transition.BspOnlyDispatch` is deliberately untouched.
Register: AP-152 RETIRED with its four false statements corrected — the risk
statement (the symptom was already inert); "small and centred at the part
origin" (max primitive is 6.714 m, and 0x0200086E's sphere origin is
(0.759, 0.165, 5.842)); the cottage door's "~14 cm base Sphere" (it is
0.100 m; 0.141 is Setup.Radius, which AP-22 proved is never collision
geometry); and naming one pinning test where two existed. AP-153/154/155
filed: retail's dispatch flag is cached once at InitPartArrayObject+0x7e
where acdream's gate is live; the query-time guard takes a client-derived
flag off the wire; and the static publishers emit Setup Spheres as
height-capped Cylinders while BuildFloodSpheres approximates retail's
bounding box with bounding spheres.
Tests. Both pinning tests corrected, neither deleted:
`FromSetup_DoorSetup_ProducesFourShapes` -> `..._EmitsBspPartsOnly`;
`FromSetup_DoorSetup_SphereAtExpectedLocalOffset` re-hosted on
`_ => false`, the DAT-real configuration for the 3,605 Sphere-only Setups.
`FromSetup_ScaleFactor_MultipliesAllRadiiAndOffsets` was the campaign's
eighth green test covering nothing — its assertions sat inside
`if (CollisionType == Cylinder)` on a fixture with zero CylSpheres, so only
`Scale == 2.0f` ever ran. Proved empirically: with the sphere radius scale
deleted, the old body passes and the corrected body fails. Three new facts:
the effective-identity gate, the App-layer CylSphere+BSP registration (no
App fixture combined the two before), and the flood-set dispatch. One new
installed-DAT sweep pins 172 affected Setups (73 CylSphere+BSP, 99
Sphere+BSP) behind external bucket controls, re-measured independently and
agreeing exactly with the filing commit's separate sweep.
All eight sabotages run and reported; every discriminating fact reddens in
the intended direction and only there. Clean Release build after deleting
every bin/obj: 0 errors. Complete suite 11,203 passed / 4 skipped / 0
failed, +5 on the 11,198 baseline at
|
||
|
|
ec29a732f5 |
test(physics): settle #331 — the uphill "refusal" is the #137 anti-parallel absorb, not a defect
#331 reported that `PhysicsEngine.ResolveWithTransition` refuses ALL uphill
motion whenever a `body:` is supplied. It does not. It refuses a step whose
sub-step offset is exactly anti-parallel to a live sliding normal — the
#137-family absorb this project already recorded as retail-faithful.
Measured on the same fixture, same gradient, same body, varying only the
heading relative to the slope gradient:
(0, -0.1, 0) cross-slope 0 -> zero movement, latched
(0.0001,-0.1, 0) cross-slope 0.0001 m -> zero movement, latched
(0.001, -0.1, 0) cross-slope 0.001 m -> climbs 0.176 m in 5 ticks
(0.01, -0.1, 0) cross-slope 0.01 m -> climbs 0.176 m in 5 ticks
The threshold is retail's own F_EPSILON small-offset abort (0.0002 m): about
0.11 degrees off the exact gradient at a 0.1 m step. `RemoteRampHarness`
builds a ramp whose gradient is exactly along Y and the original probe pushed
exactly along -Y, so it hit the measure-zero case with probability 1.
The latch itself is production-real in mechanism — a pure gravity fall under
the production RuntimeRemotePhysicsUpdater, with no fixture settle seam
involved, lands leaving Contact|OnWalkable|Sliding with slidingNormal (0,1,0)
— but every link is faithful to retail, verified in the PDB-paired binary
rather than Binary Ninja (BN typed find_transitional_position `void` and
dropped the load-bearing return value):
validate_walkable sets collision_normal from the terrain plane when
OBJECTINFO CONTACT is clear 0x0050d251 / 0x0050d261 / 0x0050d26c
validate_transition converts it unconditionally 0x0050ac19-0x0050ac30
set_sliding_normal zeroes Z AND re-normalizes 0x0050a060
SetPositionInternal persists SLIDING_TS 0x005154c2 / 0x005154e1
get_object_info re-seeds it next frame 0x00511d44 / 0x00511d4f
find_transitional_position returns
`i != 0 && state == OK` on the step-0 abort 0x0050c0ed -> 0x0050c089
ACE agrees (Transition.cs:1027, CollisionInfo.cs:58). No production code
changed; no divergence introduced, so no register row.
What lands is the coverage whose absence made this invisible — nothing in the
suite asserted that a body-bearing mover makes uphill progress on a walkable
slope, and the test that found #331 passed vacuously because the body never
moved:
RuntimeRemoteUphillProgressTests.ARemoteWithABodyClimbsAWalkableSlopeAndKeepsItsFeetOnIt
per-tick climb + surface tracking under a realistic off-gradient heading.
SAB-A1 AdjustOffset -> Vector3.Zero reddens at tick 1
SAB-A2 fixture gradient -> 0 (flat) reddens at tick 1
RuntimeRemoteUphillProgressTests.AnExactlyUpSlopeOffsetIsAbsorbedByThePersistedSlidingNormal
characterization pin for the absorb, with the retail anchors inline.
SAB-B1 delete the get_object_info sliding seed reddens (climbs to 57.7544)
SAB-A1 reddens
SAB-A2 reddens
NON-discriminating, measured and documented: making the final tick
exactly up-slope leaves it green — by then the latch is already cleared.
RemoteRampHarness gains a warning block naming the axis-alignment trap so the
next vacuous uphill assertion is caught at authoring time.
Suite re-measured from a full clean (43 bin/obj removed): 11,198 passed /
4 skipped / 0 failed, against the 11,196/4/0 baseline at
|
||
|
|
0d62a5ffeb |
docs: close the placement cutover ledger — #280 user-passed, remaining gates NOT RUN
Campaign closed by user direction after the #280 connected gate passed. GATE RESULT. #280 user-accepted: "now portal space takes longer but terrain is complete when I exit" — both halves of the specified criteria, a measurably longer hold and a complete destination on reveal. Probe evidence: three Portal reveals plus a Login reveal, every one at radius=12 where pre-fix it was a hardcoded 1, each portal hold raising the wait cue at ~5.0 s before completing. An accidental but genuine A/B came out of the same session. An earlier run set ACDREAM_PROBE_REVEAL_RADIUS=1 — that variable is a radius VALUE, not an on/off flag — which forced the pre-fix window. The user saw the original defect under it and not under radius=12. That is the before/after pair the gate asked for, obtained by mistake. Recorded prominently because the same mistake would silently reproduce the bug for the next person. WHAT IS NOT CLAIMED. The ledger closes with most connected gates outstanding BY USER DIRECTION, not because they were discharged: D-1's two reachability scenarios, AP-136's six-step park protocol, route-7 thickening (the remote-teleport probe recorded ZERO lines), the two-client observation, the nine-stop soak, and the lifecycle/reconnect route. The closeout's section 2.6 is a table of exactly this, and both the campaign plan banner and this commit say that anyone citing "the campaign passed" must cite it alongside. THE PROBE FAMILY IS DELIBERATELY NOT STRIPPED. Closing the campaign would normally retire the six ACDREAM_PROBE_* flags, but their gates were never run, and stripping now would delete precisely the instrumentation those owed gates need — the failure the handoff's own rule exists to prevent. Honouring that rule means not stripping even though the campaign is closing. ACDREAM_PROBE_REVEAL_RADIUS is also kept despite #280 closing, because AP-149 and #326 are open and would both want the same A/B harness. #280 is marked CLOSED in ISSUES with its gate evidence, and its residual AP-149 is restated there: our outer ring accepts terrain-only readiness where retail's PreFetchCells also requires each landblock's LandBlockInfo and every building's EnvCells, so distant SCENERY may still fill in after reveal even though terrain does not. Not folded in — it costs further hold time and is a game-feel call. Memory updated with the campaign's closed state and the follow-up order: #331 first, then AP-152, #330, AD-65. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
1304dafa8b |
docs: C5c closeout + successor handoff; automated gate passes 11,196/4/0
Closes the automated half of C5c. Everything still owed needs the user at a live client, and the probe strip cannot precede it. AUTOMATED GATE — PASS. Complete Release suite on the final binary at |
||
|
|
7b3e2895cd |
docs: close the AD-10 review findings — AD-65's magnitude was half the truth
Both AD-10 review lenses PASS; the deletion stands. These are the findings they raised. One production file touched, comment-only. AD-65 WAS UNDERSTATED BY HALF, and it is the finding that matters. The row states the factor as cos^2(theta) and then quantified 1-cos(theta): "13% at 30 degrees, 29% at 45". The correct figures are 25% and 50%. This is not algebra alone — #331's probe in the same push measures 0.0735 m travelled for a 0.1 m request at 30.96 degrees, i.e. 26.5% short, which is exactly cos^2(30.96). AD-65 is a LEAD for #269's slope-slide residual; at the understated magnitude it reads as marginal and could have been dismissed. At 50% short at 45 degrees it is a serious candidate. I repeated the wrong figure in conversation before the review caught it. "VERBATIM/FAITHFUL PORT" of Transition.AdjustOffset was asserted in five places and was false as of the very next commit, which filed AD-65 and AD-66 against that same function. Corrected to "structurally exact, with exactly two filed divergences" in the register row and the production doc comment. RECORDED, and it favours the change: the redundancy measurement is CONTINGENT on AD-65 — the two mechanisms agree today partly because both under-travel downhill. That makes this deletion a PREREQUISITE for fixing AD-65 rather than merely compatible with it; had the projection survived, correcting AdjustOffset would have re-introduced a disagreement between two live projections. The record claimed no such thing and should have. UNTESTED AXIS recorded: the contract's T2 — its mandatory wrong-plane-versus- right-plane discriminator — was dropped without record, breaching the contract's own clause requiring exactly that to be written down. The consequence is precise: the deletion is measured, but the change's only claimed BENEFIT (a walkable non-terrain surface now gets the committed contact plane instead of terrain far below) has zero automated coverage and rests on source reasoning. Stated in the row rather than left implied. #331 SEVERITY RAISED from UNKNOWN — the discriminator is known and it is not the fixture. With `body: null` the same uphill sweep climbs (ok=True, moved (0, -0.0999, +0.060)); with a body supplied it returns ok=False and zero movement, under a call profile identical to the local player's (IsPlayer|EdgeSlide + the human two-sphere Setup). A diagonal request keeps cross-slope X and zeroes only up-slope Y, and it fires on a 1.1 degree ramp. So "confined to the synthetic fixture" is no longer the comfortable default: the failing call shape is the shape production uses. Nothing in the suite asserts uphill progress on a walkable slope, which is why it was invisible — the test that found it passed vacuously, because the body never moved. Also: malformed XML doc on ComposeOffset (duplicate </summary> swallowed the retirement note from tooling) fixed; the placement-cutover plan's item 5 and its stale "After C5" line now record AP-22 and AD-10 as retired. Core builds clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |