736 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dd2cb92b99 |
chore(audio): Campaign A slice A6 — delete what retail does not have
Retail EoR has no music system: the linked winmm MIDI player has zero callers, 'music' appears zero times in the 65 MB decomp, SoundType has no music member, InitPrefs registers no music key, and the install ships no music files. So PlayMusic/StopMusic/MusicVolume and the AudioSettings Music knob are deleted rather than left as an API modelling dead code — the string-keyed signature was the tell, since every other entry point is DID-keyed. Old settings.json files carrying a 'music' key still load; the reader ignores unknown keys and the next save drops it. The Ambient slider is now surfaced, because slice A5 gave it something to drive, and its default returns to retail's 1.0 from an invented 0.8 — InitPrefs defaults every sound preference to unity. The panel rule is unchanged: no slider that does nothing. r05-audio-sound.md gets a SUPERSEDED banner naming its five wrong sections (falloff, pan, voice pool, selection, music, ambient) so a future reader reaches the lane notes instead of the Ghidra-era reads that this campaign spent its first two slices undoing. TS-9 re-scoped from 'any MP3 cue' to the measured blast radius: exactly 1 MP3 among 786 shipped waves, a ~2 s mono clip. Its original framing assumed a music system that does not exist. The ADPCM count remains unmeasured and is named as the open question. Deferred deliberately: #321's sound-cache decode-dedup race. It is a pre-existing concurrency flake rather than audio-parity behaviour, and shipping a speculative fix to a race I have not reproduced is exactly the shortcut this project's no-workarounds rule exists to prevent. Campaign A is code-complete; the plan carries the closeout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e42b99482e |
feat(audio): Campaign A slice A2 — retail's 2D pan+gain mixer replaces AL 3D
Retail is not a 3D audio engine. Every gameplay buffer is created with m_3D = 0 and the DirectSound 3D listener the client sets up is dead code; spatialization is two CPU scalars per voice, frozen at emission. This slice ports that math and demotes OpenAL to a voice bank. RetailSoundMixer (new, Core) carries the byte-decoded curve from SoundManager::GetAttenuation @0x00550020: g = dist < 5 ? vol : 25*vol/d2, clamped to 1 BEFORE the single master multiply, db = ceil(20*log10 g), with a hard -50 dB floor at which retail does not start the voice at all (audible radius ~94.2 m at unity). Pan is PlaySoundInternal @0x00550170's (int)(-15*sin(delta-bearing)) in whole decibels, truncating toward zero, forced to dead centre when (int)distance < 5, with no front/back and no elevation cue. Every AL source is now source-relative with rolloff 0 and the global distance model is None: AL's InverseDistanceClamped was first-power (2/d), quieter than retail up close and far louder at range with no cutoff whatsoever. That was the largest audible divergence in the subsystem (AP-28, retired here). RetailVoicePool (new, Core) ports the allocator at 0x0054FEC0: ring scan for a free or finished slot, then evict the first slot whose DAT priority is strictly lower, else drop. Eviction compared GAIN before, so a loud unimportant sound could silence a quiet important one. It lives in Core because the engine's play path talks to native AL handles and could not be tested; the pool now has 12 conformance tests. The listener keeps using the camera position, which the decode shows is retail-faithful (SmartBox::set_viewer @0x00452D36 hands the same collided camera Position to SoundManager) — only the heading extraction changes, since retail reads one compass bearing and never a forward/up basis. An earlier draft of the plan called this a defect; corrected in the plan so it is not fixed backwards. Opus review found and this commit fixes: a linear pan-to-azimuth mapping that saturated to full separation at 30 degrees (OpenAL Soft's own speaker angle) where retail gives 15 dB — now inverts the constant-power pan law, so full deflection reaches 0.776 of the arc and both channels stay live; the stale FUN_00550ad0 / gain-eviction class header, which contradicted the register row this commit writes; missing discriminating tests for clamp order and pan truncation; dead PlayingGain state whose comment invented a retail symbol; and a third in-tree copy of Position::heading, now delegating to MoveToMath.PositionHeading. MasterVolume folds into the mixer's one multiply instead of AL listener gain, so the cutoff, radius and dB quantisation move with the slider. Register: AP-28 retired; AP-173 (pan law), AP-174 (volume taxonomy), TS-64 (two unimplemented sound prefs), TS-65 (volume-squared quirk, applied on the ambient path only) filed. Research note corrected twice where its summary contradicted its own decode (30 m dB, floor vs trunc). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ffa5087527 |
docs: Campaign A (audio parity) — six-lane retail decode + campaign plan
Full review of the audio subsystem against the named 2013 retail decomp, with byte-verification of every load-bearing float compare (five BN polarity/constant elisions caught). Headlines: retail is a CPU-side 2D pan+gain engine (no 3D listener in use); the SoundTable probability field is a Bernoulli SILENCE gate our SoundCookbook never applies (4,183/4,184 entries are single-entry and we short-circuit them); 0xF750 server sounds are entirely unhandled; ambients are region-authored weighted one-shots (indoors silent by design); and retail EoR has NO music system at all. Plan proposes slices A1-A6; awaiting user go. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6bb4cfa795 |
feat(ui): the spell-bar drop ring — retail's authored drag-accept state, and the ring exposed a real drop off-by-one
Some checks failed
Headless portability / portable-headless (ubuntu-latest) (push) Has been cancelled
Headless portability / portable-headless (windows-latest) (push) Has been cancelled
Headless portability / linux-graphical (push) Has been cancelled
Headless portability / linux-vulkan (push) Has been cancelled
The green ring is retail's own art: every UIItem cell carries an authored DragAccept child (catalog 0x21000037, child 0x1000045A), and the spell bar's drag-over handler (SpellCastSubMenu::OnItemListDragOver @0x004C5990) flips it to the Accept state (0x10000040 -> surface 0x060011F9) for any spell payload. Ported through a per-slot SetDragAcceptVisual seam + a catalog DragOverAcceptance hook; other lists are untouched (null acceptance = neutral). A polarity error in our older docs (Accept/Reject state ids swapped) was corrected against three independent sources; the shipped art was always right, only the labels lied. The ring shares ONE landing computation with the drop (FavoriteDropIndex) — and that requirement exposed a genuine #354 off-by-one: the empty-tail path double-applied the -1 adjustment (retail gates it on the lift's removal @0x004C7157), landing a reordered spell second-to-last instead of last. Fixed; discriminator-verified both ways. AP-172 narrowed + its false empty-tail claim corrected. Clean-room complete solution: 11,545 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> |
||
|
|
ab3146ba88 |
docs(plan): Slice 6b/6c vendor-completion contract — move-to-use, staging, selling, status-bar axiom
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c884a938e0 |
docs(plan): Slice 6 buy-arc contract — selection coupling root cause, 0x005F wire, retail's no-double-click truth
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c721830e71 |
feat(ui): Slice 5.4 — the authored vendor browse panel (LayoutDesc 0x21000012)
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 vendor window is retail's own: LayoutDesc 0x21000012, root 0x100000B7, found by enumerating all 101 layouts for the one containing both known tab controls and clinched by the root's Type 0x10000017 — the literal UIElement::RegisterElementClass id for gmVendorUI (pc:202075). Discovery evidence and the D0 read live in the research doc's new §B.4. D0 corrected two assumptions: retail's category "tabs" are a UiMenu DROPDOWN fed by a hardcoded 18-row ordered category table (ported bit-for-bit against our ItemType enum; list always scoped to exactly one category, first-present wins, selection preserved across refresh per retail's clamp), and the layout authors THREE tabs — Items (browse, this slice), Buying and Selling (staged-transaction review, Slice 6) — decision 4's "browse/Buy tab" names the Items tab retail's mode-2 OpenTab opens. The non-default tabs render and switch pages but stay inert, fenced in comments. VendorUiController mounts Items: category dropdown, icon-cell item row with the retained scrollbar, per-unit retail pricing via VendorPricing.SellPrice (the vendor-stock path VendorProfile:: VendorSellPrice feeds), name/cost on selection. The panel is a pure projection of VendorState — opens on populate, closes on clear; the close button's VendorState.Close() is its only permitted mutation. Nothing on the wire. AP-110 narrowed (vendor leaves the absent-panels list); AP-161 files the precise Slice-6 remainder (Buying/Selling unwired, Buy/Add buttons, InqAcceptability). Twelve controller tests on a real-dat fixture. Clean-room complete solution: 11,323 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> |
||
|
|
763b127ad9 |
docs(plan): Slice 5 vendor-browse contract — research doc + the eight decisions + ordered 5.0-5.5 work
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
fa0c053ebf |
docs(physics): #347 closed WITHOUT a code change — retail's glide alternates exactly as ours does; AD-70 retired as a wrong inference
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 round-2 cdb capture is decisive: during a live retail glide, edge_slide fired ~1.5 times per find_transitional_position — the arm/move alternation's exact signature (3 entries on the arming tick, 0 on the moving tick) — with cliff_slide in lockstep, step_down at 2.5x, step_up 0, and every stack sample on our identical call path. cliff_slide's bytes match our port and ACE's (compare constant at 0x794610 verified 0.0), and the user could not distinguish the two clients side by side. The "retail redirects within the tick" premise misread round-1's set_sliding_normal cadence (per-event, not per-tick, so its 1:1 ratio with edge never discriminated anything). The alternation-tolerant assertion in Issue345SteepSlopeGlideTests is therefore the CORRECT retail-shape pin from both sides; its comment now cites the capture instead of calling the shape a residual. The #269 note is honest the other way: the hope that a within-tick port would explain that feel residual is withdrawn with the premise. The temporary Scratch347 diagnostic is deleted. Capture evidence: 345-glide-stacks.cdb.log (repo root, untracked, cited from the contract's RESOLUTION section). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
535f41bbdf |
docs(physics): #347 premise revision — retail may alternate too; ftp:edge ratio is the discriminator
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 cliff_slide arms are conformant in ACE, our port, and the bytes (compare constant at 0x794610 verified 0.0), the round-1 slidn:edge ratio (538:594) refutes a retail retry storm, and the user's side-by-side speed observation fits alternation. Round-2 cdb script now counts find_transitional_position; H-A (identical, retire AD-70) vs H-B (within-tick yield) resolves on one ratio. The temporary Scratch347 diagnostic test rides along until #347 closes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9fc4cfbf59 |
docs(physics): #347 fix contract — retail's within-tick slide continuation, pinned from pc with candidate mechanisms ordered
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ab89ebdf92 |
fix(physics): #345 — a grounded mover glides along a too-steep face; validate_walkable's return is scoped as retail's bytes scope it
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
Retail's OBJECTINFO::validate_walkable @0x0050d010 initializes its return slot to OK (0x0050d025) and assigns ADJUSTED only inside the below-plane guard, immediately after the push executes (0x0050d249). The guard-fail path — grounded, OnWalkable, plane too steep — jumps past the contact write, the push, and the assignment (0x0050d1b9 -> 0x0050d251): retail deliberately IGNORES the steep plane at primary validation so the insert proceeds, the step-down phase fails on the steep landing, and the edge family produces the per-tick lateral glide. ACE flattened this into an unconditional return Adjusted (ObjectInfo.cs:169) and we inherited it; our TransitionalInsert then retried the byte-identical Adjusted forever — the user's stop-instead-of-slide. Evidence chain: the user's retail observation (the axiom), the live cdb glide profile (edge_slide/cliff_slide 594 each in lockstep, step_up 0), the D0 implementer's correct STOP (fixtures reproduced the stuck fingerprint while faithfully executing the ACE-shaped reading — refuting the reading, not the code), and the capstone byte-decode both Opus reviewers re-derived independently, including the stack-slot frame arithmetic and every ret site's eax. The conformance fixture is the live topology: flat and steep terrain triangles sharing ONE cell's diagonal (a cell-boundary face does NOT reproduce the loop — the cell-scoped primary sample never validates a neighbour's triangle — and is pinned as supplementary). Sabotage: restoring the unconditional Adjusted reds the discriminator with the exact stuck position (0.325 m lateral, 28/30 stuck ticks vs 2.602 m / 14/30 fixed; reviewer B's independent five-angle table is monotone 10-85 degrees). Stuck ticks are counted from positions so the assertion survives the eventual probe strip. In-game glide gate PASSED 2026-08-08: "Well it works, we are sliding. I cant detect any speed change from retail." Filed alongside: #347 + AD-70 (our glide alternates arm/move at half retail's per-tick rate — retail redirects within the tick; next up by user direction), AD-71 (the guard's mutable WalkableAllowance operand vs retail's fixed is_valid_walkable global — now return-value-bearing), and the reviewers' named residuals in the #345 closure entry (placement-arm flip, other-cell coverage gap, EdgeSlide-less projectiles, ACE's server-side shared misport predicting remote drift-then-snap on steep terrain). The unported IsViewer arm of validate_walkable is noted in the D0 doc. Suite: clean-room complete solution 11,271 passed / 4 skipped / 0 failed; Core assembly re-run green after the review-driven test hardening. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7542cfd3c2 |
docs(physics): #345 D0 — implementer's correct STOP + ACE cross-check addendum + round-2 stack-capture script
The synthetic fixtures reproduce our stuck fingerprint while faithfully executing the documented control flow; ACE's independent port shows EdgeSlide reachable only via the OK arm's step-down failure. Together they force the sharper question: retail's insert returns OK per tick where ours returns Adjusted. The round-2 cdb script (stack samples on edge_slide/cliff_slide/step_down + a step_down counter round 1 never had) carries falsifiable predictions written down BEFORE the capture. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e91f16e90c |
docs: #345 fix contract — the runtime profile is the oracle; find the branch the code reading missed
Retail's per-tick edge-family entry (594 lockstep firings, zero step_up) is the constraint any pseudocode reading must reproduce — the prior 'retries from scratch, retail-identical' conclusion is refuted by the live profile, so D0's job is finding the branch that reading missed, with the step-down-phase-ordering hypothesis named as a candidate to verify rather than assume. Trap list absolute: the entire proven-faithful response machinery is off-limits; the fix is routing fidelity only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
18289f95f8 |
docs: #345 D0 verdict — the stuck-tick fingerprint is retail's own algorithm; fix attempt correctly stopped
Five links traced from the named decomp: the below-push never executes (OnWalkable guard — the probe printed the wrong guard pair), the from-scratch retry is retail-identical, and validate_transition's failure path manufactures every captured field including the (0,0,1) default. Stopping dead may simply BE retail. Two validations remain: the user observing their RETAIL client at a comparable slope (the cheapest decisive test there is), and — only if retail visibly slides — the find_cell_list broadphase question via the cdb toolchain. The mechanism paragraph's wrong-cause framing is retained and corrected in place, per the register's own honesty pattern. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3f2b2dc3ed |
docs: #345 mechanism-session contract — a self-selecting transition-phase trace before any theory
The probe design: fires only on the stuck-tick predicate (zero XY yield against nonzero request), printing per-attempt phase outcomes, step-up/ step-down verdicts, every ValidateWalkable branch with its flag guards at the SetCollisionNormal site, and the AdjustOffset pair — the collN=(0,0,1) fingerprint's producer names itself. Trap inventory from the week's do-not-retry ledger attached so the eventual fix cannot stumble into a retail-faithful mechanism. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
712244b8aa |
docs: S4b's STOP fired — retail is PLANT-THEN-LIFT; the reland maps to AD-66's bare radius alone
The contract's D0 byte-pin refuted the tangent-placement premise: validate_walkable @0x0050d010 is planted for every normal mover (Ghidra + BN + ACE agree; ours is already byte-faithful; only the camera branch is tangent). With the byte-proven bare-radius push-out the coherent retail mechanism is plant-then-lift — the push fires once per settle, raises the body to tangent equilibrium, and both checks go quiet there. The slope float comes from the push, not the placement, exactly as the original substitution's own comment argued. The 84% live fire rate was measured against our push-disabled steady state; the #341 assert-shape flip now reads as order-dependent settle state. The user's 'port the retail pair' therefore maps to relanding AD-66's bare radius alone, with the ten-run stability protocol. The STOP rule paid for itself: an implementer without it would have made ValidateWalkable DIVERGE from retail in the name of faithfulness. 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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
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> |
||
|
|
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 |
||
|
|
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> |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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
|
||
|
|
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
|
||
|
|
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> |
||
|
|
886333a2a9 |
refactor(physics): delete the redundant pre-sweep slope projection (AD-10 retired)
Stage 0's measurement (previous commit) says the projection is redundant,
so AD-10 retires by deletion rather than by narrowing.
The measurement. With the sample forced to null at BOTH fork sites, from a
clean build:
* a remote running 30 ticks down a 31-degree walkable ramp produces a
BIT-IDENTICAL trajectory, position for position;
* on an 8.4-degree ramp the two differ by at most 2.8e-5 m in Z after 30
ticks (0.03 mm) and are identical in X and Y — float ordering noise
from projecting twice against the same plane rather than once;
* the whole AcDream.Runtime.Tests suite is unchanged.
That is what redundancy looks like, and the arithmetic explains it. The
boundary projection and Transition.AdjustOffset are the same operation
(v -= N * dot(v, N)) against the same plane, and the composition is
idempotent: a vector already on the plane has dot(v, N) == 0, so the
sweep's own projection is a no-op on an already-projected offset and the
full-strength projection on an unprojected one. Either alone produces the
same offset. On terrain a THIRD mechanism, ValidateWalkable's push-out,
re-seats the sphere on the plane every sub-step regardless.
Deleted:
* both RuntimeRemotePhysicsUpdater sample sites (the host and no-host
fork branches carried the block verbatim — the AP-22 shape, a row
naming one site where two exist);
* the terrainNormal parameter and projection block on
RemoteMotionCombiner.ComposeOffset;
* the same block on ComputeOffset, which has no production callers but
held a second copy of the divergence, so leaving it would have made
the row's retirement false;
* PhysicsEngine.SampleTerrainNormal, now callerless.
Removing the parameter rather than passing null is deliberate: it is what
makes a future one-site-only regression a compile error instead of a
silent half-fix.
Two tests went with it —
ComputeOffset_RootMotionFallback_SlopedTerrainNormal_ProjectsZOntoSlope and
its flat-ground twin. Both were weak on their own terms: they drove the
production-dead ComputeOffset and computed their expected values by
re-implementing the projection formula, so they could catch a wrong
MULTIPLY but never a wrong PLANE — which is exactly what the divergence
was. The surviving coverage is geometric and runs the production tick.
Three claims in the old row did not survive contact with the code and are
recorded in the retired row rather than quietly dropped: the justification
(remotes do run the sweep); the description of ComposeOffset's guard as
"interpolation-active" when the code reads `if (!interpolationOverwrote`;
and the roof clause, stale since Bug B gated the sample on OnWalkable —
a steep roof is OnWalkable == false, so the path never ran on #32's
geometry. The retail anchor is corrected too: pc:272296-272346 truncated
both the sliding-normal validity gate at the head and the entire safety
push-out block at the tail. The whole function is 0x0050a370,
pc:272271-272393.
This does not fix #32 and does not partially fix it. #32's remote half was
already closed at
|
||
|
|
619de97ad1 |
fix(test): evaluate BOTH deleted guards; correct AP-22's overstated coverage claim
From the AP-22 dual review (both lenses PASS). No production change.
THE RECORD WAS WRONG.
|
||
|
|
bc4679cda5 |
fix(physics): delete the invented Setup-radius collision cylinder (AP-22)
Retail synthesizes NO shape for a shapeless object, so the fix is deletion,
not a corrected height formula.
CPhysicsObj::FindObjCollisions @0x0050f050 dispatches exclusively -- BSP xor
CylSphere xor Sphere xor nothing. The BSP branch leaves via an unconditional
`jmp 0x50f2b0` at 0x0050f19d and cannot reach the primitive branches; a
CylSphere-bearing object that survives its loop returns rather than falling
through to the Sphere loop; and with zero cylspheres, zero spheres and no
physics BSP, `0x0050f22f je 0x50f31b` branches straight to the epilogue,
returning the OK_TS seeded at `0x0050f13b mov edi,1`. CPartArray::GetRadius
(0x005180a0) and GetHeight (0x005180b0) are absent from the function's entire
call set -- Setup.Radius/Height serve attack cones, cylinder_distance and
MoveTo, never collision geometry. Disassembled directly from the PDB-paired
binary (GUID 9e847e2f-777c-4bd9-886c-22256bb87f32) rather than read from the
Binary Ninja text, whose ebp_1 aliasing in this function is visibly corrupt.
THREE copies were deleted, not one. The AP-22 register row cited
LiveEntityCollisionBuilder.cs and ShadowShapeBuilder.cs; the latter never
reads Setup.Radius at all, and the row omitted both
LandblockPhysicsPublisher.PublishStaticEntity and
LandblockPhysicsContentBuilder.PublishStaticCollision -- the second being the
only copy the headless host executes. Fixing just the cited site would have
left headless statics on the invented footprint.
The branch was unreachable dead code, not a live approximation. A sweep of all
5,935 Setups in the installed client_portal.dat -- validated by byte
accounting (5,935/5,935 records consumed with an exact 20 + 48*numLights
residual tail, zero unexplained bytes) and independently reproduced by the
production FlatCollisionAssetBuilder.FlattenSetup path -- finds 0 Setups
satisfying the guard: every Setup with Radius > 0.0001 carries at least one
CylSphere or Sphere, and all 1,294 genuinely shapeless Setups have Radius
exactly 0. Buckets: 678 cylsphere, 3,605 sphere-only, 358 BSP-only, 1,294
shapeless, 4,282 with Radius > 0.0001. Nothing loses collision because nothing
gained it, so no visual gate is required.
Tests, all sabotage-verified in both directions:
- InstalledSetupCollisionReachabilityTests (new, Content) -- the negative
claim plus five EXTERNAL positive controls, so a broken enumeration cannot
satisfy it vacuously. Inverting the claim reddens it; emptying the
enumeration fails on the controls at 0 != 5935 rather than passing.
- ShapelessSetupWithRadius_ProducesNoRegistration (new, App) -- restoring the
deleted block reddens exactly this fact and nothing else.
- Build_PropagatesExactStateFlagsScaleAndFullSeedCell -- re-hosts the state /
PWD-flag / seed-cell coverage that rode on the deleted fallback test, whose
fixture (a Setup with a radius and no primitives) cannot exist in the DAT.
Flipping a FromPwdBitfield bit reddens it; so does swapping SeedCellId for
the landblock id.
Also corrects ShadowShapeBuilder's retail-anchor comment, which claimed each
part's find_obj_collisions tests "CylSpheres + GfxObj BSP".
CPhysicsPart::find_obj_collisions @0x0050d8d0 tests ONLY the GfxObj physics
BSP; CylSpheres are a Setup-level array reached via CPartArray::GetCylsphere.
That comment was the written justification for the additive emission now filed
as AP-152, so it is corrected here even though AP-152 is not fixed here.
AP-22 retired with evidence; AP-152 filed (live path emits primitives AND BSP
parts additively where retail is exclusive -- 172 of 5,935 Setups including
BSP doors; deliberately not folded in, it needs its own visual gate). Issue
#330 filed: the headless host registers no live-entity collision at all, a
pre-existing gap this survey established and nothing tracked.
Gates: Release build 0 errors / 0 warnings. Complete solution suite
11,195 passed / 4 skipped / 0 failed (baseline 11,193/4/0 at
|
||
|
|
bcb66ccdf3 |
fix(test): cover the atlas-tier seam the D-1 fix depends on; correct AP-150's citation
Both items come from the D-1 fix review (both lenses PASS, D-1 genuinely closed). No production behaviour changes. L1 — THE SEAM HAD NO COVERAGE. The D-1 fix's "empty by construction" claim rests on LandblockSpawnAdapter's atlas-tier filter (`if (entity.ServerGuid != 0) continue;`) skipping the live server projections DetachNearLayer deliberately RETAINS across a demote. The reviewer removed that filter and all 4,170 App tests passed — only two Core unit tests caught it, none through a demote. So the invariant the re-assert depends on could have been deleted silently, re-opening D-1 by another route: a non-empty re-assert whose mesh reference is never satisfied leaves IsRenderReady false, which is the portal hang again. NearToFarDemote_WithALiveServerEntity_StaysRenderReady now demotes a landblock that CARRIES a live server-spawned entity through the real GpuWorldState + LandblockSpawnAdapter + LandblockPresentationPipeline, and asserts the retained entity never enters the desired set. Sabotage-verified: with the filter removed, exactly one test fails — this one — and the other 25 pass, including all four D-1 regression tests. That is the finding restated as a measurement: the D-1 tests genuinely do not cover this seam, and now something does. AP-150 citation corrected: the row cited 0x004D7064 as the ECM_UI::SendNotice_DisplayStringInfo call site. That address is the PStringBase construction of the "In Portal Space - Please Wait..." literal (:219516); the actual call is 0x004D70A1 (-> 0x006925B0). Same class of slip the #280 commit had just corrected for #326 — worth noting that a row filed WITH a byte-level disassembly still mis-cited a neighbouring address. Also refactored the existing pipeline demote test to keep its doc comment attached to its own method (an earlier insertion had orphaned its [Fact]). App.Tests 4,170 -> 4,171 passed / 3 skipped, net +1 for the new test. No new skips; none of #302/#308/#321 surfaced. src/ is byte-unchanged (the sabotage was reverted and verified). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
73cdb95c7b |
fix(streaming): make a demoted landblock render-ready like a published one (#280 D-1)
Both #280 review lenses returned FAIL on the same defect, and both were
right. IsRenderNeighborhoodResident's widened outer arm requires
IsRenderReady out to FarRadius, justified by "a Far-tier landblock
registers with an empty mesh set and is therefore render-ready." That held
only for a landblock that ARRIVED as Far. The second, equally first-class
way to be Far tier is a Near->Far DEMOTE:
DemoteLandblock -> EnqueueNearLayerRetirement
-> LandblockRetirementStage.MeshReferences
-> GpuWorldState.ReleaseLandblockMeshReferences
-> LandblockSpawnAdapter.OnLandblockUnloaded => WantsLoaded = false
while DetachNearLayer deliberately keeps the landblock loaded, terrain-mesh
resident, terrain-collision resident and DRAWN. Nothing re-publishes an
already-loaded landblock, so the demoted member satisfied NEITHER arm of
the gate, permanently: wormhole tunnel plus centered "In Portal Space -
Please Wait..." forever, no recovery short of relog.
Reachable by ordinary play. Two consecutive recalls to the same landblock
with walking in between makes ChangesStreamingCenter false, so there is no
origin recenter and the region recentres through the ordinary demote diff.
Also reachable via a mid-hold quality-preset drop -- ironically the exact
scenario ReconcileDestinationReservationRadius was added to support. The
pre-#280 radius-1 gate never touched that band, because nothing inside the
Near ring can demote.
FIX SHAPE. Make the two routes genuinely equivalent rather than teaching
the predicate to tolerate the difference. ReleaseLandblockMeshReferences
becomes "reconcile the registration to the post-retirement tier": after the
release converges, if the landblock is still loaded AND still Far tier,
re-assert the empty registration -- the identical OnLandblockLoaded(lb,
empty) a PublicationKind.Far activation makes. It is empty by construction:
DetachNearLayer retains only live server projections, which the adapter's
atlas-tier filter skips. A full retirement is unaffected (DetachLandblock
clears both _loaded and _tierByLandblock), and a throwing release still
retries because the re-assert is only reached after the adapter converged.
The alternative -- "|| (IsFarTier && IsLoaded)" at the gate -- was
rejected: it fixes one caller while leaving IsRenderReady meaning two
different things, which is precisely how this defect arose. After this
change the predicate reads "drawable at its current tier" for every caller,
with no knowledge of how the landblock got there.
WHY THE TESTS MISSED IT, fixed here too:
- Proof obligation P2 was discharged against RESIDENCY (the FarRadius+2
eviction threshold) rather than against IsRenderReady, the gate's actual
atom. The contract now carries the correction and the restated
obligation: no transition may REVOKE IsRenderReady from a landblock that
stays inside FarRadius.
- WorldRevealDerivedWindowIntegrationTests advertised itself as end-to-end
against the real GpuWorldState but constructed it with no spawn adapter,
so its IsRenderReady degenerated to IsLoaded via the "?? true". The
single most load-bearing predicate in the change was stubbed out by a
null in the test named after it -- the same shape as C5b's D3 and #276's
three settler tests. Every fixture in that file now owns a real
LandblockSpawnAdapter.
- The P1 test's comment described its subject as "a Near-shaped completion
the streaming window has since DEMOTED to Far". It is not; it is a fresh
PublishAsFar, the case that does hold. Corrected, since a future reader
would have taken it as demote coverage.
Four new regression tests, all driving the real GpuWorldState +
LandblockSpawnAdapter + LandblockPresentationPipeline through an actual
demote, and all sabotage-verified in both directions (fail with the
production change reverted, pass with it):
NearToFarDemote_LeavesTheLandblockRenderReadyThroughTheRealPipeline
NearToFarDemote_LeavesTheLandblockRenderReadyUnderBudgetedRetirement
TieredWindow_StaysResidentAfterAnOuterRingDemote
OutdoorReveal_SurvivesAnOuterRingDemoteDuringTheHold
The budgeted variant exists because production composes
LandblockRetirementCoordinator.CreateBudgeted, whose MeshReferences stage
is a separate call site from the legacy pipeline's.
SECONDARY, same commit:
- R-1: ACDREAM_PROBE_REVEAL_RADIUS=0 was parser-accepted and
Runtime-rejected -- it yields far = 0 for an outdoor destination, which
fails invalid-readiness-shape on every acknowledgement, hanging the very
A/B route the probe exists to measure. Parser floor raised to 1, with a
7-case table test.
- R-2: the composite-warmup TRIGGER had silently moved onto the far
window's critical path. Pre-#280 the gate and the composite domain were
the same radius-1 square; #280 widened the gate without widening the
domain, so every composite upload serialised behind the last outer-ring
landblock for no readiness benefit. Warmup now starts once the NEAR
sub-window is published -- trigger scope == domain scope, as before. The
reveal gate is untouched: Evaluate still requires the full window AND
composite readiness.
- AP-150 filed: acdream's RetailWaitCueDelay = 5 s arming is NOT retail's
trigger, and #280's commit message got this wrong on both clauses. Retail
emits the notice unconditionally per tunnel rotation segment, in the else
arm of the segment-expiry test at 0x004D6FCD; segment duration is
RandDouble(0.6, 1.8) s, byte-decoded at 0x004D6FE6. The 5.0 constant at
VA 0x007991B0 is CellManager::CheckPrefetchStatus's prefetch RETRY
cadence and has nothing to do with the cue. acdream's own 0.6/1.8 segment
constants already match retail exactly; only the arming is wrong.
Adopting retail's unconditional emit is filed as #329 rather than folded
in here -- it is a user-visible presentation change and wants the user's
eyes.
- AP-151 filed: the gate is materially STRICTER than retail on the
mesh-build/GPU-upload axis. Retail's LScape::PreFetchCells blocks on DAT
RESIDENCY only -- no geometry construction, no upload; that work is lazy
at draw. acdream requires a DAT read, terrain mesh build, render-thread
upload, spatial commit, collision admission and spawn-adapter activation
per member of a 625-member window, metered at MaxCompletionsPerFrame.
Nothing bounds the hold. This is the OPPOSITE asymmetry from AP-149; both
are live at once, on different axes.
- AD-2's amendment stated the false Far-tier readiness assumption verbatim;
corrected, along with the same error in
claude-memory/reference_two_tier_streaming.md, which now carries an
explicit DO-NOT-RETRY on the special-case-the-predicate shape.
- AP-115 scope-noted (it covers the cue's presentation, not its arming).
- #326's SmartBox::set_mid_radius citation corrected: the entry is
0x00453180; 0x004531D0 is the mid-function re-arm branch.
Blast radius: GpuWorldState, LandblockSpawnAdapter,
WorldRevealReadinessBarrier and StreamingDiagnostics are all App-internal;
AcDream.Headless and AcDream.Runtime reference none of them outside
comments. Headless tests run green as part of the gate below, per C5b's
lesson about surveys that skip the no-window host.
Gates: Release build 0 errors, 18 pre-existing xUnit analyzer warnings.
Complete suite "dotnet test AcDream.slnx -c Release -m:1" with
ACDREAM_PAK_PATH set: 11,192 passed / 4 skipped / 0 failed, from a clean
rebuild (a prior session's deleted probe file had been compiled into a
stale test DLL). Baseline at
|
||
|
|
408c8e8f34 |
docs: #276 remainder scoping — the outdoor half is already correct; EnvCell is the real gap
Analysis only. A candidate fix was written, built clean and passed the
three existing settler tests, then deliberately REVERTED — the only test
that discriminates it needs an EnvCell fixture that was not safe to
assemble at the end of this session. The production tree is unchanged.
Headline: the issue's framing is half wrong, and the half it misses is the
whole fix. PhysicsBody.Position's ordinary setter already carries the world
displacement into the landblock-relative frame AND calls
LandDefs.AdjustToOutside, which recomputes the outdoor cell index across
24 m cell crossings and wraps/bumps the landblock across 192 m boundaries.
So for an outdoor->outdoor settle, discarding settle.CellId costs nothing.
The live defect is EnvCells. An EnvCell id is not derivable from a world
position, and AdjustToOutside's guard ((cell & 0xFFFF) is >= 1 and <= 0x40)
deliberately excludes EnvCell ids from that path. settle.CellId is the ONLY
carrier of an EnvCell identity, and it is exactly what the settler drops —
so the defect is the issue's parenthetical ("outdoor/EnvCell seam, stacked
EnvCells"), not its main clause. That also means the fix is a no-op on the
outdoor path that dominates production and corrective only at the indoor
seam.
Recorded so the next reader does not repeat the misreading I made:
CommitTransitionPosition looks like it pairs a new cell with a stale local
origin, but line 259's `Position = worldPosition` runs the ordinary setter
first, so line 265 reads the already-updated origin. Retail anchor
CPhysicsObj::SetPositionInternal(CTransition const*) 0x00515330 commits both
objcell_id and frame, including EnvCells.
Also recorded: the three existing settler tests build bodies with no
CellPosition, so every one passes identically with or without the fix.
Shipping against them would repeat C5b finding D3 — a test that passed with
its own change reverted. The doc carries the exact discriminating test, its
required sabotage, and the fixture risk (the resolver must genuinely report
the EnvCell in settle.CellId; a fixture that silently resolves outdoor would
be green and prove nothing).
Open and unverified: whether the remote spawn-seed caller's body carries a
CellPosition at all. CommitTransitionPosition early-returns on a zero cell,
so the fix would be an inert no-op there and #276 would stay open for
remotes. Must be settled before claiming the fix closes both halves.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
1d2d4bb8bd |
docs: #317 velocity-chain audit — verdict NO RETAIL BASIS (report-only)
Report-only per CLAUDE.md's investigation rule; no fix applied and none approved. The call at LiveEntityNetworkUpdateController.cs:2459 is untouched. Verdict: velocity is a SEPARATE WIRE CHANNEL in retail, and acdream's accepted-Position path crosses it. SmartBox::DoVectorUpdate 0x004521C0 is retail's sole velocity installer for a remote (set_velocity 0x0045221E + set_omega 0x0045222C), gated on update_times[3] = VECTOR_TS — not Position's update_times[0]. An exhaustive grep of its call sites returns exactly two, and neither is the Position path: SmartBox::HandleVectorUpdate 0x00453480 (call 0x004534E6) and SmartBox::HandleCreateObject 0x00454C80 (call 0x00454EE9). HandleReceivedPosition's only set_velocity is 0x004541B4, which ZEROES the local player on the teleport arm. Retail is not merely silent here, it is deliberate: PositionPack::UnPack 0x00516740 does decode a velocity off the Position wire (field written 0x005167E9) — retail receives the value and drops it on this path. acdream instead commits acceptedSpawn.Physics?.Velocity on every accepted Position, and the retail-correct mechanism ALREADY EXISTS one method away (TryCommitAuthoritativeVector, whose doc comment describes DoVectorUpdate's exact paired shape). The Position-path call is therefore both non-retail and redundant with a correct sibling. Sharpening the divergence: the call passes `?? Vector3.Zero`, so a Position without HasVelocity actively zeroes the body — something retail never does on this path. Recommended (NOT approved): either remove the call, or keep it and file a register row as a deliberate adaptation in AP-135's class. Three unresolved inputs decide which, listed in the report's section 5 — chiefly what consumes body.Velocity for a remote (AP-80's velocity-derived animation cycle is the specific unknown), and whether ACE sets HasVelocity at all. The retail half of the audit is settled; those three are cheap follow-ups that do not need the binary again. Successor note: this function family carries Binary Ninja's dropped-flag artifact (`-((eax_4 - eax_4))` at 0x004521F5 and 0x00452186), the same one the C5b review hit in Gate A. Do not read a comparison here from pseudo-C. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
429775d4c4 |
docs: #316 investigation — verdict COSMETIC, resolved without a live gate
Report-only per CLAUDE.md's investigation rule; no fix applied and none approved. Committed so the evidence is not lost. Verdict: the player arm's airborne-snap block skips the collision-shadow publish, but the stale shadow self-heals within one object quantum (~33 ms). Not the #184 invisible-but-solid class. The reasoning is structural rather than incidental, which is why it resolved offline instead of needing a connected sample. The per-tick gate at RuntimeRemotePhysicsUpdater.cs:840 compares the body against LastShadowSyncPos/Orientation, and those fields are stamped ONLY inside SyncRemoteShadowToBody immediately after a publish. They therefore record where the shadow actually is, which makes the gate an invariant check ("is the shadow more than 1 cm / 0.51 degrees from the body?") rather than a change-detector. The snap's two raw field writes leave that invariant violated and untouched, so the next quantum sees the full delta and republishes. Three findings beyond the question asked: - One residual does NOT self-heal: past 96 m the activity gate deactivates the remote while OnPosition is not distance-gated, so a distant player-remote's render entity moves and its shadow does not, until it re-enters the bubble. Unobservable in practice — everything that could sweep against it is gated by the same rule. - The "LANDING TRANSITION" naming throughout the file is stale: the predicate is !Body.InContact, the whole airborne period, so it fires on every airborne update rather than once at the landing edge. - RuntimeSetPositionState.cs:5037 stamps LastShadowSyncPosition before a guard at :5138 that can return ahead of the publish at :5148 — a possible masking hole, deliberately not folded in. Retail note: retail has no separate shadow at all — SetPositionInternal 0x00515330 calls remove_shadows_from_cells/add_shadows_to_cells in the same transaction, so the skip is a real divergence, just a 33 ms one. Recommended next step (NOT approved): an offline two-step test composing the collapse-matrix player-guid landing fixture with one Tick, asserting the shadow converges. Strictly stronger than a connected sample, which could only show that nobody noticed 33 ms. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |