Commit graph

381 commits

Author SHA1 Message Date
Erik
db2889afda fix(physics): #116 shape-1 — Path-6 head-sphere direct Collided return
Per docs/research/2026-07-30-ts4-116-oracle-plan.md §2.3-§2.4: retail's
airborne (not-yet-Contact) BSPTREE::find_collisions dispatch, when the
FOOT sphere is completely clear but the HEAD sphere hits or near-misses,
does not defer through SetCollide/Adjusted (nor the steep-poly
slide-tangent shortcut) — it records the head polygon's normal directly
and hard-stops: pc:323824-323834 (0x0053a793/0x0053a7a4), independently
cross-checked against ACE BSPTree.cs:221-230 (`SetCollisionNormal` +
`return TransitionState.Collided;`), an exact structural match confirming
this isn't a BN misdecompile. BSPQuery.cs's Path 6 `hasSphere1` branch now
does the same: `collisions.SetCollisionNormal(worldNormal1); return
TransitionState.Collided;`, replacing the old steep-shortcut-or-deferred-
SetCollide handling. This mechanically retires one of TS-4's two
`SetSlidingNormal` write sites (sphere1's) ahead of TS-4's own item.

Added two permanent diagnostics gated on the existing
PhysicsDiagnostics.ProbeIndoorBspEnabled flag (`[path-dispatch]` at
FindCollisionsCore entry, `[path5-diag]` inside Path 5) to make future
BSPQuery dispatch tracing cheaper.

HONEST RESULT of the plan's own confirming instrumentation (re-run of
DoorBugTrajectoryReplayTests.Diagnostic_Tick22760_DumpEngineInternals):
this fix does NOT change the tick-22760 outcome (harness still cn=(0,0,1)
vs live cn=(0,+1,0)). The new dispatch-entry probes show the tick-22760
mover is GROUNDED (Contact set), so it never reaches Path 6 at all — it
dispatches Path 5 -> StepSphereDown (Path 3, both DoStepDown half-steps
fail) -> EdgeSlideAfterStepDownFailed -> SpherePath.PrecipiceSlide, whose
find_crossed_edge-false fallback returns Collided with NO collision-normal
write. A fresh byte-level read of retail's SPHEREPATH::precipice_slide
(pc:274316-274326, 0x0050cc80) confirms this is byte-exact retail
behavior (`if (eax == 0) { walkable = 0; return 2; }`, no
set_collision_normal call) — not a bug. The real tick-22760 divergence is
further upstream, most likely this test's simplified door registration
(BuildEngineWithDoorFixture) not placing the door's BSP where live retail
actually intersected it, or a walkable-polygon state-capture gap — see
the research doc's Addendum 2 for the full trace and open candidates.

#116 shape-1 is therefore NARROWED, not closed: the Path-6 fix is a real,
independent retail-faithfulness improvement; the tick-22760 acceptance
criterion is not met by it and needs further harness/geometry work before
any further code change.

Full AcDream.Core.Tests suite: 4059 passed / 2 skipped, no regressions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 12:36:26 +02:00
Erik
7e1be3def0 fix(physics): TransitionalInsert returns the real exhausted-retry state
Per the TS-4/#116 oracle plan (docs/research/2026-07-30-ts4-116-oracle-plan.md
§1.4, §4 item 1): TransitionalInsert's retry loop hardcoded
`return TransitionState.Slid;` when the attempt budget exhausted, despite
the comment's own claim of returning "whatever the last iteration said."
ACE's equivalent (Transition.cs:933, `return transitState;`) and retail's
(pc:273363, 0x0050b949, `return edi;`) both reuse one state variable
across the composite per-attempt call and return whatever it holds.

acdream's per-phase dispatch (env/building/object/other-cells/neg-poly/
step-down) is split across several locals instead of ACE's single
composite call, so `transitState` is now re-synced from whichever
phase-local variable most recently caused a retry `continue`, and the
final return uses that real value instead of the hardcoded constant.

Blast radius is zero: ValidateTransition's "not OK" branch treats
Collided/Adjusted/Slid identically, and every caller of TransitionalInsert
either feeds the result straight into ValidateTransition/
ValidatePlacementTransition (both `== OK` vs. not) or checks `== OK`
directly. Full AcDream.Core.Tests suite: 4059 passed / 2 skipped, no
change in pass count.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 12:36:05 +02:00
Erik
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 e0629145 /
  7719d25b.
- Add an "as-ported" addendum to
  docs/research/2026-07-30-constraint-leash-constants.md naming the actual
  current seam owners (the doc's own open question flagged this as
  implementer-verify-required post-J-slices).
- Update docs/plans/2026-07-29-physics-parity-campaign.md's P5 status and
  CLAUDE.md's Campaign P summary to reflect #167's closure (items #153/#72
  remain open in P5).

Verification: complete solution suite green - 9,978 tests, 5 skips, 0
failures across all 9 test projects (Core.Tests, Runtime.Tests, App.Tests,
Headless.Tests, Core.Net.Tests, Content.Tests, UI.Abstractions.Tests,
Bake.Tests, Cli.Tests).
2026-07-30 12:12:29 +02:00
Erik
e0629145ef feat(physics): P5 commit 1 - port ConstraintManager leash distance constants (#167)
Add ConstraintDistance (outdoor/indoor start=10/5, max=50/20), byte-decoded
from the matching retail binary (GetStartConstraintDistance 0x0050ebc0,
GetMaxConstraintDistance 0x0050ec10 - both x87-return functions BN elided).
Deliberately omits the vestigial player-vs-remote branch the disassembly
shows loads identical constants either way. Pins the ACE-inversion (ACE's
start mapping is outdoor 5/indoor 10, the opposite of the binary - the
binary wins). Adds a full-chain conformance test proving an armed,
over-strained leash actually blocks jump_is_allowed (0x47), not just the
bare stub-property regression already covered.

See docs/research/2026-07-30-constraint-leash-constants.md.
2026-07-30 11:54:35 +02:00
Erik
7a0f836af5 fix(physics): AP-129 review fix - port CanMoveInto/IsAllowedIn, stop failing closed
Campaign P Slice P4 Opus review verdict: FIX-FIRST. RestrictionObjPrevalenceInspectionTests
(commit 3b5e0992) found 103,766 of 729,888 installed EnvCells (1,293 landblocks -
the whole housing estate) carry a baked RestrictionObj. The AP-71 gate's
unconditional fail-closed default (CanMoveInto unmodeled) would have locked
every apartment/cottage/villa interior for every player, including its own
owner - a live regression, not the "inert in dev content" the original
register row assumed.

Ports ACCWeenieObject::CanMoveInto (0x0058da40, pc:407982-408056) and
RestrictionDB::IsAllowedIn (0x005ae8f0, pc:444493-444516) verbatim into
ObjectInfo.CheckEntryRestrictions:
- owner_iid == 0 or == mover's own guid -> admit (open/owner)
- no RestrictionDB (retail _db == 0, i.e. never authored or not yet
  received) -> admit
- present RestrictionDB -> IsAllowedIn: open-to-public flag, OR mover
  shares the house's allegiance monarch, OR mover's own guid is a
  guest-table member
- unresolved restriction object -> fails CLOSED, exactly retail's own
  fallback when GetObjectA can't resolve it (pc:704-716)

Wire feed (Core.Net):
- CreateObject.cs: HouseOwner (WeenieHeaderFlag 0x02000000), HouseRestrictions
  (0x04000000), and Monarch (0x40) PWD-tail fields were parsed-and-skipped;
  now captured. Also fixes the HouseRestrictions PHashTable header
  misconception: the wire is ONE packed u32 (low 24 bits = entry count),
  not a separate count(u16)+numBuckets(u16) pair - verified against
  Chorizite's RestrictionDB.generated.cs. The old skip's byte-count
  happened to match for realistic guest-list sizes, but a future
  numBuckets value >255 would have corrupted the parse; now correct
  regardless.
- GameEvents.cs/GameEventWiring.cs: new House_UpdateRestrictions (0x0248)
  parser + wiring - retail's live guest-list refresh, whole-unit replace.
  No-ops if the house object hasn't arrived via CreateObject yet.
- ClientObject/WeenieData/ClientObjectTable: HouseOwnerId, MonarchId,
  Restrictions (new HouseRestrictionRecord) fields + merge-preserving
  Ingest + targeted UpdateHouseRestrictions.

Physics wiring:
- PhysicsEngine gains an Objects (ClientObjectTable?) property, mirroring
  the existing DataCache pattern - acdream's GetObjectA equivalent, used
  ONLY by the entry-restriction gate.
- RuntimeEntityObjectLifetime wires Physics.Engine.Objects = Objects in
  all three constructors, right alongside the table's own construction -
  the same canonical table every other subsystem borrows from, never a
  second one. This is the production fix: without it the gate still fails
  closed on every restricted cell (unresolvable object), so the wiring is
  load-bearing, not cosmetic.

Register: AP-129 narrowed (not retired) to the genuine remaining residual -
House_UpdateRestrictions' Sequence byte isn't used for staleness/reordering
rejection (low-probability, self-correcting), and outdoor CLandCell
restriction (a separate DAT structure) remains unported and unaffected by
this fix.

Tests: 15 new/updated in Ap71EntryRestrictionGateTests.cs (resolved-unowned
admits, owner admits, present-list-excluded blocks, present-list-included
admits, open-to-public admits, shared-allegiance-monarch admits, unresolved
blocks via null and via an empty table, plus two new end-to-end
PhysicsEngine.Objects-wired scenarios); 2 new CreateObject parser tests +
2 new GameEventWiring tests for the wire feed.

AcDream.Core.Tests: 4049 passed, 2 skipped, 0 failed.
AcDream.Core.Net.Tests: 761 passed, 0 skipped, 0 failed.
Complete solution suite: 9,961 total, 9,956 passed, 5 skipped, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 11:36:11 +02:00
Erik
cc8d57a26e fix(physics): AP-10 - restore retail's 0.1m dry-corner water sink-in; wire WATER_CONTACT_TS
Campaign P Slice P4 item 2. TerrainSurface.SampleWaterDepth now returns 0.1
(was collapsed to 0) for a partially-water cell's dry corner, matching
retail's ObjCell.get_water_depth / calc_water_depth (via ACE's unambiguous
C# port). ValidateWalkable's formula was already byte-for-byte verbatim
(ACE ObjectInfo.ValidateWalkable line 124); only the constant was collapsed.

The old collapse's justification ("0.1 destabilizes the feet-exactly-on-plane
contact-touch check because dist > EPSILON skips SetContactPlane that tick")
is structurally true of retail too - traced and confirmed this slice: in ALL
THREE implementations (retail, ACE, acdream) a skipped touch-reassertion is
NOT a fall, because Contact/OnWalkable are STICKY -
PhysicsEngine.ResolveWithTransition's onGround computation ORs the fresh
per-call ContactPlaneValid with the seeded, persistent
PhysicsBody.TransientState.OnWalkable bit (itself written back by the
caller's own sticky TransientState). PhysicsEngine.SampleTerrainWalkable's
isWater = waterDepth >= 0.45f threshold means the restore does not flip the
dry corner's water classification (0.1 still < 0.45) - only the sink-in
depth changes. Full Core.Tests suite green (4038/2 skips, up from 4026)
proves the sticky-bit argument held in practice.

WATER_CONTACT_TS (TransientStateFlags.WaterContact, declared but never
written) is now mirrored alongside CONTACT_TS/ON_WALKABLE_TS at every commit
point that writes them: PhysicsObjUpdate.ApplySetPositionContact (projectiles
+ remote teleport), PhysicsObjUpdate.CommitSetPositionTransition (remote
teleport placement), and PhysicsEngine's per-resolve body-state commit (local
player + remote dead-reckoning + ordinary movers via ResolveWithTransition -
the actual SetPositionInternal-equivalent path). No signature changes needed:
body.ContactPlaneIsWater is already fresh by the time each function runs.

CollisionShadowVerifier audit: no change needed. It diffs graph-vs-flat BSP
traversal outcomes (ObjectInfo/CollisionInfo/SpherePath fields already
including ContactPlaneIsWater); it never touches PhysicsBody.TransientState,
and the water-depth constant is computed identically upstream of both
traversal modes, so it cannot introduce a new graph/flat divergence.

Filed #264 for the three items research explicitly left open (none block
this port): no confirmed retail consumer of WATER_CONTACT_TS was found (an
xref scan wasn't attempted - bitmask reads aren't text-greppable); the
CLandCell ENTIRELY_WATER ethereal/swim exemption from terrain collision was
not cross-checked; jump-in-water/swim-animation effects were not
investigated (out of physics/collision scope).

Conformance: Ap10WaterSemanticsTests covers SampleWaterDepth golden values
(NotWater/EntirelyWater/PartiallyWater wet+dry corners), the isWater
threshold non-flip, WaterContact mirroring in both PhysicsObjUpdate
functions, and two settle-to-rest end-to-end PhysicsEngine.ResolveWithTransition
scenarios (water: sinks exactly waterDepth below the plane and sets
WaterContact; dry: rests exactly on the plane and clears any stale
WaterContact bit).

Register: retired AP-10 (92 active AP rows, down from 93).

AcDream.Core.Tests: 4038 passed, 2 skipped, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 10:52:39 +02:00
Erik
d6c3f8657a fix(physics): AP-71 - port check_entry_restrictions at the head of indoor FindEnvCollisions
Campaign P Slice P4 item 1. Ports retail's CObjCell::check_entry_restrictions
(pc:308873-308912, 0x0052b6d0), called FIRST by CEnvCell::find_env_collisions
(pc:309576) before any BSP work, as ObjectInfo.CheckEntryRestrictions wired at
the top of the indoor branch of Transition.FindEnvCollisions.

Resolves the research doc's open question on restriction_obj's source: the
ACE cross-check (references/ACE/Source/ACE.DatLoader/FileTypes/EnvCell.cs:32,
66-67) plus an independent reflection probe of Chorizite.DatReaderWriter
2.1.7's own EnvCell.RestrictionObj field confirm it is a plain DAT-baked
uint32 gated by EnvCellFlags.HasRestrictionObj (0x8) - not a live wire
override. The BN pseudo-C's "count for an array alloc" read at the same
UnPack offset was the mis-attributed field-name collision
feedback_bn_decomp_field_names warned about.

CellPhysics.RestrictionObj is wired from envCell.RestrictionObj in BOTH the
dev/graph-fixture path (CacheCellStruct) and the production/prepared path
(CachePreparedCellStruct) - the latter already receives a live parsed
envCell for Position/EnvironmentId, so no bake-format change was needed.

The mover's own CanBypassMoveRestrictions (BF_ADMIN 0x100000 AND
BF_IMMUNE_CELL_RESTRICTIONS 0x400000, acclient.h:6452-6454) is decoded via
the same PWD-bitfield pipeline TS-23 established for PK/PKLite/Impenetrable
(EntityCollisionFlags -> ToMoverState -> ObjectInfoState moverFlags).

Remaining gap (filed as AP-129, replacing the retired AP-71 row): CanMoveInto
(house owner IID + guest/ban list) is unmodeled, so a genuinely restricted
cell fails CLOSED for everyone, not just intruders - matching retail's own
fallback when the restriction weenie can't be resolved (pc:704-716). Outdoor
CLandCell restriction (LandblockInfo.RestrictionTables, a separate DAT
structure) is explicitly out of scope for this gate.

Conformance: Ap71EntryRestrictionGateTests covers the pure gate logic
(NPC bypass, admin bypass, fail-closed, ordinary-cell no-op), the PWD-bitfield
two-bit AND decode, and three end-to-end Transition.FindEnvCollisions
scenarios proving zero behavior change for ordinary cells.

AcDream.Core.Tests: 4026 passed, 2 skipped, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 10:30:45 +02:00
Erik
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>
2026-07-30 09:52:55 +02:00
Erik
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>
2026-07-30 09:05:44 +02:00
Erik
26e0334af3 merge: Campaign P Slice P2 response-layer (TS-1 resolved, AP-7 ported, TS-4 stopped at escape valve)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

# Conflicts:
#	docs/architecture/retail-divergence-register.md
2026-07-30 08:33:18 +02:00
Erik
001e466d42 review(physics): P1 Opus review APPROVE - UN-8 retired by byte decode; PK-timer semantics recorded for P3
All seven review lenses pass. CanJump's polarity is upgraded from
plausibility to proof: raw bytes of 0x00591b50 show fld load / fcomp
[0x007c5e24 = 2.0f] / test ah,5 / jp -> return 0, i.e. return 1 iff
load < 2.0 with unordered refusing - exactly the shipped code, NaN edge
included. UN-8 deleted. CACQualities::JumpStaminaCost's pk flag decoded
for P3: PlayerKillerStatus in {4,0x40} AND PropertyFloat 0x91 + 20 s >=
now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 08:32:06 +02:00
Erik
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>
2026-07-30 08:18:06 +02:00
Erik
4f7e29f7cf fix(physics): AP-7 - port calc_friction's retail 0.25f threshold; retire AP-7, file AD-55
Campaign P Slice P2 step 3 (docs/research/2026-07-30-response-layer-edge-family-pseudocode.md
§1, §6 Step 5). The named retail decomp (CPhysicsObj::calc_friction,
pseudo-C:276694-276822, 0050ee70) independently re-confirms the 0.25f
threshold (derived twice, once per BN-rendered branch); the in-code claim
that "the decompile uses 0.0" traced to the older, unnamed FUN_0050f940
Ghidra chunk at a different address -- per CLAUDE.md the named decomp wins.

calc_friction now reads angle = dot(Velocity, GroundNormal); if (angle >=
0.25f) return; then unconditionally removes the normal-aligned velocity
component, then applies the existing (already-present but previously
unreachable) PhysicsState.Sledding-gated friction overrides. The BN-rendered
"two duplicated branches" around the state check is adopted as a single
linear function matching ACE's PhysicsObj.calc_friction shape -- the branch
split is most likely a BN decompiler artifact around one `if (state &
SLEDDING_PS)` block (ACE-derived, Ghidra-verify; low implementation risk
either way since ACE's reading is adopted regardless).

Why this doesn't repeat the reverted 2026-04-30 L.3c regression (naive 0.0
-> 0.25f bump dropped forward locomotion 3 -> 0.16 m/s): that test predates
the 2026-07-17 R6 "local player animation-owned grounded movement" landing.
PlayerMovementController (Runtime/Gameplay, out of this slice's scope) zeroes
Velocity.X/Y to exactly zero every tick before calc_friction runs whenever
animation root motion drives the walk, so friction has nothing horizontal
left to hammer on the production graphical local-player path. Pinned at the
PhysicsBody level (the only file this slice may touch) by
GroundedRootMotion_FrictionThreshold_DoesNotHammerLocomotionTests. The
headless/get_state_velocity path and remote/NPC movers still feed real
velocity into this function and remain the ones to watch if a similar
regression resurfaces there -- flagged in the retired AP-7 row for future
sessions working in Runtime/Gameplay.

Left an open, explicitly-flagged discrepancy: the raw decomp's Sledding
slope-flatness test computes cos(10 deg) (~0.984808) while ACE's port (and
acdream's prior dead code) compares GroundNormal.Z > 0.99999536f (~0.175 deg
from flat) -- physically different tests, neither confirmed this pass
(Ghidra MCP down). Kept 0.99999536f provisionally (least churn) and filed
AD-55 for just that constant rather than silently picking one.

Register: AP-7 retired with a corrected citation; AD-55 filed for the
cos(10 deg) question. Core.Tests: 3916 passed, 2 skipped (both pre-existing
and unrelated), 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 08:17:32 +02:00
Erik
325fee7cbb docs+test(physics): retire stale TS-1 row; file AD-53/AD-54 for its two acdream-only branches
Campaign P Slice P2 step 1 (docs/research/2026-07-30-response-layer-edge-family-pseudocode.md
§2, §6 Step 1/2). The TS-1 register row (retail-divergence-register.md:238)
described work that is already done: SpherePath.PrecipiceSlide,
Transition.CliffSlide, and Transition.EdgeSlideAfterStepDownFailed are real,
tested ports of retail's edge_slide -> precipice_slide/cliff_slide chain
(pc:274316, pc:272397, pc:273001-273090). Its cited :1254 line was stale
stepping-loop code the file moved past.

The one real remaining gap (the back-probe fallback skipping retail's
walkable_check_pos/localspace_sphere recache before its second
precipice_slide call, pc:274318-274326 / 0050b4e0-0050b507) needed no
production code change: a fresh read of SPHEREPATH::get_walkable_pos
(0050a8f0), cache_localspace_sphere (0050c9d0), and set_walkable_check_pos
(00509ce0) shows that machinery exists to re-project a sphere across
retail's PER-CELL local coordinate frames. acdream's SpherePath.WalkableVertices
and GlobalSphere are populated in UNIFIED WORLD SPACE at assignment time
(SetWalkable/SetWalkableTransformed, SetCheckPos/RestoreCheckPos), so both
operands BSPQuery.FindCrossedEdge compares are already commensurable --
retail's recache is a no-op correction under this architecture, and
FindCrossedEdge never reads a sphere radius, so retail's walkable_scale
radius correction has no acdream counterpart either. Documented in-code at
the back-probe site with full citations, and pinned with
EdgeSlideBackProbePrecipiceSlideTests: a walkable polygon rediscovered near
GlobalCurrCenter, tested against GlobalSphere[0] restored to the original
failed target, crosses the edge and slides -- it does not wedge into
Collided (and the inverse case, standing inside the polygon with no edge
crossed, correctly still returns Collided matching retail's own
precipice_slide on a false find_crossed_edge).

TS-1's other two flagged gaps are real acdream-only compensating branches,
not retail reads, and get their own rows rather than being silently
retired alongside it:
- AD-53: CliffSlide's three-source reference-normal fallback chain
  (LastWalkablePlane -> LastKnownContactPlane -> world-up) vs retail's
  direct last_known_contact_plane.N use. A fresh read of
  last_known_contact_plane's maintenance (pc:272659-272668) confirms retail
  overwrites it unconditionally every validate_transition pass, including
  with a steep plane -- so the fallback chain compensates for AP-4's
  incomplete OnWalkable bookkeeping, not a retail-matching read.
- AD-54: the walkable-steepness reroute to CliffSlide before PrecipiceSlide
  when the stored walkable polygon itself is steeper than FloorZ. Retail's
  raw edge_slide has no such branch; the permissive LandingZ acceptance
  that makes this state reachable IS retail-faithful (TS-4's own
  BSPTREE::find_collisions citation), but whether retail's outer
  transitional_insert retry loop absorbs the resulting COLLIDED_TS some
  other way is not yet independently verified -- flagged open in the row.

Physics test suite: 1836 passed, 1 skipped (D4, unrelated to this change).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 08:13:20 +02:00
Erik
f27ad9ee43 feat(core): adopt retail's full WeenieError code table
acdream carried 16 status codes, curated by hand out of the CMotionInterp
and MoveToManager decompilation passes. The other 362 were unnamed, which
made every one of them a cast site waiting to happen. This slice takes the
whole table: 372 values under 378 names.

The oracle set is finally complete. All six vendored reference repos were
empty when the 2026-07-29 enum campaign ran, which is why it deferred this
decision; they are re-cloned now, so ACE's WeenieError could be read
directly instead of leaning on the UtilityBelt catalog alone.

The two agree without a single conflict. ACE has 369 members, no internal
value collisions. The catalog has 372, shares all 369 ACE names, and
disagrees on none of their values. Its three extras -- IsNowOpenFellowship
(0x050B), IsNowClosedFellowship (0x050C), LockedFellowshipCannotRecruit
(0x0518) -- each turn up in ACE's separate WeenieErrorWithString enum with
a `_` marking the interpolated name, so the catalog is just the less-split
view of the same client enum. All three are adopted on agreement between
two oracles, not on one.

Retail cannot arbitrate any of this. acclient.h has no counterpart enum;
its charError (26) is character-creation only. Recorded, not guessed
around.

Six values keep two names. acdream's NotGrounded, CrouchInCombatStance,
SitInCombatStance, SleepInCombatStance, ChatEmoteOutsideNonCombat and
ActionDepthExceeded are each anchored to a retail decompilation site, where
ACE's names for those values are server-side coinages. Rather than pick,
both are declared, acdream's first so ToString() is untouched.

Behaviour is unchanged, and there is no way for it not to be: nothing in
the tree branches on a WeenieError member. MotionInterpreter's switch is on
a motion type and merely returns one of these; WeenieErrorText.For switches
on a raw uint; the chat translation table WeenieErrorMessages is keyed on
uint throughout, so naming a code does not make it render. The one site
that moved is RemoteTeleportHook, where the (WeenieError)0x3Cu cast becomes
the now-named WeenieError.ITeleported at the same value.

Register row AP-15 is narrowed rather than retired. Its code-catalog caveat
is superseded -- an unnamed code is no longer a way for it to bite -- but
the sentences are still ACE's doc comments rather than retail's
string_table.bin, and that part stands.

The enum moved out of MotionInterpreter.cs into its own file at the same
namespace. At 372 members it does not belong inside a physics class file.

Core tests 3903 passed / 2 skipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 07:33:27 +02:00
Erik
8ccaf72ae7 feat(core): adopt the retail members the equipment and physics enums were missing
With the two wrong enums corrected, the remaining wire-adjacent families diff
cleanly against the retail header - same values everywhere they overlap, just
fewer members on our side. This adopts the gaps.

EquipMask gains retail's eleven INVENTORY_LOC composite slot groups (acclient.h:
3193). The 32 primitive slots were already exact and stay pinned by EquipMaskTests;
what was missing were the groups the wire and the UI actually reason in - Armor,
Jewelry, ReadySlot, Weapon, WeaponReadySlot, the wrist/finger/sigil pairs, and
All. These are transcribed as literals, not derived, for the reason the previous
commit documents at length.

That transcription immediately earned itself. A type remark on EquipMask claimed
retail's CLOTHING_LOC composite "also sets bit 31, 0x80000000, which is not a
named INVENTORY_LOC primitive". It does not. CLOTHING_LOC is 0x080001FF: the nine
wear slots plus bit 27, which is the perfectly well-named Cloak slot. No
INVENTORY_LOC member touches bit 31 at all - ALL_LOC stops at bit 30. The remark
is corrected and a test now asserts the actual decomposition.

TransientStateFlags gains WaterContact (0x8) and CheckEthereal (0x100), the two
retail bits acdream's transition never declared. Neither is produced or consumed
yet; they are named so those slots cannot be quietly reused for an acdream-local
flag and then collide.

PhysicsStateFlags gains ReservedUnused1 (0x2) and ReservedUnused2 (0x2000), which
retail declares as UNUSED1_PS/UNNUSED2_PS. Same reasoning: reserved is a fact
worth recording.

AttackHeight gains Undef = 0. The three real heights are 1-based and were already
right; retail reserves 0 and the wire sends it, so it is now named instead of
arriving as an undefined cast. The numeric values are unchanged, so this renames
nothing at runtime.

Also checked and found already correct, so left alone: ObjectInfoState (matches
ObjectInfoEnum exactly, None being DEFAULT_OI), AttackType (every primitive plus
both composites - Unarmed 0x19 and MultiStrike 0x79E0 - land on retail's
literals), RadarBlipShape, RadarBehavior, MovementType, HoldKey, ParticleType, and
PhysicsDescriptionFlag. AttackType is worth calling out because the campaign's
extraction tooling reported it as a conflict; the tool reads one line per member
and had truncated a multi-line composite. The enum was fine.

RetailEnumConformanceTests grows tables for each of the above, each citing its
acclient.h line.

Core tests 3,785 -> 3,836. Full suite 9,701 passed / 5 skipped, no failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 01:27:57 +02:00
Erik
3f3401257c fix(headless): complete connected movement gate 2026-07-27 10:26:44 +02:00
Erik
b6547ff38c feat(headless): hydrate isolated collision worlds 2026-07-27 09:25:58 +02:00
Erik
4d095be286 fix(physics): traverse prepared indoor portal topology 2026-07-27 00:02:44 +02:00
Erik
cdee7a4b49 refactor(runtime): close simulation ownership
Move remote-motion construction, CreateObject vector initialization, final simulation-component retirement, and the combined J5 ownership ledger into Runtime. Delete App compatibility views and moved-state reconstruction while preserving the existing graphical projection and retail update order.
2026-07-26 15:53:31 +02:00
Erik
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>
2026-07-26 12:33:53 +02:00
Erik
82f8d4f82e refactor(physics): remove parsed collision graphs
Publish prepared GfxObj, Setup, CellStruct, and EnvCell collision records without retaining their parsed DAT BSP, polygon, vertex, or shape graphs. Strip temporary physics bundles at stable world commit, keep graph traversal only as an explicit test/tooling oracle, and report graph residency from actual retained fields.

Validated by 115 focused collision/streaming tests, a zero-warning Release build, and 8,413 passing Release tests with five pre-existing skips.

Co-authored-by: Codex <codex@openai.com>
2026-07-25 18:21:19 +02:00
Erik
068a06518d perf(physics): cut production traversal to flat assets
Make prepared flat BSP data authoritative for gameplay while retaining the parsed graph only as an exact sampled referee. Fail production publication when collision package data is genuinely absent, keep idempotent already-cached publication valid, and move cell membership, floor lookup, camera diagnostics, and live/static shape bounds onto the flat representation.

Validated by 8,402 Release tests, a strict dense-Arwic connected gate with 46,309/46,309 exact referee matches, and graceful shutdown.

Co-authored-by: Codex <codex@openai.com>
2026-07-25 17:00:04 +02:00
Erik
d9446030e6 feat(streaming): shadow-publish flat collision assets
Carry one immutable prepared collision closure with each accepted near-tier generation and install graph plus flat views through the same retained publication receipt. Apply the same strict package-only rule to live entities, add exact sampled graph-authoritative comparison artifacts and lifecycle counters, and prove cancellation, demotion, rehydrate, revisit, teardown, reconnect, and the nine-stop route with 14,064 zero-mismatch samples.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-25 16:38:54 +02:00
Erik
f7ff9f4eea feat(physics): add differential flat BSP traversal
Port every current containment, overlap, walkable, and six-path moving-collision query to immutable integer-indexed assets behind a graph-authoritative referee. Exact synthetic, installed-DAT, complete-resolver, and zero-allocation gates prove bit-identical behavior before connected dual publication.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-25 15:47:58 +02:00
Erik
9cd42417a8 feat(content): bake and read flat collision assets
Append strict collision/topology payloads to the existing prepared package so later physics cutover can drop parsed DAT graphs without adding a second mapping or changing traversal behavior. The full 2,232,170-key catalog is deterministic across worker counts, exact-byte aliased, corruption-isolated, and cancellation-safe.
2026-07-25 15:22:08 +02:00
Erik
d9300c7854 feat(physics): define deterministic flat collision assets
Add immutable indexed physics and containment BSP records, exact-bit polygon and Setup payloads, separated CellStruct/topology ownership, and iterative positive-before-negative flattening. Reject malformed graphs and ranges, and prove source identity over synthetic edge cases and installed retail DAT samples without cutting production traversal over.
2026-07-25 14:52:47 +02:00
Erik
16d182c2f0 perf(physics): reuse reset-complete transition scratch
Mirror retail's ten-deep LIFO transition lifetime, retain all query scratch with complete reset contracts, and remove Tier-0 enum boxing without changing collision decisions. Fresh and retained engines are bit-identical across the expanded oracle, while measured transition profiles now allocate 0 bytes per resolve.
2026-07-25 14:37:02 +02:00
Erik
98f1ac8934 perf(streaming): cursor publication across frame budgets 2026-07-24 19:10:18 +02:00
Erik
4f1c067a21 perf(physics): pool collision cell snapshots
Preserve the fixed per-cell collision walk across nested registry mutations while replacing the repeated List allocation with an explicitly owned pooled snapshot. Capture cardinality once, return storage on every exit, and pin live-list mutation behavior with a focused regression test.

Co-authored-by: Codex <codex@openai.com>
2026-07-23 06:12:34 +02:00
Erik
c79d0a49da refactor(streaming): complete landblock presentation cutover
Remove the legacy GameWindow apply path and make the concrete render, physics, and static publishers the only production owner graph. Serialize full-window retirement with shared-origin teleport and session boundaries so old coordinate-frame resources cannot survive into a new world or login.
2026-07-21 22:47:30 +02:00
Erik
3613d393e6 refactor(streaming): extract landblock physics publisher
Move streamed terrain/cell/building and static collision publication behind a focused update-thread owner. Preserve retail publication order while making replacement and retirement exact by logical landblock ownership, including current-cell rebasing and adjacent-seam isolation.

Co-authored-by: Codex <noreply@openai.com>
2026-07-21 21:13:22 +02:00
Erik
69a2ca0c6d refactor(world): extract live projection mechanics
Move appearance rebinding, collision construction, default-pose resolution, local shadow ownership, and exact leave-world presentation into focused owners. Preserve retail parent ordering with staged validation, committed recovery, recursive attached-subtree withdrawal, and retryable exact teardown across parent, pickup, position, unwield, and pose-loss edges.

Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-21 16:17:03 +02:00
Erik
fcb66198fc refactor(world): canonicalize live physics host ownership 2026-07-21 14:05:34 +02:00
Erik
833520253a fix(animation): harden presentation phase handoffs
Own every borrowed PartArray frame across callbacks, version presentation state across appearance rebinding, reject recursive phase consumption, and preserve static MotionDone when only a stale visual pose is discarded.
2026-07-21 10:01:25 +02:00
Erik
a2fd61684a fix(animation): retain trailing PartArray poses
Port CPartArray::UpdateParts minimum-count behavior: preserve the prior visual and rigid transforms for setup parts absent from a short authored AnimFrame, including the initial hydrated rest pose.

Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-21 09:41:15 +02:00
Erik
5acc3f01cf fix(interaction): close selection lifecycle review gaps
Bind queued actions and pending inventory requests to exact live incarnations, separate optimistic placement from authoritative responses, and serialize retail-style inventory ownership across UI surfaces.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-21 09:01:02 +02:00
Erik
f961d70023 feat(physics): port retail complete object frame pipeline
Restore the named-retail object update order across local, remote, static, projectile, animation, shadow, teleport, and effect lifetimes. Separate authoritative root commits from spatial rebucketing, preserve per-owner hook/FIFO ordering, and remove update-path allocations with exact lifecycle and residency gates.

Add deterministic conformance, adversarial lifetime, GUID-reuse, pending-cell, quaternion, timestamp, and allocation coverage. Release build is warning-free and all 6,446 tests pass with five intentional skips; retail, architecture, and adversarial reviews are clean.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-20 09:10:31 +02:00
Erik
31a0889f08 fix(animation): restore retail transition sign gates
Match the v11.4186 CSequence transition assembly and ACE cross-reference, including direction-specific pose gates and the strict physics epsilon boundary. Add conformance tests and correct the stale research interpretation.

Co-authored-by: Codex <codex@openai.com>
2026-07-19 21:44:12 +02:00
Erik
c5ab99081c fix(motion): restore retail object manager order
Process animation completion at the retail process_hooks boundary, then run targeting, movement, PartArray completion, and PositionManager in the named UpdateObjectInternal order for local, remote, hidden, and position-less animated objects. Retire TS-42 with deterministic conformance coverage.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
2026-07-19 21:40:14 +02:00
Erik
0f82a08f0a fix(items): use retail cylinder range for loot windows
Keep external-container visibility tied to the signed surface gap between the player and container physics cylinders, matching gmExternalContainerUI's authored UseRadius range watcher. This prevents a valid ViewContents response from being closed at ACE's natural approach endpoint.

Port Position::cylinder_distance and ACCWeenieObject::ObjectsInRange into Core with conformance coverage for 3-D separation, overlap, mode precedence, and inclusive boundaries.

Release build succeeds and all 5,895 tests pass with five intentional skips.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-17 20:32:34 +02:00
Erik
f1ba147ac5 perf(vfx): port retail particle visibility degradation
Resolve DAT-authored particle ranges from the hardware GfxObj, apply retail distance and completed-cell visibility gates, and preserve the exact finite/infinite off-view update semantics. This removes dense-world simulation work without shortening terrain, entity, fog, or streaming distance.

Publish doorway-clipped outdoor cells through a focused frame controller, retain effect cell identity for outdoor statics, reject hidden emitters before particle-slot scans, and offer an explicit opt-in Extended particle range.

Release build succeeds and all 5,857 tests pass with five intentional skips. Retail-conformance, architecture, and adversarial review cycles are clean; connected Aerlinthe visual/performance gate pending.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-17 15:27:36 +02:00
Erik
124e046976 fix(runtime): align portal and movement presentation
Port retail portal viewport projection and reveal behavior, preserve outbound combat style, drive remote and local grounded movement from authored CSequence root frames, and reuse the local prepared pose so animation hooks advance once.

User-verified portal, observer movement, combat stance, and short-tap locomotion gates. Release build passed with 5,767 tests and five intentional skips.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-17 08:48:27 +02:00
Erik
e95f55f25b fix(portal): synchronize destination presentation state 2026-07-16 21:17:13 +02:00
Erik
7b7ffcd278 fix(gameplay): reconcile wield ownership and target facing
Preserve PlayerDescription inventory/equipment ownership across authoritative manifest replacement, make weapon switching and combat/UI consumers read the same canonical object state, and carry the complete outbound player position frame across landblocks.

Route target-facing and mouse-look through the shared MovementManager and MotionInterpreter completion owner. Match retail input aggregation, toggle ordering, turn/sidestep remapping, per-axis hold keys, and synchronous movement publication without render-only heading state.

Initialize the live streaming origin from the first accepted canonical player Position, defer other projections until that origin exists, and retain logical entity identity through hydration.

Advance the project ledger from completed M2 to active M3, synchronize CLAUDE.md/AGENTS.md and durable memory, and record the next cast-lifecycle, spellbook/enchantment, and two-client portal gates.

Co-Authored-By: Codex <noreply@openai.com>
2026-07-15 08:19:23 +02:00
Erik
8d63e5c28a test(vfx): harden live missile and effect lifetimes
Add deterministic 96-owner lifecycle and renderer-resource stress gates plus an exact twelve-cycle recall/portal ownership gate. Prove zero retained records, projectiles, spatial buckets, script queues, particle/light owners, shadows, pending effects, and mesh references after churn, GUID reuse, deletion, and session reset.

Make logical teardown incarnation-specific and reentrancy-safe with lifetime epochs, atomic resource registration, generation-aware effect/teleport cleanup, projection mutation tokens, and failure-isolated visibility fan-out. Finish canonical spatial transactions before reporting observer failures and never discard superseded cleanup failures.

Synchronize architecture, roadmap, milestones, retail research, divergence bookkeeping, and durable memory. All three independent review tracks are clean; Release build and the full 5,454-pass/5-skip suite are green.

Co-Authored-By: Codex <noreply@openai.com>
2026-07-14 15:47:00 +02:00
Erik
1e98d81448 feat(vfx): port retail hidden and teleport presentation
Preserve canonical live-object ownership across Hidden transitions and remote teleport placement so effects, collision, streaming, and targeting remain synchronized.
2026-07-14 14:59:48 +02:00
Erik
a51ebc66e9 feat(physics): integrate live projectile runtime
Attach the retail projectile driver to canonical LiveEntityRecord ownership, sharing one PhysicsBody and full-cell identity with RemoteMotion regardless of creation order. Apply timestamp-gated state, vector, and position corrections in place, preserve active ordinary-body behavior when Missile clears, and keep renderer, effects, shadows, and spatial buckets synchronized across loaded/pending transitions.

Validate malformed packets before canonical timestamp mutation, validate adopted bodies from their current frame rather than stale CreateObject data, serialize late Setup resolution with streaming DAT reads, and preserve classification across clock anomalies. Retain shadow registrations through temporary leave-world residence while logical teardown remains generation-scoped.

Add 31 App lifecycle/adversarial tests plus Core shadow suspension coverage, and synchronize the retail pseudocode, architecture, milestones, roadmap, and durable physics memory.

Co-Authored-By: Codex <noreply@openai.com>
2026-07-14 12:51:42 +02:00
Erik
f02b995e4f feat(physics): port retail projectile integration
Add a pure Core projectile driver for retail clock quanta, final-state acceleration, 50-unit velocity clamping, world-space angular integration, full-3D AlignPath, and oriented Setup-local sphere sweeps. Preserve exact success versus set_frame-only failure semantics, independent Contact/OnWalkable state, and full-cell identity across all landblock directions.

Port OBJECTINFO::missile_ignore with explicit weenie/creature metadata, remove the invented transition-step cap, retain PathClipped without arming PerfectClip, and keep authoritative target identity optional. Add malformed-shape/clock, thin-wall, terrain, target-exclusion, frame-spike, failure-state, and boundary conformance coverage.

Retire TS-2 and synchronize the architecture, milestones, roadmap, research oracle, and durable physics memory.

Co-Authored-By: Codex <noreply@openai.com>
2026-07-14 11:43:11 +02:00
Erik
363e046112 feat(vfx): decode retail hooks and typed tables
Replace the incomplete package path with one DatCollection-backed compatibility seam for PhysicsScripts and Animations. Preserve CreateBlockingParticle's inherited payload and following cursor, route every production and audit consumer through the corrected loaders, and apply retail's post-UnPack StartTime ordering.

Add exact stored-order PhysicsScriptTable upper-threshold resolution, high-byte DID and embedded-ID validation, plus live effect profiles with Setup-to-PhysicsDesc precedence across top-level and attached entity lifetimes. Keep blocking execution deferred and narrow TS-11 accordingly.

Pin synthetic malformed/cursor/order fixtures, installed-DAT blocking and recall audits, high-index IDs, IEEE boundaries, profile teardown, and ordinary decoder parity; synchronize architecture, inventory, milestones, roadmap, research, and memory.

Co-Authored-By: Codex <noreply@openai.com>
2026-07-14 08:17:44 +02:00