27 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1390f9477d |
fix: airborne jump refusal fires at RELEASE, not press (user retail
gate; supersedes CH round-1 item A) The user's retail description matches the decomp exactly: charge_jump @0x005281c0 has NO grounded check - it refuses only 0x49 (CanJump encumbrance) and 0x48 (fallen/crouch-family forward commands). Pressing jump while airborne begins the powerbar and charges normally. The 0x24 "You can't jump while in the air" comes exclusively from the RELEASE path (ClientCombatSystem::DoJump @0x0056B110 -> CMotionInterp::jump -> jump_is_allowed, whose airborne 0x24 our port already carries test-pinned). A charge held through landing executes a normal jump on the grounded release. PlayerMovementController's input orchestration now mirrors CommenceJump/DoJump: - Press edge: ChargeJump() decides; a refused charge (0x48/0x49) reports and never begins the bar (retail's jump_pending stays 0). The invented airborne press-edge 0x24 report (CH user-gate round 1 item A - added when the press/release split was not yet known) is deleted; CommenceJump's in-air fallback text is unreachable with a faithful charge_jump. - Hold: accumulates grounded OR airborne; leaving the ground mid-charge no longer force-fires the jump. - Release: fires jump(); an airborne release refuses 0x24 there. Tests: the round-1 press-edge test is replaced by two release-semantics tests (airborne release reports once; held-through-landing grounded release jumps silently). Runtime 1,619, App 4,987/3, Core jump family 159. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ab82347d42 |
fix(runtime): same-value LocalEntityId re-assertion is a no-op; file #365 headless hydration regression
The Campaign CH jump-probe headless reproduction quarantined on its first advance tick: AdvanceBeforeNetwork re-asserts the resolved local entity id every tick, which the C3c configuration seal treats as a mutation on a dormant controller. A same-value write is now a no-op; a DIFFERENT id while sealed still throws. One layer deeper the probe exposed #365: the headless world never hydrates (entities stay 0, the movement controller never publishes), so headless bots cannot move at head — filed with the full evidence chain. HeadlessDiagnosticWriter.Failure now emits the full exception detail (type-only cost a whole diagnosis round-trip). Runtime tests 1,322/0, Headless tests 89/0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c1f1582576 |
fix(chat): Campaign CH user-gate round 2 -- portal notice rerouted to SpewBox, verbatim /help extraction, jump-in-air evidence
Item 2: retail's portal-space "In Portal Space..." notice is the SpewBox (ECM_UI::SendNotice_DisplayStringInfo(0x1A,...) -> AddTextToScroll(str, 0x1A, 1, 0), hardcoded to the SpewBox per the decomp), not a dedicated centered overlay. PortalWaitNoticeController and its lease are deleted; PortalTunnelPresentation's per-rotation-segment cadence now writes straight into RuntimeCommunicationState.AddText(ClientLocal) -- the SpewBox's own dedupe-at-index-0 handles the repetition exactly as retail's does. Register row AP-184 records the surface fix and the AP-178 scope extension. Items 4+5: /help text was partially fabricated -- the user caught the "/help death" meta-message. Generalized tools/pdb-extract/sweep_weenie_strings.py to decode narrow PStringBase<char> literals (the ClientCommunicationSystem::Help* family's shape) alongside its original UTF-16LE support, then swept every HelpXxxGroup function's exact byte extent against the PDB-paired acclient.exe. 4 of 7 group topics (death/status/text/allegiances) are now complete verbatim listings; the other 3 (channels/chatting/commands) keep an honest UNVERIFIED note citing HelpStupidChannelHack @0x0056f290 (a genuinely undecodable BN-mislabeled-fragment mechanism) instead of the old fabricated sentinel. 7 of ~35 channel one-liners are also now verbatim. ISSUES.md #364 tracks the remainder; RetailCommandHelpTableTests.cs pins every result byte-exact. Item 1: jump-in-air refusal still silent live is NOT reproduced and NOT speculatively fixed. Exhaustive static re-audit found the mechanism correct by construction (single-writer OnWalkable, exactly-once-per-frame Update()/Capture(), no interfering edge-history resets). A live headless repro (new jump-probe bot policy, real ACE connect) was blocked -- probeaccount2 has no character, and the graphical client already owned testaccount this session so the task's own fallback rule forbade using it. Two temporary probes are left behind ACDREAM_PROBE_JUMP=1 (blocked entirely in Headless by the existing multi-session static-state guard -- graphical-only for the next round). Item 3 confirmed fixed, no regression. Item 6 (resize: no diagonal cursors, cannot grow Y from bottom-right) folded into CH6a's existing scope. Full Release suite: 12,267 passed / 4 skipped / 0 failed (up from 12,221/4/0). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
47e40900f3 |
fix(chat): Campaign CH user-gate round 1 — jump-in-air edge, portal cue cadence, wrap/prefix/color fixes
The user tested Campaign CH's CODE-COMPLETE build live and reported ten defects (docs/plans/2026-08-09-chat-parity-campaign.md, "User gate — round 1"). Items A-G are fixed here; the remaining three (extra chat windows on 1/2/3/4, resize working in only one corner, transparency/ artifacts) are out of scope for a fix and filed as slice CH6. A. Jump-in-air refusal never fired live: the jump block only ever evaluated input.Jump inside the grounded-charge or already-charging branches. PlayerMovementController now detects the press RISING EDGE while airborne and reports WeenieError.NotGrounded once per press, leaving the grounded charge/fire path untouched. B. ChatVM's invented "[System] " prefix is dropped — retail prints system text bare. [Popup] is unchanged (AP-175). C. SpewBoxController's color is now the user-pinned exact value (1, 1, 0.247, 1), the same bright yellow as an incoming Tell. Register row AP-178 updated: color CLOSES, size/position/font stay open per the user's live report that they still differ. D. Closes #329: PortalTunnelPresentation now emits the portal wait cue unconditionally on every rotation-segment boundary, matching gmSmartBoxUI::UseTime's decompiled else-arm exactly instead of gating on a 5-second hold local transits never reached. PortalWaitNotice Controller now renders it in the same pinned yellow as item C. Register row AP-150 retired. E. Closes #362: new ClientCommandResponses.cs parses and renders the four previously-unhandled inbound GameEvents (ChannelIndex, ChannelList, AvailableHouses, AllegianceInfoResponse), each ported line-for-line from the named-retail decomp's inbound handlers. Register row TS-70 retired. F. ChatWindowController.WrapText now splits on embedded '\n'/'\r\n' first, then word-wraps each segment independently — server text like /help's reply no longer collapses onto one line. G. The chat input field's right edge no longer holds a fixed absolute pixel position across a window resize; Bind now upgrades it to retail edge-mode 1 (UiLayoutPolicy) or the AnchorEdges.Right stretch fallback so it tracks the window's client width instead of overflowing past a narrower resize. Full Release suite: 12,247 passed / 4 skipped / 0 failed (baseline 12,221/4/0 + 26 new tests across items A, E, F, G). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
77c8296e3f |
feat(chat): Campaign CH slice CH2 — retail SpewBox interface text
Retail routes on-screen refusals ("You can't jump while in the air",
"You are too encumbered to carry that!") through a SEPARATE transient
screen surface (gmSpewBoxUI, ClientSystem::AddTextToScroll @0x00563C50)
that never touches the chat scroll — type 0x1A is exactly the bit every
ChatInterface window's default filter excludes
(ChatInterface::ChatInterface @0x004F4550). acdream had no such split:
every WeenieError rendered in chat at a single stand-in LogTextType
0x00 (CH1-era approximation, register AP-176), and locally-detected
jump refusals were silently discarded.
This slice ports the full mechanism per
docs/research/2026-08-09-chat-retail-interface-text.md:
CORE (AcDream.Core/Chat):
- WeenieErrorMessages.Resolve now returns (text, RetailLogTextType) from
a 338-row transcription of ClientCommunicationSystem::HandleFailureEvent
@0x00571990 (Appendix A's 339 cases minus one, 0x4F8, deliberately
excluded — its case body is a tangled decompiler artifact, not
resolvable with confidence). Spot-checked ~20 rows directly against
the raw decomp (case 0x2b/0x36/0x3a/0x4e/0x4ec/0x4f3/0x4f4 and the
jump family), beyond the ~10 the brief asked for, because the first
pass surfaced two transcription classes the research doc's markdown
silently ate: (1) 7 ids marked "shared string global" resolved by
reading the case bodies directly (0x24/0x48/0x49 reuse the jump-
refusal globals; 0x4DE/0x4DF/0x55A/0x55E are pure param passthrough);
(2) 19 "arg3 + literal" CONCATENATION ids whose leading space (and
therefore their %s marker) the markdown table's cell-trimming ate —
fixed by re-reading each case body, several requiring a SECOND
non-truncated data_XXXXXXXX dump elsewhere in the same oracle file to
recover text the ~33-char inline preview cut off. One retail typo is
preserved verbatim: 0x4F4's second placeholder is literal "$s", not
"%s" — only the first substitutes.
- ClientTextRefusals: the 11 process-lifetime string globals, all
byte-recovered from the PDB-paired C:\Users\erikn\Downloads\acclient.exe
(MATCH verified via check_exe_pdb.py) via raw UTF-16LE prefix search —
5 were truncated in the research doc's own transcription and all 5
turned out to end "...combat mode"/"...this position", not the
shorter "...combat" a truncated read would suggest.
- SpewBoxState: the gmSpewBoxUI pending/visible queue port (insert-at-0,
dedupe-against-index-0-only, MaxConcurrentItems overflow, per-entry
expiry, one-frame enqueue/drain decoupling). Placed in Core (not
Runtime as the brief's default) because AcDream.UI.Abstractions
references Core but not Runtime, and SpewBoxVM needs to wrap it
directly — the same constraint ChatVM already satisfies against
ChatLog.
- Folded the 4-entry WeenieErrorText.cs into the full table; deleted it.
RUNTIME (AcDream.Runtime):
- RuntimeCommunicationState.AddText(text, type, windowId): the
AddTextToScroll chokepoint. type == ClientLocal -> SpewBox only, never
chat; everything else -> the existing transcript, tagged with type.
- GameEventWiring gains an `onInterfaceText` delegate hole (Core.Net
cannot reference Runtime, so this follows the file's own established
pattern for every other Runtime-owned sink). Rewires 0x028A/0x028B/
UseDone through the full table + router; fixes 0x02EB
CommunicationTransientString's routing type from a CH1-era 0x00
guess to retail's hardcoded ClientLocal (Handle_Communication__
TransientString @0x0057D460).
- LiveSessionEventRouter's 0xF7E0 ServerMessage handler now routes
through AddText with the wire chatType verbatim instead of always
writing ChatLog directly.
- PlayerMovementController gains OnInterfaceText, applied by
RuntimeLocalPlayerMovementState to every controller it installs.
Reports ChargeJump/jump refusals exactly as ClientCombatSystem::
CommenceJump @0x0056AF90 / DoJump @0x0056B110 do — confirmed via
their compiled dispatch that ONLY 0x24/0x48/0x49 produce text;
0x47 (GeneralMovementFailure, fully-constrained/no-stamina) and any
other code are retail-SILENT (DoJump's jump table has exactly 4 real
targets), which contradicts this task's brief ("0x47 -> the
constrained/stamina row per §4.2") — the brief's reading of §4.2
described what jump_is_allowed COMPUTES, not what CommenceJump/DoJump
DISPLAY for it. Implemented the decomp-verified silent behavior.
APP (AcDream.App / AcDream.UI.Abstractions):
- The 5 composition sites that already used RetailLogTextType.ClientLocal
now call Communication.AddText instead of Chat.OnSystemMessage
directly, so they reach the SpewBox instead of the transcript.
- SpewBoxVM (UI.Abstractions) + SpewBoxController (App), modeled
directly on PortalWaitNoticeController. Position/font/colour/
MaxConcurrentItems are placeholders: SpewBoxLayoutDumpDiagnostic
exhaustively swept the installed client_portal.dat's entire LayoutDesc
id range (0x21000000-0x21000075, 101/118 ids populated, sanity-checked
against 3 known ids) and found ZERO elements of class 0x10000016 —
gmSpewBoxUI is mounted from C++ code, not any authored LayoutDesc, so
the dump cannot recover these values.
REGISTER: AP-176 retired (its WeenieError half is now the full table
port); its OnCombatLine half was never in this slice's scope and is
split out to AP-179 so that divergence keeps a row. AP-177 (invented
line lifetime) and AP-178 (invented position/font/colour/max-items)
filed for the presentation placeholders above. AP-175 (PopUpString ->
chat instead of modal) is untouched, not duplicated.
Suite: 11,890 passed / 4 skipped / 0 failed (was 11,835/4/0; +55 net
new tests, 0 regressions).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
972c7ab3b8 |
fix(input): focus loss no longer faults on an unpublished movement controller (#356)
Losing window focus calls CameraPointerInputController.HandleFocusChanged -> MouseLookController.EndForLifecycle -> PlayerMovementController .EndMouseLook, and EnsurePublishedForRuntimeOperation throws when the controller exists but is not yet published (mid-login) or already retired (post-logout). A focus callback can land in either window, so a simple alt-tab during the login stream took the whole process down with an unhandled InvalidOperationException. Hit live during the Campaign A listening-session launches. EndAndRestoreCursor already guarded 'no controller'; publication state is the finer-grained form of the same condition, so the guard is completed with the new CanExecuteLiveMovement predicate (the exact lifecycle set EnsurePublishedForRuntimeOperation accepts) rather than wrapping the call in a catch. Cursor restore still runs unconditionally - presentation is always safe. Published-controller behaviour is unchanged. Also files issue #357: the login placement stall this session exposed (reveal ready=True, player Place edge never executes, world never opens). That one is NOT fixed here - full evidence chain, wire capture, and probe output are in the issue. It is a placement-domain bug and blocks the Campaign A listening gate. Suite: green except the known load-dependent measurement flake (RuntimeCollisionReportingStateTests allocation pin), which passes in isolation. 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> |
||
|
|
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> |
||
|
|
6921a02744 |
refactor(physics): delete legacy PhysicsEngine.Resolve/ResolvePlacement/HasCellSurface (C5a, AP-1/AD-1)
Member-wise deletion of the three legacy resolver members named in docs/research/2026-08-05-c5a-contract.md: PhysicsEngine.Resolve, PhysicsEngine.HasCellSurface, and PhysicsEngine.ResolvePlacement. An exhaustive receiver census over src/ found zero production callers of any of the three — every production placement writer already reaches the canonical PhysicsEngine.SetPosition transaction exclusively through RuntimeSetPositionState (three call sites total). The deletion is purely member-wise: IsSpawnCellReady and AdjustPosition, which shared the same source region as the deleted members, are preserved byte-identical — every remaining production caller of either (including PhysicsCameraCollisionProbe, AdjustPosition's sole surviving production caller) is unaffected. Companion changes: - PlayerMovementController's 3-argument SetPosition test overload is renamed to SeedPlacementForTest (internal) and CommitPreparedPosition is deleted; 83 call sites across 19 test files were mechanically renamed to match. - Seven pinned test dispositions from the contract are executed: 3.1 (PhysicsEngineTests.cs: 11 legacy-resolver tests deleted, 6 ResolveWithTransition tests kept), 3.2/3.3/3.4 (re-point to canonical SetPosition, with TransitionScratchDifferentialTests.cs additionally gaining positive IsCommitted assertions after each bitwise comparison so the differential proves a placement actually committed, not just that two possibly-uncommitted results match), 3.5 (Runtime rename), and 3.6 (PlayerMovementPlacementTransactionTests.cs rewritten — its xmldoc now states plainly that the render-root publish moved to RuntimeSetPositionState.cs, but the sticky-release relocation claim was false and is retracted; this disposition's coverage loss is the sticky release path, not silently absorbed elsewhere). - Stale `PhysicsEngine.Resolve`/`Resolve` doc citations in CellTransit.cs, PlayerMovementController.cs, and HeadlessSessionWorldProjection.cs are corrected to name the surviving canonical entry points by symbol (SetPosition, AdjustSetPosition/AdjustPosition, ResolveWithTransition) rather than fragile line numbers. Retires AP-1 and AD-1 in docs/architecture/retail-divergence-register.md: both rows described production zero-delta placement routing remaining on the legacy resolver pending the Slice 4B2/4B route cutover; that resolver no longer exists, so the condition each row tracked is now structurally false rather than merely narrowed. AP-145 (routed through the prior commit) and this commit's AP-1/AD-1 together bring the section counts to 101 AP / 47 AD active rows. Builds on the AP-145 fix (previous commit) — this commit's staged tree was independently rebuilt and its four suites independently rerun on top of that commit before this commit was created, in addition to the combined rebuild/rerun below. Full-solution build: 0 errors (21 pre-existing warnings, all unrelated). Suite results (combined tree): Core 4270/4271 passed (1 skip; the single DatSoundCacheTests concurrent-decode-dedup failure is a known load-sensitive race, confirmed passing standalone and unrelated to this change), Runtime 1176/1176, Headless 86/86, App 4132/4135 (3 skips). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e0f96a55bf |
fix(physics): C4 route 3 — portal placement authority (local player)
Removes a duplicate placement authority for local-player portal arrival. Portalling worked before this change and works after it — this is not a bug fix, EXCEPT that it found and fixed one dead-code production bug. THE PRODUCTION BUG: TryExecuteCanonicalPortalPlacement re-read the accepted destination at Place time, but TryBeginPortalReveal already consumes that slot at Aim time — so the arm was 100% dead code and every real portal Place refused with host-token-unavailable. Found only because we refused to accept 7 skipped tests instead of chasing the count to zero. RETAIL IS THE GENERIC PATH FOR THE THIRD ROUTE RUNNING: SmartBox::TeleportPlayer @0x00453910 = SetPositionSimple(dest, 1) with flags 0x1012, followed by PlayerPositionUpdated. BOTH INVERSIONS, WITH THEIR ANCHORS: unlike route 2, the leash IS armed here (ConstrainTo @0x0045418A) and velocity is zeroed (set_velocity @0x004541B4); unlike route 4b-3, the local teleport_hook runs AFTER placement (@0x004538AE). THE THREE-ROUND DEFECT CHAIN, HONESTLY: - Round 1 released the player at the pre-teleport position while the anim stream marched on — the contract wrongly assumed Place re-fires (process rule 1's third occurrence this campaign). - Round 2's fix inferred commit from a global PendingCount, which three non-committing paths also clear — making the SAME bug complete cleanly and silently. Strictly worse than round 1: round 1 at least tripped portal-complete-before-materialized. - Round 3 latches the commit where it actually happens (ReconcileAndAcknowledgePortal), keyed on reveal generation and teleport sequence, via TryConsumePortalCommit. Two of the three required regression tests landed and are sabotage-verified on both hosts (ParkedPlace_ForgottenByOrdinaryMergeDoesNotLatchAsCommitted / HeadlessPortalPrepareDestinationForgottenByOrdinaryMergeDoesNotLatchAsCommitted). The third (force-arm-takes-the-slot) was judged unnecessary on review: with the inference gone, PendingCount is only a "don't ask yet" guard at both gates, so a force operation occupying or vacating the slot no longer changes an input the commit decision reads — the case collapses into what the landed test already discriminates. THE B2/P3 RESOLUTION: both round-2 reviews were right about different branches of the same synchronous call. RuntimePlacementProjectionSubscription .OnPlacement acknowledges the FIFO head only when TryApply returns true; a Place whose portal authority went stale (transit ended/superseded while parked) used to return false, wedging every later entity's placement receipt behind it forever. Both sinks (RuntimePlacementPresentationSink, HeadlessRuntimePlacementProjectionSink) now acknowledge-and-ignore a stale-authority Place instead of refusing it. The regression test (RuntimePlacementPresentationSinkTests .PortalPlace_StaleTransitHostOrSequenceIsAcknowledgedAndIgnored) had been asserting the old, wrong `false` behaviour; it now asserts and sabotage-verifies the fix. Also lands: AP-144 (register discipline — the portal movement-event send reuses the stricter UsePositionFromServer gate where retail's SendMovementEvent is the looser autonomy_level != 0 test, diverging only at level 1, currently unreachable), AP-145 + issue #318 (the local-player collision-shadow presentation write bypasses its own publisher's ShadowObjects write via a direct cache .Set(), self-healing only once dedup diverges — filed, not fixed, pending a composition test), AD-42 deleted (its last citation retired by the canonical portal arm), AD-2 updated (the wait-cue's trigger predicate now covers a second cause), and two documentation corrections: the enter_world misattribution (both call sites are in SmartBox::HandleCreateObject, only one in the player branch — portal arrival is TeleportPlayer, not enter_world) and the stale "local player never reaches this path" comment on the generic-remote-render-pose write. Suite: 11,090 passed / 4 skipped / 0 failed. No new skips, nothing weakened. STILL OWED: the connected two-client gate, with ACDREAM_PROBE_LOCAL_TELEPORT=1, scored only if [local-tp] lines actually appear in the capture — and explicitly NOT scored as covering issue #318 (no composition test yet asserts PhysicsEngine.ShadowObjects directly). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9966b53174 |
feat(physics): C4 route 2 — ForcePosition through the canonical placement
A local-player ForcePosition had TWO independent writers for one accepted
packet: LocalForcePositionTransaction snapped the physics body
(PlayerMovementController.BlipPosition, a raw SnapToCell with no collision
resolve), while LiveEntityNetworkUpdateController's generic tail separately
wrote position/cell/rotation to the render WorldEntity from the raw wire and
rebucketed it. Two stores, one packet — the divergence class
|
||
|
|
529e0e9d88 |
feat(runtime): C3c - production placement cutover: both hosts on the residence conductors (routes 1+8)
Campaign P remaining-physics-divergence, placement cutover slice C3c
(docs/plans/2026-08-02-placement-cutover.md). Both production hosts now
register every initial Create through the residence + continuation-
executor + first-entry-conductor machinery (C0-C3b):
- Graphical (route 1): RegisterEntityWithInitialResidence at Create; the
shared RuntimeFirstEntryDriveController pumps both conductors from the
placement-receipt flow; MaterializeProjection and RebucketLiveEntity
are presentation-only while a residence is ACTIVE (ExecutorCompleted is
the presentation-binding receipt); post-residence entities take the
full legacy path including the prepare_to_enter_world clock edges.
PlayerModeController attaches presentation to the Runtime-published
controller; its legacy resolve/step-heights/host-construction path is
deleted; presentation-only rollback (retail has no entry-flow rollback).
- Headless (route 8): OnSpawned registers with residence when a drive
exists; content-less sessions keep the pre-flip direct registration;
SynchronizeLocalPlayer/CreateController/ApplySetupStepHeights deleted;
prepared-collision read failure is a typed AwaitingCollisionSource
retry; far remotes outside the service window complete celless.
- RuntimeLocalPlayerMovementState.Controller setter sealed internal; all
controller mutation flows through the publication lifecycle.
Fix slices landed within this cutover, each dual-gated:
- F1: live movement-stat/server-physics application routed through the
Runtime ownership seam (post-logout ingest crash on the retired
controller eliminated; RuntimeMovementSkillProjection deleted).
- F2: login activation wedge - collision-admission prefix gate factored
out of the seal (reentrant-commit RejectedAuthority), rearm generation
identity corrected, PlayerModeAutoEntry requires the Runtime-published
controller (world reveal can no longer seal unmaterialized).
- F3: landblock-prefix 0-sentinel replaced by explicit absent-id guards;
map-corner landblocks (grid row/col 0) fully legal through admission,
park/rearm/retire, quiescence, and outdoor shadow seeds.
- F5: local-player first-entry ground contact seeded by the shared
SpawnPlacementSettler (moved App->Core) at FinalizeActivation - the
retail first-gravity-frame touch (enter_world 0x00516170 carries no
seed); the legacy unconditional force-seed is overwritten by a real
floor-found contact; airborne spawns stay airborne; outbound contact
bit verified end-to-end. Fixes the standing-cast 'You can't do that
while in the air!' rejections.
- R1 (dual-review round): login constraint leash armed at the committed
placement (HandleReceivedPosition 0x00453FD0 analog); register rows
AD-61 (settle-timing compression now covering the local player) and
AD-42 (repointed off the deleted resolve split) in this commit;
residence-conversion owner API; wire-landblock guards; drive-pending
ledger in IsConverged; route attach/detach latch; executor-drain drift
model documented + source-pinned.
Gates: Runtime 1,003, App 4,039/3 skips, Headless 79, complete solution
10,816/0 failed/4 skips (Release, -m:1); connected lifecycle/reconnect
gate PASS (logs/connected-world-gate-20260802-175401; graceful exits,
world-visible, zero airborne rejections). The nine-stop soak remains red
for the pre-existing
|
||
|
|
5785a07b3e | feat(runtime): commit dormant SetPosition activation | ||
|
|
99f867f053 | feat(runtime): seal dormant SetPosition evaluations | ||
|
|
22651c823d | feat(runtime): publish dormant local physics ownership | ||
|
|
5a0f9868a6 | fix(physics): port retail slope landing stop | ||
|
|
2d611b2b01 |
fix(physics): #265 landing-bounce family - retail check_contact seed + velocity-free landing commit
Retail jump landings BOUNCE: the floor touch records both a contact plane (grounding) AND a collision normal (collided_with_environment), and handle_all_collisions reflects the unmodified impact velocity off it at 5% elasticity (v += -(v.n)(elasticity+1).n, DEFAULT_ELASTICITY 0.05 @0x007c6a7c). Our transition already recorded both facts; the bounce was suppressed by the AD-25 adaptation stack in the per-tick commit: a Velocity.Z<=0 landing gate (needed because the resolver glued ascending movers to the ground) plus a landing Velocity.Z=0 hand-zero whose stated purpose was making the reflect a no-op. Downhill glided instead of bouncing, flat-ground landings had no pop, and uphill jumps flapped between grounded/airborne against the animation machine. Three retail mechanisms replace the stack: - check_contact (0x0050f5b0) seeding in ResolveWithTransition: a body in CONTACT seeds the transition's contact only while v.contactPlane.N <= 0.0002; moving away seeds the last-known plane alone (get_object_info 0x00511cc0). Ascending jumps therefore run contact-free (ballistic, no glue) - the gate's reason-for-being is gone. The plane requirement is strict: Contact-without-plane is unrepresentable in retail. - SetPositionInternal-shaped commit (0x00515330, byte-read end-to-end, velocity-sign-FREE): contact purely from the transition's contact plane, HitGround on the airborne->walkable edge, HandleAllCollisions with unmodified impact velocity. Whole commit gated on Ok && candidateMoved (retail pc:283657 skips SetPositionInternal entirely when the candidate did not move) - a standing body's contact state is never re-derived, which is what keeps rest bit-stable (AD-41 updated). - Byte decodes: gate override state&0x800000=Sledding, zero branch state&0x20000=Inelastic, reflect strictly dot<0 - our port already had all three correct. Settle: real landings (>=0.25 m/s) bounce and decay geometrically; smaller impacts are consumed by retail's unconditional small-velocity zero, so standing never micro-bounces. Re-baselines documented in place: landing-survival pin measures decay post-settle; LiveCompare_Tick0/376 pin the new IsOnGround=false on zero-move ticks (captured true was the retired seed echo; tick 376's captured body carries an 11.8 m/s grounded velocity from the deleted get_state_velocity-overwrite era); de-overlap fixture now carries the plane real grounded bodies always have. New pins: LandingBounceSeedingTests (ascent no-seed, rest keeps contact, strict plane, slope 5% reversal + tangential preservation, Sledding override). Investigation + implementation record: docs/research/2026-07-30-landing-bounce-family.md. Complete Release suite: 10,031 passed / 5 skips / 0 failures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
06c76009f1 |
fix(physics): #265/#166 - stop zeroing grounded residual velocity, wire GroundNormal
Capture bisect (docs/research/2026-07-30-265-capture-bisect.md, mined from artifacts/matrix-session2-resolve.jsonl records 3415-3434) traced #265's lost roof slides / permanent landing freeze and #166's missing downhill sled to a pre-existing (2026-07-20, ten days before Campaign P - not a regression) mechanism in PlayerMovementController.cs's grounded quantum block: it hand-zeroed Velocity.X/Y to exactly zero every tick once OnWalkable whenever animation root motion drives the walk (the production graphical local-player path), discarding any residual horizontal momentum a fall left on the body before calc_friction (AP-7/AD-55, already correctly ported) or PhysicsBody. UpdatePhysicsInternal's Euler integrator ever got a chance to act on it. Two changes: 1. PhysicsEngine.cs now syncs body.GroundNormal (the vector calc_friction dots velocity against, per retail CPhysicsObj::calc_friction 0x0050ee70's `contact_plane.Normal` read) from the committed ContactPlane.Normal at the same commit point that already publishes ContactPlane. GroundNormal had zero production writers before this and silently defaulted to Vector3.UnitZ forever - even surviving velocity would have been tested against a fake flat-ground normal on any real slope. Core-level, so player, remote, ordinary, and projectile movers all benefit uniformly. 2. PlayerMovementController.cs's grounded block no longer reconstructs Velocity at all for the animation-root-motion case (only the headless/test-controller get_state_velocity fallback still does, unchanged). Root motion continues to fully own commanded locomotion; this only stops destroying whatever Velocity already holds, letting it compose with root motion through the same ResolveWithTransition sweep exactly as retail's CPhysicsObj::UpdatePositionInternal composes both channels. Symptom (a), the uphill-jump bounce, traces to a SEPARATE, byte-exact (re-verified against acclient_2013_pseudo_c.txt:282647-282760), already-closed retail mechanism (AD-25, PhysicsObjUpdate. HandleAllCollisions's shouldReflect gate) - confirmed orthogonal to this fix, not addressed here (see the research doc's as-fixed addendum §9.5). Issue265SteepSlopeCaptureBisectTests.cs gains a composed harness (ReplayRealRoofLandingComposed) mirroring PlayerMovementController.cs's per-tick composition against Core types only, proving: the old model reproduces the mined freeze exactly; the new model survives the landing and slides continuously (the real captured geometry glides at constant velocity per retail's own dot>=0.25 early-return - AP-7); a synthetic dot<0.25 case shows genuine exponential decay via calc_friction; and a synthetic uphill-bounce case proves the fix changes nothing about HandleAllCollisions's reflection decision. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9e17554ed4 |
fix(physics): P5 commit 3 - retire TS-35, close #167 (ConstraintManager leash)
PhysicsBody.IsFullyConstrained now reflects real ConstraintManager state (pushed every tick by the same per-tick pumps commit 2 wired), so jump_is_allowed's already-ported gate (WeenieError 0x47) actually fires while an object is rubber-banding hard against a server position correction, closing the last piece of #167. Housekeeping: - Delete register row TS-35 (retired: the write side is no longer stubbed). - Rewrite the stale doc comments on PhysicsBody.IsFullyConstrained, ConstraintManager (class + IsFullyConstrained), PositionManager.ConstrainTo, EntityPhysicsHost.PositionManager, and PlayerMovementController.PositionManager that described the leash as permanently unarmed/stubbed. - Close #167 in ISSUES.md citing the research doc and commits |
||
|
|
7719d25bc5 |
feat(physics): P5 commit 2 - arm the ConstraintManager leash on accepted positions (#167)
Wire ConstraintManager.ConstrainTo at every current acdream inbound-position acceptance seam, matching retail SmartBox::HandleReceivedPosition (0x00453fd0): - Remote (player + NPC): LiveEntityNetworkUpdateController arms right after the hard-teleport branch (remotePlacementRequired) returns - reaching that point already means MoveOrTeleport did NOT hard-place - anchored to the object's own live IPhysicsObjHost.Position. - Local player teleport: PlayerMovementController.SetPositionCore now runs UnConstrain (retail teleport_hook 0x00514ed0, previously a no-op because nothing armed the leash) then re-arms anchored to the just-snapped position, composing with the existing StopCompletelyAtPhysicsObjectBoundary velocity zero rather than duplicating it. CommitPreparedPosition mirrors the same pair for the deferred player-mode-entry commit path. - Local player ForcePosition: PlayerMovementController.BlipPosition arms with NO preceding UnConstrain (retail BlipPlayer/SetPositionSimple survives motion/velocity/stick, and the leash is no different). Push PhysicsBody.IsFullyConstrained from PositionManager.IsFullyConstrained at the SAME per-tick chokepoint each pump already runs AdjustOffset (PlayerMovementController.Update, RuntimeRemotePhysicsUpdater.Tick/TickHidden) so TS-35's read gate in jump_is_allowed sees live state instead of a stub that is never written. Tests: local-player arm/teardown/rearm/taper/jump-refusal (Runtime.Tests, PlayerMovementControllerTests), remote-tick IsFullyConstrained push (Runtime.Tests, RuntimePhysicsStateTests). Full Core/Runtime/App suites green with no regressions. |
||
|
|
bb7b899bfe |
fix(physics): TS-23 - plumb real PK/PKLite/Impenetrable mover flags
Campaign P Slice P3 item 3. The wire parse (CreateObject's PublicWeenieDesc._bitfield), the decode (EntityCollisionFlagsExt. FromPwdBitfield), the per-GUID storage (ClientObjectTable. PublicWeenieBitfield), and the exemption logic (CollisionExemption. ShouldSkip) all already existed and were already correct -- every mover-flags call site just fed a GUID-prefix IsPlayer heuristic instead of the real per-entity PK/PKLite/Impenetrable state (retail OBJECTINFO::init 0x0050cf30 state |= 0x80/0x800/0x1000). Port: - EntityCollisionFlagsExt.ToMoverState translates the decoded PWD bit-space into the ObjectInfoState bit-space FindObjCollisions actually reads -- two different numberings that must not be confused. Deliberately does not translate IsPlayer (every call site already derives that correctly from its own GUID heuristic per #184 Slice 2b). - EntityCollisionFlagsExt.ResolveMoverPvpState is the one shared ClientObjectTable-backed lookup (guid -> ObjectInfoState), replacing what would otherwise have been three separate inline copies across GameWindow/LivePresentationComposition/RemoteTeleportController. - Threaded as a new optional moverPvpState parameter through RuntimeRemotePhysicsUpdater.Tick/TickHidden and RuntimeOrdinaryPhysicsUpdater.TryBegin (default None preserves every pre-P3 caller unchanged), and as PlayerMovementController.OwnPvpFlags for the local player's own two resolve call sites. - TS-23 section 12b: PlayerWeenie.JumpStaminaCost's pk parameter now reads the real PlayerKillerStatus(0x86)/LastPkAttackTimestamp(0x91) pair against retail's 20-second recency window (pkStatus in {4, 0x40} && (timestamp + 20.0) >= now), replacing the P1 hardcoded false. RuntimeMovementSkillState/Snapshot and LiveSessionEventRouter.RecomputePvpStatus push both the PWD bitfield and the PlayerKillerStatus pair reactively, riding the SAME ClientObject event triggers RecomputeBurden already uses. - A conformance test caught a genuine precision bug in the first PK-timer clock choice: DateTimeOffset.UtcNow's Unix-epoch seconds (~1.7 billion) loses ~128 seconds of precision in a 32-bit float, silently swallowing the entire 20-second window. Switched to Environment.TickCount64 (small, monotonic magnitude) -- also the more retail-plausible basis, since LastPkAttackTimestamp is itself a wire PropertyFloat and retail's Timer::cur_time is almost certainly a process/session-relative counter for the same precision reason, not an absolute epoch. Non-PK invariant (the acceptance criterion): an entity with no ClientObjectTable row, or a row whose PublicWeenieBitfield is null or 0, resolves to ObjectInfoState.None -- a no-op OR into moverFlags, bit-identical to every pre-P3 caller's hardcoded value. A dedicated test drives two real ClientObjectTable rows through CollisionExemption.ShouldSkip and confirms PK-vs-PK collides while PK-vs-non-PK and non-PK-vs-non-PK both stay exempt (walk through). Register: TS-23 retired (both the collision-flags and PK-timer halves); the stale "M2 combat must land TS-23" phase-gate note removed. dotnet build + dotnet test (Core.Tests 4008/2 skip, Runtime.Tests 425/0, App.Tests 3968/3 skip, complete solution build) all green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
dae5b1ea68 |
fix(physics): TS-46 - seed the sweep from the Setup's own sphere list
Campaign P Slice P3 item 1. Retail CPhysicsObj::transition (0x00512dc0) seeds the collision sweep from CPartArray::GetSphere (the Setup's own <=2-sphere list, each origin+radius scaled by m_scale) via SPHEREPATH::init_sphere (0x0050c670) -- not from a symmetric two-scalar (radius, height) capsule reconstruction. The human Setup 0x02000001's authored spheres are (0,0,0.475) r=.48 and (0,0,1.350) r=.48; the old reconstruction from (0.48, 1.835) produced (0,0,0.48) + (0,0,1.355), a 5 mm head-center offset the TS-46 register row documented as a residual. Port: - SpherePath.InitPath gains a sphere-list overload (ImmutableArray< FlatCollisionSphere>, scale) sharing a new InitPathCore with the existing (radius, height) overload, which is now the degenerate 2-scalar case of the same code -- byte-for-byte unchanged, so every captured-fixture replay (CellarUpTrajectoryReplayTests, DoorBugTrajectoryReplayTests, CellarLipWedgeTests) keeps passing unmodified. - PhysicsEngine.ResolveWithTransition gains optional sphereList/ sphereScale parameters; empty/default preserves the legacy scalar path for every pre-existing caller. - LiveEntityMotionRuntimeController.GetSetupMoverShape is a new sibling of GetSetupCylinder (left untouched) that resolves the Setup's own sphere list plus Setup-derived step-up/step-down (CPartArray::GetStepUpHeight/GetStepDownHeight, 0x005180d0/0x005180f0, x ObjScale, 0.4 m fallback matching the pre-existing literal). - Threaded through PlayerMovementController (both resolve call sites, new SphereList property set by PlayerModeController.ApplyStepHeights and the Headless world projection), RuntimeRemotePhysicsUpdater (Tick + TickHidden), and RuntimeOrdinaryPhysicsUpdater.TryBegin. Remote/ordinary step heights are now Setup-derived instead of a hardcoded 0.4f literal. Projectile and camera-probe sweeps are untouched (already single-sphere-exact). - PlayerModeController.ApplyStepHeights also now applies the x ObjScale multiply to the player's own step heights (previously only the remote/ordinary paths did), closing an adjacent gap the P3 research flagged. Ts46SphereListConformanceTests proves the sphere-list overload sees the exact dat spheres (not the reconstruction), that the scalar overload is unchanged, and that ResolveWithTransition's sphereList parameter actually drives the sweep (a decoy-scalar control pair using a head-height obstacle sphere). Register: TS-46 retired (both residuals it named are closed); header count corrected to 40 active TS rows. dotnet build + dotnet test (Core.Tests 3991/2 skip, Runtime.Tests 425/0, App.Tests 3968/3 skip, complete solution build) all green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9355ddcec6 |
feat(physics): Campaign P P1 - stat-coupled movement (burden/stamina/vitae)
Ports the retail CACQualities/EncumbranceSystem/MovementSystem chain (named-retail decomp pc 256393/412901-414050/416169-416320/695958+) so PlayerWeenie's run rate, jump height, jump permission, and jump stamina cost are real functions of burden, current stamina, and vitae/skill enchantments instead of stubs. Core: - New EncumbranceSystem.cs (delegates to the already-verified BurdenMath formulas — one source of truth for the burden HUD and movement physics) and MovementSystem.cs (GetRunRate/GetJumpHeight/ JumpStaminaCost/GetJumpPower, decomp-cited; ACE cross-referenced where BN dropped the general-case arithmetic entirely). - PlayerWeenie rewritten as the CACQualities-shaped composition: CanJump gates on burden (<2.0 load, UN-8 — x87 polarity resolved by plausibility, Ghidra MCP unavailable this slice), JumpStaminaCost returns the real ceil((load+0.5)*power*8+2) cost and always affords it (matches decomp — retail's own function never refuses; "weak" jump comes entirely from the stamina==0 skill-zeroing gate inside InqRunRate/InqJumpVelocity, not a hard refusal), SetStamina wires a null="unknown, don't gate" sentinel preserving every pre-P1 test. - EnchantmentMath.GetMod gained an optional StatModType flag filter (GetSkillMod convenience wrapper) so the SAME vitae/family-stacking machinery already used for vital-max buffs now also answers "what's the vitae+skill-enchantment-adjusted Run/Jump skill" — reusing the M3 active-enchantment state, not a new engine. Runtime: - RuntimeCharacterState now stores the pre-EnchantSkill base run/jump skill and recomputes the adjusted value (vitae first, then matching Skill-flagged buffs, floor 0.5, truncate) on every base push AND on every Spellbook.EnchantmentsChanged notification — a vitae change alone moves the produced rate without a fresh PlayerDescription. - RuntimeMovementSkillState extended with Burden/CurrentStamina (RuntimeMovementSkillProjection.ApplyTo pushes both through the existing seam); LiveSessionEventRouter recomputes burden from the same Strength+aug-property+EncumbranceVal inputs the burden HUD already assembles (reacting to the same ClientObjectTable events) and pushes current stamina from LocalPlayerState vital updates. - Wires the previously dead-lettered ReportExhaustion() R3-W4 seam: LiveSessionRuntimeFactory's OnMovementStatsUpdated callback re- applies the current snapshot to the live controller and forces an immediate movement re-evaluation on any skill/burden/stamina change. Register: retires TS-5 (CanJump/JumpStaminaCost stubs) and AP-25 (no vitae in pushed skill). Adds AP-127 (two minor unmodeled retail bonus properties + the stamina-buff-adjusts-local-copy nuance, deliberately out of the bounded "run/jump query path only" scope) and UN-8 (the CanJump x87 polarity call, flagged for a future Ghidra MCP confirmation pass). Extends TS-23 (PlayerKillerStatus not parsed) to cover JumpStaminaCost's new pk parameter, hardcoded false pending P3. Full pseudocode + retail citations + the vitae/skill-level finding in docs/research/2026-07-30-stat-coupled-movement-pseudocode.md. Release suite: Core.Tests 3977/2 skips, Runtime.Tests 425/0 skips, App.Tests 3968/3 skips — all green. (One pre-existing, unrelated Debug- only flake in LandblockBuildOriginTests reproduces on the pre-P1 baseline and passes in Release; not touched here.) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b6547ff38c | feat(headless): hydrate isolated collision worlds | ||
|
|
38e83640d9 | feat(headless): complete deterministic bot command parity | ||
|
|
aa3f4a60f8 |
refactor(runtime): own local movement and outbound cadence
Move the canonical local movement controller, body/motion managers, object clock, movement wire data, and MTS/jump/AP sender into AcDream.Runtime. Replace process skill defaults with typed Runtime character options, make graphical and direct commands borrow one autorun owner, retain the construction-time PartArray seam, and include movement in terminal ownership convergence. Preserve the accepted pre-inbound movement/jump and post-inbound autonomous-position order while moving the exact packet/cadence fixtures into Runtime tests. Add graphical/direct parity, two-instance isolation, teardown, allocation, architecture, and divergence-path coverage. Co-authored-by: Codex <noreply@openai.com> |
Renamed from src/AcDream.App/Input/PlayerMovementController.cs (Browse further)