feat(vfx): bind effects to live animated poses
This commit is contained in:
parent
96ddfdf175
commit
542dcfc384
41 changed files with 3246 additions and 741 deletions
|
|
@ -291,9 +291,30 @@ serial FIFO per owner: duplicate plays append, owners progress independently,
|
|||
not advance; a new play for an already-created cell-less object is dropped as in
|
||||
retail. Attached children advance through their eligible parent. Every effect
|
||||
resource uses the canonical, globally unique `WorldEntity.Id`; static
|
||||
allocators fail before their namespace can wrap. Live anchors are refreshed from the current
|
||||
`WorldEntity` before script dispatch; Step 5 extends that root pose to exact
|
||||
animated part transforms for emitters and dynamic lights.
|
||||
allocators fail before their namespace can wrap. `EntityEffectPoseRegistry`
|
||||
publishes the final root and indexed rigid animated-part transforms after animation
|
||||
and equipped-child composition. Animation hooks are captured during sequence
|
||||
advance and drained only after those poses are current; PhysicsScripts then run,
|
||||
attached emitters and object lights refresh, and particle simulation advances.
|
||||
World-released particles retain their birth positions and parent-local particles
|
||||
follow their current owner while in-world. Pending spatial projections skip
|
||||
particle updates and drawing exactly like retail's cell-less object gate without
|
||||
ending emitter state or identity; absolute creation timestamps remain unchanged,
|
||||
so elapsed particles/durations expire on re-entry without a backlog burst. Their
|
||||
anchors may still accept authoritative pose correction before that update.
|
||||
Missing emitter DAT records fail diagnostically
|
||||
without a synthesized effect. `LiveEntityLightController` keeps live light
|
||||
projection/re-entry outside `GameWindow`; `LightingHookSink` remains the Core
|
||||
hook and per-frame pose consumer. It also owns retail's Lighting-bit/
|
||||
`SetLightHook` latch so spatial withdrawal preserves logical state while a
|
||||
true state transition creates or destroys the Setup lights. Stable indexed
|
||||
poses exclude Setup visual scale and remain separate from drawable `MeshRefs`.
|
||||
Light registration follows final runtime visibility edges; equipped-child
|
||||
updates are parent-before-child with retained per-child buffers and cascade
|
||||
withdrawal when an ancestor pose disappears. A later pose publication drains
|
||||
the waiting attachment graph transitively, so A→B→C recovers B before C in the
|
||||
same parent-first pass. Recovery is edge-triggered by object/appearance/pose
|
||||
publication; permanent missing DAT or holding parts are never polled per frame.
|
||||
|
||||
The remaining aggregation is primarily `_playerController`'s player-specific
|
||||
movement plus the separate `WorldEntity`/animation/physics component types.
|
||||
|
|
|
|||
|
|
@ -171,7 +171,6 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
|
|||
| AP-64 | **Wield-reject rollback assumes `InventoryServerSaveFailed 0x00A0`** — an optimistic wield rolls back only if ACE emits `0x00A0` for a `GetAndWieldItem` rejection; otherwise corrected by the next authoritative message. Gate-verify via WireMCP. | `src/AcDream.Core.Net/GameEventWiring.cs` (0x00A0 handler) | If ACE uses a different reject opcode for wield, the optimistic state is left dangling until the next full update. | Rejected wield briefly shows the item as equipped in the doll — cosmetic flicker or stuck state if `0x00A0` is not the rejection path. | `InventoryServerSaveFailed 0x00A0`; WireMCP gate-verify |
|
||||
| AP-65 | **PickupEvent (0xF74A) no longer evicts the weenie from `ClientObjectTable`** — only `DeleteObject (0xF747)` evicts, matching the retail `object_table`-vs-`weenie_object_table` split. An item another player picks up near you (only ever gets `PickupEvent`, never `DeleteObject`) lingers as a data-only entry (`ContainerId 0`, not in any view) until teleport/relog `Clear()`. | `src/AcDream.Core.Net/ObjectTableWiring.cs` (EntityDeleted handler); `src/AcDream.Core.Net/WorldSession.cs` (PickupEvent branch) | The weenie entry for nearby pickups is a harmless data ghost — no UI shows it (no container, not wielded). Retail evicts it from `weenie_object_table` when the object fully leaves interest range via `DeleteObject`; acdream defers to teleport/relog clear. | Slight memory growth in long sessions if many world items are picked up by other players near you; item data ghosts cannot cause functional issues because no UI queries `ContainerId 0`. | `CACObjectMaint::DeleteObject` / `SmartBox::HandleDeleteObject`; ACE `Player_Inventory.cs TryDequipObjectWithNetworking` |
|
||||
| ~~AP-66~~ | **RETIRED 2026-07-13 — authored paperdoll empty-slot presentation.** The earlier “no silhouettes” conclusion inspected the ItemList elements' own media but missed `UIElement_ItemList::InternalCreateItem`, which clones a distinct `UIElement_UIItem` catalog prototype for each location. All 21 supported jewelry, weapon, ammo, shield, clothing, cloak, trinket, and armor lists now resolve their exact `ItemSlot_Empty` surface from live DAT; `PostInit` confirms non-armor lists remain visible while the nine armor lists toggle with Slots. | `src/AcDream.App/UI/Layout/PaperdollSlotBackgrounds.cs`; `ItemListCellTemplate.cs`; `PaperdollController.cs` | — | — | `gmPaperDollUI::GetLocationInfoFromElementID @ 0x004A37F0`; `PostInit @ 0x004A5360`; `UIElement_ItemList::InternalCreateItem @ 0x004E3570`; `LayoutDesc 0x21000037` |
|
||||
| AP-67 | Server-spawned weenie fixtures (lanterns, braziers, glowing items carrying `Setup.Lights`) register their lights at their SPAWN-frame world position via `RegisterOwnedLight` in `OnLiveEntitySpawnedLocked` (mirroring the dat-static path in `ApplyLoadedTerrainLocked`); the light does NOT follow the object if it later moves. Cell-scoped light presentation is removed by `LeaveWorldLiveEntityRuntimeComponents` and restored on world re-entry; logical teardown uses the same leave-world tail. | `src/AcDream.App/Rendering/GameWindow.cs` (`OnLiveEntitySpawnedLocked` weenie-light block; `LeaveWorldLiveEntityRuntimeComponents`; `TearDownLiveEntityRuntimeComponents`) | Closes the prior gap where ONLY dat-baked statics registered lights, so server-placed lanterns cast nothing; the overwhelming majority of light-bearing weenies (wall lanterns, braziers, candelabra) are stationary, so spawn-frame placement matches retail; retail's `add_dynamic_light` re-places the light from the object frame each frame | A moving light-bearing weenie (e.g. a torch-carrying NPC) leaves its light at the spawn position instead of following — rare; the faithful fix is to re-place on `OnLivePositionUpdated` | retail object-borne lights `insert_light` 0x0054d1b0 / `add_dynamic_light` 0x0054d420 (per-frame object frame) |
|
||||
| AP-68 | acdream keeps the 128 nearest-to-CAMERA point lights live (`MaxGlobalLights=128`, `BuildPointLightSnapshot`) and selects per cell CAMERA-INDEPENDENTLY (by the cell's own bounds), so a building interior stays lit at any distance within a town; retail keeps only the 40 nearest-to-PLAYER static lights (`Render::max_static_lights=0x28`, distance-sorted replace-farthest `insert_light`) and re-bakes a cell when its live light set changes, so distant interiors are baked dark and "light up" only as the player approaches and their torches enter the live 40. INTENTIONAL — acdream's always-lit interiors are the preferred behavior (no 1999-era light-budget pop-in); user-confirmed 2026-06-20. | `src/AcDream.Core/Lighting/LightManager.cs` (`MaxGlobalLights=128`, `BuildPointLightSnapshot`, `SelectForObject`) | The retail pop-in is a fixed-function light-budget artifact, not an intended aesthetic; revert to retail by clamping the global set to 40 + distance-to-player sort if ever desired | Distant town interiors are lit in acdream where retail's are dark until approached — a deliberate, user-preferred divergence | `Render::max_static_lights` 0x28; `insert_light` 0x0054d1b0 (distance-sorted, replace-farthest); bake re-trigger `SetStaticLightingVertexColors` cache `burnedInStaticLights != num_static_lights` |
|
||||
| AP-69 | On a landblock unload/reload, acdream retains the accepted live record and the same `WorldEntity`. Full unload moves non-persistent live projections to that landblock's pending bucket; Near→Far demotion drops only dat-static layers and leaves live projections resident. Neither path replays logical registration or reconstructs from a stale CreateObject. ACE will NOT re-broadcast objects whose guid remains in its per-player `KnownObjects` set, matching retail's retained object-table model. DIVERGENCE: acdream has NO retail 25 s / 384 m visibility cull — the retained record is pruned ONLY by an explicit server `DeleteObject` (0xF747) or session teardown. (#138) | `src/AcDream.App/World/LiveEntityRuntime.cs`; `src/AcDream.App/Streaming/GpuWorldState.cs`; `GameWindow.RehydrateServerEntitiesForLandblock`; `StreamingController` (`onLandblockLoaded`, Loaded + Promoted) | Stable logical identity is retail-faithful and prevents duplicated meshes, scripts, emitters, or stale-spawn teleports while remaining independent of an ACE re-broadcast | An object the server silently stopped tracking WITHOUT a `DeleteObject` (e.g. a creature that wandered out of PVS) can reappear at its last-known position, where retail's 25 s cull would have dropped it — a stale ghost until a real `DeleteObject` or the next session. Close it by porting holtburger's 25 s/384 m `ACE_DESTRUCTION_TIMEOUT` self-cull | `CPhysicsObj::change_cell` 0x00513390; `CPhysicsObj::leave_world` 0x005155A0; ACE `ObjectMaint.KnownObjects` never cleared on teleport (`Physics/Common/ObjectMaint.cs`, `WorldObjects/Player_Tracking.cs`); holtburger client 25 s cull `liveness.rs` `ACE_DESTRUCTION_TIMEOUT_SECS` |
|
||||
| AP-70 | `TeleportAnimSequencer.ComputeFadeAlpha` uses a smoothstep curve for all fade states; retail's `gmSmartBoxUI::UseTime` drives fade alpha through a 1024-entry `GetAnimLevel` lookup table whose contents are unrecovered | `src/AcDream.Core/World/TeleportAnimSequencer.cs` (`ComputeFadeAlpha`) | `GetAnimLevel` table address and contents not yet extracted via cdb; smoothstep is a perceptually-reasonable S-curve that closely approximates a typical gamma-corrected fade; the visual difference is imperceptible under normal teleport conditions | Fade timing differs subtly from retail — ramp-in or ramp-out may be slightly too fast/slow at the black-fade edges; retire by reading the `GetAnimLevel` table via cdb and replacing smoothstep with a direct 1024-entry lookup (spec §8) | `gmSmartBoxUI::UseTime` 0x004d6e30; `GetAnimLevel` 1024-entry table (address unrecovered — spec §8 cdb trace) |
|
||||
|
|
@ -228,9 +227,6 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
|
|||
| TS-7 | SkyObject `weather_enabled` gate not honored — weather-flagged sky objects (bit 0x04) always instantiate | `src/AcDream.Core/World/SkyDescLoader.cs:50` | No weather_enabled toggle exists yet; IsWeather flag parsed + documented as the gate to wire | Weather-only sky meshes (rain cylinders) appear where retail-with-weather-off suppresses them | `GameSky::MakeObject` 0x00506ee0, guard at decomp:268630 |
|
||||
| TS-8 | `MagicUpdateEnchantment` (0x02C2) records carry no StatMod — mid-session buffs don't move vital max until relog (**#7/#12**) | `src/AcDream.Core/Spells/Spellbook.cs:150` | The wire parser hasn't been extended to the full ~60-64 byte Enchantment payload; PlayerDescription's block IS parsed | Vitals HUD percent reads differently from retail for the whole session after any buff cast | `EnchantAttribute` 0x00594570; holtburger magic/types.rs |
|
||||
| TS-9 | MP3 (0x55) and MS-ADPCM (0x02) waves undecoded — affected sounds skipped; retail decoded both via winmm ACM | `src/AcDream.Core/Audio/WaveDecoder.cs:33` | Managed decoder (NAudio or similar) deferred; PCM covers the vast majority of ~3500 waves | Any MP3 (common for music-ish clips) or ADPCM cue plays as silence where retail plays it | winmm ACM path (r05 §2.1) |
|
||||
| TS-10 | Setup lights anchored at entity root — per-light Frames not transformed through the animated part chain | `src/AcDream.Core/Lighting/LightInfoLoader.cs:31` | Per-part world transforms aren't exposed to the lighting layer; awaiting animation hook integration | A carried torch glows from the character origin, not the hand, and doesn't track swing/idle animations | LightInfo.ViewSpaceLocation per-part Frame (r13 §1) |
|
||||
| TS-11 | Retail's complete inherited `CreateBlockingParticleHook` payload is decoded by the narrow `DatCollection`-backed compatibility reader, but `ParticleHookSink` deliberately no-ops it until the live emitter-binding owner can apply same-live-nonzero-logical-ID suppression | `src/AcDream.Core/Vfx/ParticleHookSink.cs` (`RetailCreateBlockingParticleHook` case); decoder `src/AcDream.Content/Vfx/RetailAnimationHookReader.cs` | Decoding and cursor alignment are now exact for both PhysicsScripts and Animations; executing it as an ordinary replacement before Step 5 would encode the wrong lifetime rule | Blocking-particle hooks still emit nothing until Step 5; ordinary hooks following them are no longer cursor-misaligned | `CreateBlockingParticleHook::Execute` 0x00526EF0; `ParticleManager::CreateBlockingParticleEmitter` 0x0051B8A0; r04 §6 |
|
||||
| TS-12 | Animated entities' emitters use rest-pose part transforms anchored at entity root; retail attaches to the live animated part (per-tick refresh deferred; statics fixed by C.1.5b/#56) | `src/AcDream.Core/Vfx/ParticleHookSink.cs:80` (+ :20) | The renderer doesn't expose per-part world transforms to VFX; root + precomputed matrices reproduce retail placement for everything that doesn't animate | Effects hooked to animated parts (swinging hand, nodding head) emit from the rest pose / float at spawn offsets instead of tracking motion | `ParticleEmitter::UpdateParticles` 0x0051d2d4 |
|
||||
| TS-14 | Setup `Flatten` ignores ParentIndex part hierarchy (treats every placement as root-local); still in production use (GameWindow hydration, SkyRenderer) | `src/AcDream.Core/Meshing/SetupMesh.cs:15` | Most Setups are flat single-level rigs where root-local equals composed; hierarchical composition deferred ("Phase 3") | Any Setup with genuinely nested parts renders them at wrong offsets — mis-assembled multi-part objects in the Flatten paths | retail Setup ParentIndex chain composition |
|
||||
| TS-15 | No distance-driven degrade (LOD): always close-detail slot 0; plus the **#47** static `Degrades[0]` swap for 34-part humanoids only (structural sentinel detector) | `src/AcDream.Core/Meshing/GfxObjDegradeResolver.cs:57` (+ `src/AcDream.App/Rendering/GameWindow.cs:2608`) | LOD plumbing doesn't exist; slot 0 is correct for player + nearby NPCs; #47 closed the visible low-detail-arms bug without porting UpdateViewerDistance | Distant objects render max-detail (perf + wrong visuals where far meshes intentionally differ/hide parts); a future 34-part non-humanoid matching the sentinel gets the wrong mesh swap | `CPhysicsPart::UpdateViewerDistance` 0x0050E030; ::Draw 0x0050D7A0; ::LoadGfxObjArray 0x0050DCF0 |
|
||||
| TS-16 | Click picking is Stage A only: ray-vs-fixed-radius spheres (0.7–1.0 m) + screen rect matched to the indicator; retail's per-polygon refine deferred (**#71**); rect-over-circle is a user-approved UX divergence | `src/AcDream.Core/Selection/WorldPicker.cs:199` | Stage B only needed if visual testing surfaces Stage-A over-picks; sphere/rect + cell-BSP occlusion adequate so far | Clicks near (not on) an entity still select it; fixed radii can mis-prioritize overlapping candidates vs retail's polygon-accurate test | `CPolygon::polygon_hits_ray` 0x0054c889 |
|
||||
|
|
@ -304,8 +300,8 @@ TS-23 (PK bits), TS-25 (stance in MoveToState), TS-17 (AttackConditions),
|
|||
and revisit AP-13 (ComputeDamage) + AP-24 (jump-charge constant via the
|
||||
0x0056ADE0 decompile). Emote work must land TS-24 (command-list packing).
|
||||
Membership Stage 2 must land TS-18 (BuildingCellId).
|
||||
The audio phase lands TS-9/TS-29; the live-pose animation-hook layer lands
|
||||
TS-10/TS-11/TS-12/TS-14.
|
||||
The audio phase lands TS-9/TS-29; the remaining live-pose animation work lands
|
||||
TS-14.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -500,7 +500,7 @@ behavior. Estimated 17–26 days focused work, 3–5 weeks calendar.
|
|||
- **Wave 4 inventory drag visuals implemented — corrective live visual gate pending.** `IconComposer` now preserves retail's separate underlay-free `m_pDragIcon` for cursor tracking across inventory, paperdoll, and toolbar bindings. Physical source cells keep their full icon in place and reveal authored ghost mesh `0x0600109A` from drag begin through every release/cancel path, with the persistent selection square above the mesh; shortcut aliases remain unghosted. `ItemList_DragOver` destination states are split exactly: contents-grid placement uses the green accept circle `0x060011F9`, while side-bag/main-pack container drop-in uses the green arrow `0x060011F7`. AP-47 retired. Research: `docs/research/2026-07-13-retail-item-drag-visuals-pseudocode.md`.
|
||||
- **Wave 4.6 item giving implemented 2026-07-13; starter-dungeon live gate pending.** SmartBox drag release now preserves the exact release coordinates, world-picks the target, and runs the existing retail `ItemHolder::AttemptPlaceIn3D` policy. NPC/creature targets send exact `GiveObjectRequest 0x00CD(target,item,amount)` bytes, selected partial stacks use the shared split quantity, and the object table waits for ACE's authoritative remove/stack-size response. `PlayerDescription.Options1` supplies the real `DragItemOnPlayerOpensSecureTrade` option. Issue #216; research: `docs/research/2026-07-13-retail-give-item-pseudocode.md`.
|
||||
- **M2 held-object parenting shipped and live-gated 2026-07-11.** The combat toggle now ports `GetDefaultCombatMode` over ordered equipped contents, so a bow requests Missile instead of the old hardcoded Melee. CreateObject preserves parent/placement/timestamp fields, `0xF749` ParentEvent is handled, and `EquippedChildRenderController` renders the weapon as a separate child composed from the animated hand part + holding frame + child placement frame. Live gate passed: bow selected missile stance, rendered in-hand, followed animation, unequipped cleanly, and melee remained correct. App Release builds with zero warnings; the full 4,765-pass / 5-skip suite is green. AP-111 is retired; research: `docs/research/2026-07-11-combat-default-and-parent-event-pseudocode.md`.
|
||||
- **Missile/portal VFX campaign Steps 0–4 implemented and independently reviewed 2026-07-14.** The retail oracle and packet fixtures are pinned, complete `PhysicsDesc` plus F754/F755 parsing and nine-channel gates are shipped, and App now has one canonical `LiveEntityRuntime` record per server-object incarnation. Logical register/unregister is separate from spatial rebucketing, so landblock churn and attached equipment retain identity without replaying renderer/script creation or reconstructing from stale spawn data. Canonical materialized and visible target/radar views are distinct; pickup/parent leave-world preserves owners while pausing root simulation; spawn publication is once per incarnation. `GpuWorldState` is spatial-only for live objects. Production PhysicsScript and Animation loading shares a narrow `DatCollection`-backed compatibility reader for retail's inherited blocking-particle payload, including mesh-side preloading; the proven concurrent-safe DatReaderWriter 2.1.7 read path avoids blocking update-thread effects behind streaming. Typed-table selection preserves DAT order and retail's first `intensity <= Mod` boundary, and live `PhysicsDesc` effect defaults replace rather than fall back to Setup. The Step 4 scheduler is now one serial FIFO per owner with duplicate stacking, catch-up dispatch, deterministic delayed `CallPES`, complete hook-router fan-out, update-frame clock publication, retail cell/Frozen eligibility, and structural rejection of malformed zero-time recursive DAT chains without rejecting valid timed weather loops. `EntityEffectController` retains pre-create F754/F755 in one mixed per-GUID FIFO, resolves local identity only through `LiveEntityRuntime`, replays once only after the canonical owner is fully ready and in-world, drops later plays while that existing owner is cell-less, routes attached-child updates through the eligible parent, and replaces/clears the live SoundTable on every PhysicsDesc application. Scripts, particles, lights, translucency, audio, and teardown all share canonical `WorldEntity.Id`; static allocators fail fast before namespace wrap. IA-7, AD-14, and TS-13 are retired; AD-32 now covers only the remaining non-effect, non-Parent pre-create packet families, AD-43 registers the corrupt-DAT zero-time-cycle safety boundary, and TS-11 remains Step 5's live logical-emitter suppression.
|
||||
- **Missile/portal VFX campaign Steps 0–5 implemented and independently reviewed 2026-07-14.** The retail oracle and packet fixtures are pinned, complete `PhysicsDesc` plus F754/F755 parsing and nine-channel gates are shipped, and App now has one canonical `LiveEntityRuntime` record per server-object incarnation. Logical register/unregister is separate from spatial rebucketing, so landblock churn and attached equipment retain identity without replaying renderer/script creation or reconstructing from stale spawn data. Canonical materialized and visible target/radar views are distinct; pickup/parent leave-world preserves owners while pausing root simulation; spawn publication is once per incarnation. `GpuWorldState` is spatial-only for live objects. Production PhysicsScript and Animation loading shares a narrow `DatCollection`-backed compatibility reader for retail's inherited blocking-particle payload, including mesh-side preloading; the proven concurrent-safe DatReaderWriter 2.1.7 read path avoids blocking update-thread effects behind streaming. Typed-table selection preserves DAT order and retail's first `intensity <= Mod` boundary, and live `PhysicsDesc` effect defaults replace rather than fall back to Setup. The Step 4 scheduler is now one serial FIFO per owner with duplicate stacking, catch-up dispatch, deterministic delayed `CallPES`, complete hook-router fan-out, update-frame clock publication, retail cell/Frozen eligibility, and structural rejection of malformed zero-time recursive DAT chains without rejecting valid timed weather loops. `EntityEffectController` retains pre-create F754/F755 in one mixed per-GUID FIFO, resolves local identity only through `LiveEntityRuntime`, replays once only after the canonical owner is fully ready and in-world, drops later plays while that existing owner is cell-less, routes attached-child updates through the eligible parent, and replaces/clears the live SoundTable on every PhysicsDesc application. Step 5 publishes current rigid root/indexed-part poses after animation and recursively composed held-child transforms, keeping render-only Setup scale out of particle and holding-location anchors; it then drains animation hooks, owner PhysicsScripts, moving emitters/lights, and particle simulation in fixed order. Normal/blocking/anonymous logical emitter identity is exact, including Stop retaining a blocking ID until final-particle retirement; moving and held Setup lights follow their current roots and share retail's PhysicsState Lighting/SetLight transition state; world-released versus parent-local particle behavior is preserved; missing emitter DAT produces an actionable diagnostic and no invented fallback. Drawable meshes no longer collapse stable Setup-part indices; nested attachments update parent-before-child, recursively withdraw on ancestor pose loss, and recover the complete descendant chain only on real publication edges. Loaded↔pending projection edges skip particle update/draw while retaining original absolute creation times and logical identity, and withdraw light presentation; re-entry evaluates elapsed state once without backlog emission or recreating an effect owner. Scripts, particles, lights, translucency, audio, and teardown all share canonical `WorldEntity.Id`; static allocators fail fast before namespace wrap. IA-7, AD-14, TS-10, TS-11, TS-12, TS-13, and AP-67 are retired; AD-32 now covers only the remaining non-effect, non-Parent pre-create packet families, and AD-43 registers the corrupt-DAT zero-time-cycle safety boundary.
|
||||
- **M2 local attack receive funnel implemented 2026-07-11; live gate pending.** Retail `ExecuteAttack` only sends the request; ACE chooses the concrete melee/missile action and returns it in a non-autonomous mt-0 `UpdateMotion`. The local branch now runs that state through the same constructor-defaulted `MoveToInterpretedState` funnel and 15-bit action-stamp gate as remotes, then applies sticky/long-jump tails. The old local-only direct `Commands[]` replay is deleted. Shared conversion lives in `InboundInterpretedMotionFactory`; research: `docs/research/2026-07-11-local-combat-motion-pseudocode.md`.
|
||||
- **M2 basic retail combat bar implemented 2026-07-11; corrective live visual gate pending.** Production mounts authored `gmCombatUI` LayoutDesc `0x21000073`, shows it only for Melee/Missile, and routes mouse plus keyboard through one `CombatAttackController`. Corrections include the authored wider centered dark-red child range (accepted IA-20), full-width left-to-right bright live attack-charge feedback, exact Speed-left/Power-right justification, silent control-only `AttackDone(ActionCancelled)`, target-frame Keep in View with manual orbit, and persistent corpse motion: the AP-80 velocity-only NPC adaptation can now replace only Ready/Walk/Run, never authoritative Dead/actions. `CombatTargetController` ports the selection-cleared AutoTarget consumer, so a selected creature's authoritative Dead motion clears it and selects the nearest eligible creature when enabled. The 2026-07-12 replacement-corpse correction unifies both multi-frame and static/reactive spawns behind retail's CreateObject lifecycle: apply the wire's Dead state while detached, then `MotionTableManager::HandleEnterWorld` strips Ready→Dead links before the first in-world tick; multiple corpses remaining fallen passed the live user gate that day. Follow-up #205 makes the toolbar read the final canonical selection after a reentrant Auto Target notice and restricts automatic candidates to hostile non-player monsters (intentional PK-edge divergence IA-19); that live gate also passed 2026-07-12. Research: `docs/research/2026-07-11-retail-combat-bar-pseudocode.md`, `docs/research/2026-07-11-combat-target-camera-pseudocode.md`, `docs/research/2026-07-12-death-and-auto-target-pseudocode.md`; AP-24/AP-95 retired, AP-80 narrowed, AP-110 narrowed, AP-112 records the remaining attack-start and exact trained-Recklessness seams.
|
||||
- **Retail client command families implemented 2026-07-13; corrective live gate pending.** One shared typed catalog now separates retail client actions from ACE administrator commands for both chat backends. Named-decomp ports cover recall/house/PK travel; age/birth; framerate, lock, version, location, corpse, and die confirmation; clear plus named/automatic UI layouts; AFK/consent; emotes; friends; squelch/filter/message types; and fill-components. App owns execution, Core owns authoritative friends/squelch state, Core.Net owns exact UIQueue/ControlQueue packets. Confirmation reuse now ports retail `DialogFactory` contexts, queue groups/priority, fresh DAT roots, property results, callbacks/close notices, and server abort handling; `/die`, gameplay request tuples, and guarded item-use prompts share its type-1 LayoutDesc `0x2100003C` presenter. The first live gate passed the family except for raw suicide-success code `0x004A` and the title-bar-only FPS presentation; the correction maps retail's text and mounts SmartBox element `0x10000047` with live two-decimal `FPS`/`DEG`. #L.6 is closed; TS-31/TS-47 are narrowed. Research: `docs/research/2026-07-13-retail-client-command-families-pseudocode.md`, `docs/research/2026-07-13-retail-dialog-factory-pseudocode.md`.
|
||||
|
|
|
|||
|
|
@ -471,7 +471,7 @@ include dungeons.
|
|||
`PlayerDescription.Options1` preserves retail's player secure-trade
|
||||
preference. See
|
||||
`docs/research/2026-07-13-retail-give-item-pseudocode.md` and issue #216.
|
||||
- **M2/M3 missile, effect, and portal presentation campaign (Steps 0–4
|
||||
- **M2/M3 missile, effect, and portal presentation campaign (Steps 0–5
|
||||
implemented and independently reviewed 2026-07-14)** — named-retail projectile and
|
||||
physics-script behavior is pinned in one oracle, the complete `PhysicsDesc`
|
||||
and F754/F755 wire surfaces are parsed with retail timestamp gates, and
|
||||
|
|
@ -495,7 +495,22 @@ include dungeons.
|
|||
retain Setup defaults. Concurrent-safe DAT reads stay off the streaming lock; malformed
|
||||
zero-time recursive chains fail diagnostically while valid timed weather loops
|
||||
continue. The
|
||||
remaining steps port live animated attachment poses, projectile motion and
|
||||
live-pose step now publishes exact root/indexed rigid-part transforms after
|
||||
animation and equipped-child composition, then drains animation hooks,
|
||||
PhysicsScripts, attached emitters/lights, and particle simulation in fixed
|
||||
frame order. World-released particles remain at their birth positions while
|
||||
parent-local particles follow; normal/blocking/anonymous logical emitter
|
||||
semantics are exact; pending projections skip particle updates/draw while
|
||||
retaining absolute creation times, the same emitter, and its logical ID, so
|
||||
re-entry expires elapsed state once without backlog emission. Moving Setup lights follow top-level and held-object
|
||||
roots and honor authoritative Lighting/SetLight transitions; missing emitter
|
||||
assets diagnose without a fabricated fallback. Stable Setup-part indices do
|
||||
not collapse around missing visuals, nested attachments update parent-first,
|
||||
and per-child pose buffers are reused. Rigid attachment/effect frames exclude
|
||||
visual Setup scale, attachment withdrawal cascades through descendants, and
|
||||
later pose publication recovers the complete descendant chain parent-first;
|
||||
light registration follows final projection visibility. AP-67, TS-10, TS-11, and TS-12 are
|
||||
retired. The remaining steps port projectile motion and
|
||||
sweeps, and Hidden/UnHide portal presentation.
|
||||
- **L.1c local attack receive path (implemented 2026-07-11; live gate pending)** —
|
||||
local non-autonomous mt-0 UpdateMotion now uses retail's wholesale interpreted
|
||||
|
|
|
|||
|
|
@ -740,10 +740,84 @@ Repository links:
|
|||
2. Logical entity creation/destruction is separate from spatial rebucketing.
|
||||
3. Server Position/Vector/State/Delete packets remain authoritative.
|
||||
4. No client-side impact, explosion, damage, or deletion is synthesized.
|
||||
5. Per-object frame ordering preserves retail: animation/timed object hooks,
|
||||
movement commit and child updates, particle tick, then PhysicsScript tick.
|
||||
A script-created emitter begins particle simulation on the following tick.
|
||||
5. Presentation frame ordering is fixed: advance animation/root motion,
|
||||
publish indexed parts, compose equipped children, drain animation hooks,
|
||||
tick owner PhysicsScripts, refresh attached emitters/lights, then advance
|
||||
particle simulation and draw.
|
||||
|
||||
Retail stages animation hooks inside `CSequence::update`, then executes them
|
||||
from `CPhysicsObj::process_hooks` before the new root is committed. The same
|
||||
update subsequently calls `CPhysicsObj::set_frame` (`0x00514090`), which runs
|
||||
`CPartArray::SetFrame` and `UpdateChildrenInternal`; that relocates the newly
|
||||
parented emitter/held child to the final pose before `ParticleManager` advances
|
||||
or anything draws. acdream's explicit publish-then-drain barrier preserves that
|
||||
observable ordering without exposing a half-committed pose to modern sinks.
|
||||
6. Hidden suppresses mesh/collision/interaction but does not destroy the effect
|
||||
owner.
|
||||
7. DAT assets determine models, colors, timing, and attachment behavior.
|
||||
8. Every ported branch receives a conformance test and a named-retail citation.
|
||||
|
||||
## 14. Step 5 implementation map (2026-07-14)
|
||||
|
||||
- `EntityEffectPoseRegistry` exposes current root and indexed part-local poses
|
||||
through `IEntityEffectPoseSource`. The indexed channel contains retail's
|
||||
rigid `CPhysicsPart` frames, not drawable mesh scale: `UpdateParts
|
||||
0x005190F0` multiplies only the animation origin by object scale, while
|
||||
`SetScaleInternal 0x00518A00` stores Setup `DefaultScale` separately on
|
||||
`gfxobj_scale`. Animated entities publish those rigid frames beside their
|
||||
visually scaled `MeshRef` composition; equipped children then publish
|
||||
`holdingFrame * parentPart * parentRoot` plus the child's own indexed part
|
||||
locals. Appearance replacement republishes immediately.
|
||||
- `AnimationHookFrameQueue` captures hooks during sequence advance and drains
|
||||
only after equipped-child composition. `AnimationDone` and `UseTime` remain
|
||||
paired with the sequencer even if its render pose vanished during teardown.
|
||||
- `ParticleHookSink` binds each live handle to local owner, exact part index
|
||||
(including root `-1`), complete offset Frame, logical emitter ID, and render
|
||||
pass. It applies the offset origin through `partLocal * rootWorld`; the
|
||||
retained offset quaternion remains intentionally unused per
|
||||
`Particle::Init 0x0051C930`.
|
||||
- Normal nonzero logical IDs replace, blocking nonzero IDs suppress while the
|
||||
old emitter remains live, and zero IDs create anonymously. Natural emitter
|
||||
retirement releases the logical ID. `StopParticleEmitter` retains the ID
|
||||
until that retirement, while destroy removes it immediately. Missing
|
||||
`ParticleEmitterInfo` DAT
|
||||
records log owner + DID and create nothing; the former synthesized generic
|
||||
cloud is removed.
|
||||
- World-released particles retain their emission origin while future emissions
|
||||
follow the refreshed anchor. Parent-local particles consume the refreshed
|
||||
anchor for existing particles, matching `Particle::Update 0x0051C290` and
|
||||
`ParticleEmitter::UpdateParticles 0x0051D180`.
|
||||
Loaded-to-pending spatial transitions skip particle update/draw under retail's
|
||||
cell-less `update_object 0x00515D10` gate while retaining the emitter,
|
||||
particles, original absolute creation times, and logical ID. Authoritative pose
|
||||
correction may still refresh the anchor. Re-entry evaluates elapsed particle
|
||||
age/emitter duration once and emits no absent-time backlog; timestamps are not
|
||||
shifted to freeze the effect.
|
||||
- Setup lights retain their complete local `LIGHTINFO` frame.
|
||||
`LiveEntityLightController` owns live registration/re-entry and every frame
|
||||
`LightingHookSink` composes it through the current owner root/cell. Held
|
||||
objects use the child root published after `CPhysicsObj::UpdateChild
|
||||
0x00512D50`; top-level roots follow their `WorldEntity`. PhysicsState's
|
||||
Lighting bit and `SetLightHook` both drive the same create/destroy state,
|
||||
matching `CPhysicsObj::set_state 0x00514DD0` and `set_lights 0x0050FCF0`.
|
||||
This retires AP-67 and TS-10.
|
||||
Light presentation follows `LiveEntityRuntime`'s final projection edge, so a
|
||||
loaded-to-loaded rebucket does not replay registration and a pending owner
|
||||
contributes no stale light. Attached lights wait for the composed-child-pose
|
||||
barrier before their first registration.
|
||||
- Setup-part indices are retained separately from drawable `MeshRefs`; a
|
||||
missing middle GfxObj cannot shift later hook/holding indices. Nested held
|
||||
objects update parent-before-child and compose through the parent's already
|
||||
published child root. The buffers are retained by the attachment owner and
|
||||
reused each frame.
|
||||
- Logical teardown removes queues, emitters, lights, and poses exactly once;
|
||||
rebucketing does not. Pose loss withdraws an attached projection and its
|
||||
descendants while retaining accepted relations for valid later recovery.
|
||||
A post-publish parent-first recovery drain realizes the complete descendant
|
||||
chain, preventing an A→B→C graph from stranding C after B returns. The drain
|
||||
is edge-triggered by object/appearance/pose publication, never a per-frame
|
||||
retry loop for a permanently absent Setup or holding part.
|
||||
Session reset clears captured hooks and live poses
|
||||
through normal owner teardown while retaining DAT-static/synthetic poses.
|
||||
TS-11 and TS-12 are retired with the emitter-binding and
|
||||
live-part mechanisms.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue