feat: port retail magic lifecycle and retained spell UI
Complete the retail cast-intent, target, component, enchantment, and busy-state paths; mount the DAT-authored spell bar, spellbook, component book, effects panels, and shared panel lifecycle; and add scoped input plus conformance coverage. Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
parent
7b7ffcd278
commit
07be994d97
84 changed files with 17822 additions and 1051 deletions
|
|
@ -5136,49 +5136,52 @@ responds as if the user clicked the item.
|
|||
|
||||
## #L.2 — Spellbook favorites panel
|
||||
|
||||
**Status:** OPEN
|
||||
**Status:** DONE 2026-07-15
|
||||
**Severity:** MEDIUM
|
||||
**Filed:** 2026-04-26 (deferred from Phase K)
|
||||
**Component:** ui / magic
|
||||
|
||||
**Description:** In `MagicCombat` scope, 1-9 should fire
|
||||
`UseSpellSlot_1..9` (distinct from hotbar). Requires a small UI to
|
||||
pin favorite spells + a spellbook tab nav. Cross-references issue
|
||||
#L.3 (combat-mode dispatch).
|
||||
**Resolution:** The authored `gmSpellcastingUI` page inside combat layout
|
||||
`0x21000073` now exposes all eight server-persisted favorite tabs, equipped
|
||||
caster endowment, spell selection/name, drag-remove/drop-add favorite edits,
|
||||
cast button, and `UseSpellSlot_1..9`. Cast intent goes through the single
|
||||
`SpellCastingController`; ACE remains authoritative for turning, motion,
|
||||
components, mana, fizzle, impact, and completion.
|
||||
The same slice also mounts the authored Helpful/Harmful effect indicators
|
||||
(`0x21000071`, buttons `0x100000F5/F6`) and routes their panel IDs 4/5 through
|
||||
the shared retail `gmPanelUI` lifecycle, so closed effect lists have an exact
|
||||
reopen path and replace the current main panel instead of becoming orphaned.
|
||||
|
||||
---
|
||||
|
||||
## #L.3 — Combat-mode tracking + scope-aware Insert/PgUp/Delete/End/PgDn dispatch
|
||||
|
||||
**Status:** OPEN
|
||||
**Status:** DONE 2026-07-15
|
||||
**Severity:** MEDIUM
|
||||
**Filed:** 2026-04-26 (deferred from Phase K)
|
||||
**Component:** input / combat
|
||||
|
||||
**Description:** Insert/PgUp/Delete/End/PgDn mean different things in
|
||||
melee / missile / magic combat modes (per retail keymap MeleeCombat /
|
||||
MissileCombat / MagicCombat blocks). Phase K has the bindings and the
|
||||
scope stack; what's missing: `CombatState.CurrentMode` field +
|
||||
listener for the server-side `SetCombatMode` packet (likely 0x0053 or
|
||||
similar — confirm against ACE source). When mode arrives, push the
|
||||
appropriate scope; when leaving combat, pop.
|
||||
**Resolution:** Every binding now persists its retail `InputScope`; schema 4
|
||||
migrates older files. `CombatState.CurrentMode` selects the Melee/Missile/Magic
|
||||
shadow scope, held actions release atomically on any scope/binding transition,
|
||||
and reentrant callbacks cannot replay a stale held-chord snapshot. Settings
|
||||
conflict checks and rebinds preserve the scoped identity.
|
||||
|
||||
---
|
||||
|
||||
## #L.4 — F-key panels: Allegiance / Fellowship / Skills / Attributes / World / SpellComponents
|
||||
|
||||
**Status:** OPEN
|
||||
**Status:** PARTIAL — magic/character/inventory surfaces shipped
|
||||
**Severity:** LOW
|
||||
**Filed:** 2026-04-26 (deferred from Phase K)
|
||||
**Component:** ui
|
||||
|
||||
**Description:** Retail F3-F6, F8-F12 toggle UI panels for various
|
||||
character data. Phase K has the bindings (`ToggleAllegiancePanel`,
|
||||
`ToggleFellowshipPanel`, `ToggleSpellbookPanel`,
|
||||
`ToggleSpellComponentsPanel`, `ToggleAttributesPanel`,
|
||||
`ToggleSkillsPanel`, `ToggleWorldPanel`, `ToggleInventoryPanel`); the
|
||||
panels themselves don't exist. Each is its own design feature.
|
||||
Inventory (F12) is the most-requested.
|
||||
**Current state:** Retained Inventory, Skills, Attributes, Spellbook, and
|
||||
SpellComponents panels now exist and their F-key actions route through the
|
||||
window manager. The spell/component book is the authored `0x21000034` tree,
|
||||
with learned-spell filters, exact DAT spell/component icons, component
|
||||
categories, selection, scrolling, and server-persisted desired amounts.
|
||||
Allegiance, Fellowship, and World remain separate open panel features.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -128,6 +128,23 @@ window registration, plugin mounts, cursor feedback, layout persistence, and the
|
|||
retained tick/draw/restore/dispose paths. Panel-specific construction must not
|
||||
move back into `GameWindow.OnLoad`.
|
||||
|
||||
Magic follows the same boundary. Core `Spellbook` is the one learned/favorite/
|
||||
desired/enchantment state projection; Core.Net owns exact manifest and live
|
||||
message parsing; App `SpellCastingController` owns validated cast intent; and
|
||||
the retained spell bar, spell/component book, and effects controllers only
|
||||
project that state and invoke supplied actions. ACE remains authoritative after
|
||||
the targeted/untargeted request for turning, animation, mana/components,
|
||||
fizzle, impact, damage, and completion.
|
||||
The shared LayoutDesc importer resolves inherited controller trees with retail
|
||||
`LayoutDesc::InqFullDesc`/`ElementDesc::Incorporate` child identity semantics;
|
||||
panel controllers never reconstruct missing inherited widgets by hand.
|
||||
`RetailPanelUiController` is the single retained owner of retail
|
||||
`gmPanelUI::RecvNotice_SetPanelVisibility` semantics: one active main-panel
|
||||
child, optional DAT-property-driven deferred restoration, and visibility
|
||||
reconciliation for persistence. Toolbar buttons and the authored Helpful/
|
||||
Harmful effects indicators both send panel IDs into this owner; neither owns a
|
||||
parallel window-lifecycle map.
|
||||
|
||||
---
|
||||
|
||||
## Project Structure (current + target)
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
|
|||
| AP-14 | Encumbrance multiplier is a rough piecewise-linear stand-in (1.0→50%, ~0.7@100%, 0.1@300%) for retail's exact curve | `src/AcDream.Core/Items/ItemInstance.cs:187` | Hand-fit segments capture the curve's shape for scaffolding | Client-side burden-scaled effects (speed prediction) differ from retail at most burden ratios when loaded | r06 §6 (retail encumbered multiplier curve) |
|
||||
| AP-15 | WeenieError translation table covers only ~30 common codes (from ACE enum docs, not retail string_table.bin); unknown codes render raw hex | `src/AcDream.Core/Chat/WeenieErrorMessages.cs:26` | Untranslated codes are rare, fall back losslessly, 30-second add when reported | Server messages outside the table show as raw hex instead of the retail sentence | retail string_table.bin; ACE WeenieError*.cs |
|
||||
| AP-16 | Point/spot lights selected per-object / per-cell as the **8 nearest reaching lights** (sphere-overlap, nearest-first) via `LightManager.SelectForObject`, capped at `MaxLightsPerObject=8`; called from `WbDrawDispatcher.ComputeEntityLightSet` (objects) and `EnvCellRenderer.GetCellLightSet` (cell shells). Retail's bake (`SetStaticLightingVertexColors`) sums ALL reaching static lights per vertex with no count cap. Retail's *hardware* path (`minimize_object_lighting` 0x0054d480) DOES cap at 8 per object, so the cap is faithful to retail's hardware path — not to its bake path. The `LightManager.Tick` UBO path survives for DIRECTIONAL (sun) lights only; `mesh_modern.vert`'s UBO loop skips point/spot entries (`posAndKind.w != 0 → continue`) — point lights reach the shader exclusively via the per-object SSBO (binding 5) | `src/AcDream.Core/Lighting/LightManager.cs:234` (`SelectForObject`); `MaxLightsPerObject` ~line 174; call sites `WbDrawDispatcher.ComputeEntityLightSet` + `EnvCellRenderer.GetCellLightSet` | Matches retail's hardware constraint (8 lights per object/cell); selection is nearest-sphere-overlap which faithfully allocates lights to the surfaces that actually see them | Surfaces reached by >8 point lights are dimmer than retail's uncapped bake — rare (a dungeon room has a handful of torches), but real; see AP-35 for the bake-vs-GPU-evaluate architecture difference | `minimize_object_lighting` 0x0054d480 (retail's 8-light hardware cap); `SetStaticLightingVertexColors` 0x0059cfe0 (retail's bake, no count cap) |
|
||||
| AP-17 | Spell metadata from third-party CSV (3,956 rows, bad rows silently skipped), not the portal.dat SpellTable; Family feeds stacking decisions | `src/AcDream.Core/Spells/SpellTable.cs:10` | The dat spell-table port (obfuscated/encrypted aspects) wasn't done; CSV closed #11 fast and unblocked #6 stacking | Any CSV↔dat drift (wrong Family, missing rows) silently produces wrong buff-stacking winners and wrong panel info | portal.dat SpellTable 0x0E00000E |
|
||||
| AP-17 | Descriptive spell metadata and Family still come from a third-party CSV (3,956 rows, bad rows silently skipped); retail DAT now supplies decrypted formulas, school, exact icons, level filters, and component preflight | `src/AcDream.Core/Spells/SpellTable.cs:10`; `src/AcDream.App/Spells/MagicRuntime.cs` | CSV originally closed #11 and unblocked #6 stacking; M3 moved every cast-critical formula/icon/level decision to portal.dat but has not yet replaced descriptive/Family metadata | CSV↔DAT drift can still produce wrong stacking winners or panel name/description/beneficial classification; component choice and level/icon presentation no longer drift here | portal.dat SpellTable 0x0E00000E |
|
||||
| ~~AP-18~~ | **RETIRED 2026-07-10 — faithful retail radar port.** Exact `RGBAColor_Radar*` floats were recovered from named static data and `gmRadarUI::GetBlipColor` was re-ported with `_blipColor` overrides plus portal/vendor/attackable-creature/admin/PK/PKLite/free-PK/fellowship precedence. The old implementation was not merely hue-tuned: it also had wrong portal/vendor colors and an incomplete dispatch matrix. | `src/AcDream.Core/Ui/RadarBlipColors.cs` + radar classification tests | — | — | `gmRadarUI::GetBlipColor` 0x004d76f0; static RGBA initializers at named decomp pc:1089736-1089804 |
|
||||
| AP-19 | `PortalSideEpsilon` 0.01 (≈1 cm) instead of retail F_EPSILON ≈ 0.0002 — a documented render-root-lag tolerance, NOT a retail constant. DO-NOT-RETRY: T2 (BR-4) tried the retail value; CornerFloodReplay refuted it | `src/AcDream.App/Rendering/PortalVisibilityBuilder.cs:49` | Retail's tight epsilon only works with eye-exact swept curr_cell tracking; our viewer cell lags the eye by up to ~1 cm at pressed corners. Tighten after the #108-membership family + cdstW near-clip pin land | A 1 cm misclassification band at portal planes can flood or cull a portal the eye hasn't crossed — one-frame leaks / grey flashes at knife-edge doorway/corner positions | F_EPSILON @0x007c8c70; `PView::InitCell` 0x005a4b70 |
|
||||
| AP-20 | Sub-pixel view-polygon vertex merge fixed at 1080p-reference NDC units (2/1080); retail merges at ~1 actual screen pixel | `src/AcDream.App/Rendering/PortalProjection.cs:179` | Unit approximation whose coarseness only strengthens convergence — the merge is the flood's fixpoint floor (replaced MaxReprocessPerCell=16) | At 4K+ a legitimately visible 1–2 px sliver aperture collapses to degenerate and rejects — a thin/distant doorway stops admitting its flood slightly earlier than retail | `Render::copy_view` 0x0054dfc0 |
|
||||
|
|
@ -210,7 +210,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
|
|||
| ~~AP-107~~ | **RETIRED 2026-07-11 (Wave 3.3 / #197)** — typed `OfferPrimaryClick` returns `NotActive`, `ConsumedSuccess`, or `ConsumedRejected`; every retained item surface plus radar/world offers active target mode before local selection/open/use fallback. Rejections are consumed and cannot drift selection. | `src/AcDream.App/UI/ItemInteractionController.cs`; inventory/paperdoll/toolbar/radar/world call sites | — | — | `UIElement_ItemList::HandleTargetedUseLeftClick @ 0x004E24D0` |
|
||||
| AP-108 | Paperdoll/AutoWield still omit full `AutoWieldIsLegal`/dual-wield rules, double-click examine/drag from the doll, body-part selection lighting, and retail's synchronous `" - cannot unwield the %s"` failure suffix (the current send seam reports rejection asynchronously). **Primary replacement retired from this row 2026-07-14:** inventory activation and paperdoll drops share the confirmed blocker transaction, preserve explicit slot intent, relocate an already-worn item to an explicitly selected compatible slot, and emit retail's successful move-to-backpack status. **Aetheria retired 2026-07-13.** | `src/AcDream.App/UI/Layout/PaperdollController.cs`; `src/AcDream.App/UI/AutoWieldController.cs` | Basic equip slots, Aetheria, and live doll work; primary weapon, incompatible shield, and mismatched ammo blockers sequence through server-confirmed dequip→wield in both peace and war | Remaining illegal/off-hand cases, asynchronous dequip rejection wording, doll examine/drag, and selection lighting still differ functionally | `CPlayerSystem::AutoWield @ 0x00560A60`; `ACCWeenieObject::BlocksUseOfShield @ 0x0055D3E0`; `gmPaperDollUI @ 0x004A3590..0x004A5F90` |
|
||||
| AP-109 | Character Titles page is inert and live displayed-title/luminance state is absent | `src/AcDream.App/UI/Layout/CharacterStatController.cs`; `CharacterSheetProvider.cs` | Attributes/skills core output is user-accepted | Titles cannot be selected/displayed and level-200 luminance fields are missing | `gmCharacterTitleUI @ 0x0049A610`; `gmStatManagementUI::UpdateExperience @ 0x004F0A70` |
|
||||
| AP-110 | Remaining retained gameplay panels and world HUD are absent: advanced-combat powerbar, spellbook/effects/favorite spell bars, residual social/examine/floating chat, quests/map/vitae/options/smartbox, vendor/trade/salvage/tinkering, and D.6 nameplates/floaters | D.5/D.6 roadmap; retained layout registration set | Basic `gmCombatUI` now covers the active M2 melee/missile loop; Wave 10 lands each remaining surface against authoritative state | Large portions of retail gameplay still have no production UI | Named `gm*UI::PostInit` methods and LayoutDesc catalog |
|
||||
| AP-110 | Remaining retained gameplay panels and world HUD are absent: advanced-combat powerbar, residual social/examine/floating chat, quests/map/vitae/options/smartbox, vendor/trade/salvage/tinkering, and D.6 nameplates/floaters | D.5/D.6 roadmap; retained layout registration set | Basic combat plus M3 spellbook/component/effects/favorite-spell surfaces now cover the active melee/missile/magic loops; later waves land each remaining surface against authoritative state | Large portions of retail gameplay still have no production UI outside the current combat/magic/inventory/character loops | Named `gm*UI::PostInit` methods and LayoutDesc catalog |
|
||||
| ~~AP-111~~ | **RETIRED 2026-07-11 (M2 held-object parenting)** — equipped hand items are no longer omitted from the render world. CreateObject now preserves Placement/Parent/position timestamp bootstrap; live `0xF749` ParentEvent is parsed with retail sequence freshness; a focused render controller resolves `Setup.HoldingLocations`, applies the child's placement frame, and recomposes the separate child entity after every parent animation tick. Pickup retains the weenie's visual metadata for a later wield. | `src/AcDream.Core.Net/Messages/{CreateObject,ParentEvent}.cs`; `src/AcDream.Core/Meshing/EquippedChildAttachment.cs`; `src/AcDream.App/Rendering/EquippedChildRenderController.cs` | — | — | `ClientCombatSystem::GetDefaultCombatMode @ 0x0056B310`; `SmartBox::HandleParentEvent @ 0x004535D0`; `CPhysicsObj::set_parent @ 0x00515A90`; `CPhysicsObj::UpdateChild @ 0x00512D50` |
|
||||
| AP-112 | The basic combat bar ports visibility, height selection, desired-power slider, exact 1.0/0.8-second charge, ready-stance gating, request/release, `MaybeStopCompletely`, server-response queueing, and auto-repeat, but still omits `StartAttackRequest`'s `FinishJump` call and exact trained-Recklessness visibility semantics (IA-20 keeps the dark range as the accepted baseline) | `src/AcDream.App/Combat/CombatAttackController.cs`; `src/AcDream.App/UI/Layout/CombatUiController.cs` | The shared player movement owner now performs retail's server-control-gated full stop and movement report before an attack build; the remaining seams require the jump owner and a distinct Recklessness treatment | Starting an attack while charging a jump may not finish that jump exactly when retail does; trained/untrained Recklessness presentation is identical | `ClientCombatSystem::StartAttackRequest @ 0x0056C040`; `CommandInterpreter::MaybeStopCompletely @ 0x006B3B90`; `gmCombatUI::ListenToElementMessage @ 0x004CC430` |
|
||||
| AP-113 | Invalid lifestone-command arguments display the local text `Usage: /lifestone`; retail definitely emits a local usage/error line but Binary Ninja misidentifies the referenced wide-string address, so its exact wording is not yet recovered | `src/AcDream.UI.Abstractions/Panels/Chat/ChatCommandRouter.cs`; `RetailClientCommandCatalog.cs` | The behavior boundary is exact (handled locally, no chat and no game action); only a low-impact diagnostic sentence differs | `/ls now` can show different wording/color from retail while still refusing the invalid request correctly | `ClientCommunicationSystem::DoLifestone @ 0x0056FC70` |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# acdream — strategic roadmap
|
||||
|
||||
**Status:** Living document. Updated 2026-07-15. **M3 active.** M2's connected demo loop is complete: melee and missile weapon switching, target-facing, combat animation and damage/death presentation, loot/inventory, stack operations, and giving an item to a world target have all passed user gates. The projectile/PhysicsScript/particle/portal foundation is implemented through its Step 9 automated hardening gate; single-client missile and protection visuals pass, while the final two-client portal-out/materialization observer gate remains open. The immediate work order is an F.4/L.1d retail-conformance audit against that shipped foundation, implementation of the remaining cast lifecycle, then F.5 spellbook and active-enchantment UI.
|
||||
**Status:** Living document. Updated 2026-07-15. **M3 active; automated implementation complete, visual gates pending.** M2's connected demo loop is complete. M3 now has the retail cast-intent/component/target lifecycle, exact live enchantment wire state, scoped Magic input, retained spell bar, spellbook, component book, positive/negative effects panels, authored effect-indicator buttons, and shared `gmPanelUI` switching over the Step 9 projectile/PhysicsScript/particle/portal foundation. The remaining M3 work is the connected single-client magic-UI/casting gate and final two-client portal-out/materialization observer gate.
|
||||
**Purpose:** One source of truth for where the project is and where it's going. Every observed defect or missing feature has a named phase that owns it; when something looks wrong in-game, look here to find the phase that'll address it. Implementation details live in per-phase specs under `docs/superpowers/specs/`, not in this file.
|
||||
|
||||
---
|
||||
|
|
@ -11,17 +11,19 @@
|
|||
turn/windup/release/projectile/impact lifecycle, self-cast a buff and see its
|
||||
active enchantment, then recall through the DAT-driven portal presentation.
|
||||
|
||||
**Next concrete work, in order:**
|
||||
**Current gate order:**
|
||||
|
||||
1. Reconcile current E.5 wire, M2/M3 projectile/VFX, motion, and enchantment
|
||||
state against named-retail F.4/L.1d; record only the proven remaining gaps.
|
||||
2. Port and conformance-test those cast-state and animation lifecycle gaps,
|
||||
including facing, release, interruption/fizzle, recoil, and authoritative
|
||||
completion.
|
||||
3. Ship F.5's retained spellbook and active-enchantment surfaces over the
|
||||
existing shared state/command seam.
|
||||
4. Run the pending two-client portal-out/materialization observer visual gate
|
||||
before M3 closes.
|
||||
1. **Done (automated):** named-retail F.4/L.1d reconciliation, full live
|
||||
enchantment messages, account/focus-aware component formula preflight,
|
||||
retail target eligibility, authoritative cast request/completion ownership.
|
||||
2. **Done (automated):** authored Magic combat page, eight favorite tabs,
|
||||
equipped caster endowment, scoped retail keys, spellbook/component book,
|
||||
positive/negative active-enchantment panels, Helpful/Harmful indicator
|
||||
buttons, and the retail one-active-main-panel lifecycle.
|
||||
3. **Pending user visual gate:** connected projectile/self-buff casts plus the
|
||||
complete retained magic UI interaction checklist.
|
||||
4. **Pending user visual gate:** two-client portal-out/materialization observer
|
||||
presentation before M3 closes.
|
||||
|
||||
Opportunistic UI/physics fixes are no longer the work-order driver. They enter
|
||||
the active slice only when they block this demo or represent a severe regression.
|
||||
|
|
@ -527,13 +529,14 @@ behavior. Estimated 17–26 days focused work, 3–5 weeks calendar.
|
|||
- **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 (live-gated 2026-07-15).** 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 (live-gated 2026-07-15).** 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.
|
||||
- **M3 cast lifecycle + retained magic UI implemented 2026-07-15; visual gates pending.** `SpellCastingController` ports retail's immediate targeted/untargeted request boundary, component preflight, target eligibility, movement stop, and shared UI-busy lifetime while leaving turn/cast/fizzle/impact outcomes authoritative to ACE. Formula preflight reads decrypted portal.dat formulas, the SCID↔WCID map, canonical account randomization, exact school-focus map, infused-magic properties, and scarab-only substitution. Complete live enchantment packets preserve spell/layer identity, all StatMod fields, client-clock normalization, and exact purge buckets. Authored retained surfaces now include the Magic page of `gmCombatUI`, all eight favorite tabs, equipped caster endowment, Magic-scoped retail keys, `gmSpellbookUI`, `gmSpellComponentUI`, both `gmEffectsUI` windows, and the floating Helpful/Harmful effect indicators. `RetailPanelUiController` ports the shared one-active-panel lifecycle so toolbar and indicator launchers converge on one owner. Exact DAT icons and server-persisted filters/desired components are retained. #L.2/#L.3 close and #L.4/AP-110 narrow. Research: `docs/research/2026-07-15-retail-magic-ui-and-casting-pseudocode.md`.
|
||||
- **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`.
|
||||
- **✓ SHIPPED — Character window** (`LayoutDesc 0x2100002E`, `CharacterStatController`, 2026-06-26, same branch). **Visually user-confirmed 2026-06-26 — Attributes tab reads as retail.** Three tabs, header (name/heritage/PK), large-gold level number (dat font, `largeDatFont` 18px), "Total Experience (XP):" + "XP for next level:" captions, 9-row attribute list (icons + right-aligned values + Health/Stamina/Mana vitals), click-to-select (top/bottom selection bars + footer State-B "{Attr}: {value}" / "Experience To Raise: Infinity!" + affordability-gated raise triangles), centered footer. User noted "still needs some polish for later" — deferred to Issue #158.
|
||||
- **✓ CORRECTIVE PORT — live character experience qualities** (2026-07-13, #217; connected gate passed). Added retail `PrivateUpdatePropertyInt64 (0x02CF)` so Total Experience, the level-progress meter, and Skills' Unassigned Experience refresh from the same authoritative server update; Total XP value alignment now matches the authored right-justified layout.
|
||||
- **✓ SHIPPED — D.5.4 — Client object/item data model (foundation).** Shipped 2026-06-18 (`b506f53`..`a33e897`, 11 commits). Renamed `ItemRepository`→`ClientObjectTable` / `ItemInstance`→`ClientObject`; broadened the table to hold EVERY server object (retail `weenie_object_table` shape). `CreateObject` is now the canonical merge-upsert (`ClientObjectTable.Ingest`, retail `SetWeenieDesc` semantics) via a new Core.Net `ObjectTableWiring` (off GameWindow); `DeleteObject` evicts; `PlayerDescription` is a membership manifest (`RecordMembership`); live container-membership index (`GetContents`, retail `object_inventory_table`). `_liveEntityInfoByGuid` retired (selection/describe resolve from the one table). Root fix: the old enrich-existing-only `EnrichItem` dropped `CreateObject`s for items with no `PlayerDescription` stub — live-Coldeve 4/6 hotbar slots blank; items are now created, not dropped. **Crux resolved:** retail is TWO tables (`object_table` + `weenie_object_table`), NOT one — acdream's `WorldEntity` (3D system) + `ClientObjectTable` (data/UI) split was already architecturally faithful; the fix was the ingestion path, not a table unification. 2671 tests green.
|
||||
- **Roadmap correction (2026-07-10):** the completion order is now the architecture-first campaign in `docs/superpowers/plans/2026-07-10-retail-ui-fidelity-completion.md`. Retail `gmToolbarUI` is object-only: preserve `ShortCutData.index_`, `objectID_`, and `spellID_`, but do not invent spell glyphs on this bar. `PlayerModule::favorite_spells_[8]` feeds separate spell bars.
|
||||
- **✓ D.5.3 — Toolbar selected-object display (issue #141) SHIPPED.** Exact health/player/pet/Free-PK policy, selected-item mana (`0x100001A2`), stack entry (`0x100001A3`), stack slider (`0x100001A4`), formatted count/name, shared split quantity, click-to-use, peace/war, health/name, and paperdoll self/upper-item selection all passed live by 2026-07-11. Exact raw `ShortCutData` preservation is already shipped in Wave 4.2; the eight `favorite_spells_` lists belong to the separate spellbook/spell-bar phase.
|
||||
- **☐ D.5.5+ — Core panels.** Inventory (`gmInventoryUI`/`gmBackpackUI`), equipment/paperdoll (`gmPaperDollUI`/`gm3DItemsUI` + the `UiViewport` 3D doll), vendor, trade, spellbook. Research drop done (`docs/research/2026-06-16-*`). Depends on **D.5.4** (data model) + the item-slot/list/icon spine (D.5.1/D.5.2) + the **window manager** (Plan 2: open/close/z-order/persist + faithful grip/dragbar drag-resize) + the drag-drop spine wired (`UiRoot` has the chain; the per-cell accept/drop hooks are still stubs in `UiField`). Also deferred from D.5.1: drag/reorder + the `AddShortcut`/`RemoveShortcut` mutate wire.
|
||||
- **☐ D.5.5+ — Remaining core panels.** Inventory, equipment/paperdoll, spellbook/component book, and active-effects surfaces are shipped through the retained window manager. Vendor, trade, salvage, tinkering, and the remaining social/world panels stay open. Research drop: `docs/research/2026-06-16-*`; current magic research: `docs/research/2026-07-15-retail-magic-ui-and-casting-pseudocode.md`.
|
||||
- **D.6 — HUD.** **Radar/compass IMPLEMENTED 2026-07-10; jump power bar IMPLEMENTED 2026-07-13; live-world visual gates pending.** The radar is retained retail `gmRadarUI` (`LayoutDesc 0x21000074`): a 120×140 circular face (`0x06004CC1`) with orbiting N/E/S/W tokens, a fixed bright-green player marker, and coordinate footer (`0x06004CC0`). Projection is `radarPixelRadius / radarRange` with 75 m outdoor / 25 m indoor ranges — no 1.18 factor or scrolling strip. The separate `gmFloatyPowerBarUI` (`LayoutDesc 0x21000072`) now appears only while jump is pending and clips the authored JumpMode fill left-to-right from the movement controller's shared 0–1 charge. Both live in `AcDream.App/UI` behind focused controllers; `GameWindow` supplies state delegates only. Remaining D.6: target name plate, damage floaters, and other world-space HUD elements through the raw `TextRenderer` path. See slice 06 §A.2, `docs/research/2026-07-10-retail-radar-pseudocode.md`, `docs/research/2026-07-13-retail-jump-powerbar-pseudocode.md`, and the named `gmRadarUI`/`gmPowerbarUI` decomp.
|
||||
- **✓ SHIPPED — D.7 — Cursor manager (user-confirmed 2026-07-11).** All reachable `ClientUISystem::UpdateCursorState @ 0x00564630` branches resolve through production DAT EnumIDMap table 6, with event-ordered global-default versus captured/hovered `MediaDescCursor` layering from `UIElementManager::CheckCursor @ 0x0045ABF0`. OS fallback remains only AP-72 and emits a visible diagnostic. The live gate covered world/object, combat, targeted-use, text, move/resize, and drag contexts; combat-indicator mouse clicks and the quick key share the same toggle command.
|
||||
- ~~**D.8 — Sound.**~~ **Superseded — shipped as Phase E.2** (`SoundTable`/`Sound` dat decode, OpenAL 16-voice engine, per-entity 3D positional audio via `AudioHookSink`). Entry kept here for history; see the shipped table.
|
||||
|
|
|
|||
|
|
@ -634,6 +634,18 @@ protection effects pass the single-client visual gate. M3 begins with a focused
|
|||
named-retail gap audit of F.4/L.1d—not a rewrite of this foundation—then fills
|
||||
the spellbook/active-enchantment UI and completes the two-client portal gate.
|
||||
|
||||
**Implementation update (2026-07-15):** The automated F.4/L.1d audit and port
|
||||
are complete. Cast requests now use retail component-formula selection,
|
||||
target compatibility, shared busy/completion ownership, and exact
|
||||
targeted/untargeted messages. Live enchantment add/remove/dispel/purge state is
|
||||
fully parsed with layer identity and client-clock normalization. The authored
|
||||
Magic combat page, eight favorite tabs, caster endowment, spellbook/component
|
||||
book, scoped keys, positive/negative effects windows, and the authored
|
||||
Helpful/Harmful indicators are mounted in the retained runtime. Main-panel
|
||||
visibility now flows through retail's one-active `gmPanelUI` owner. M3 remains
|
||||
active only for the connected single-client magic
|
||||
UI/casting visual gate and the final two-client portal observer gate.
|
||||
|
||||
**Phases to ship:**
|
||||
- **F.4** — Spell cast state machine (buffs + recalls first, projectile
|
||||
spells second).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,277 @@
|
|||
# Retail magic UI and casting pseudocode
|
||||
|
||||
This note is the implementation oracle for the M3 cast lifecycle and retained
|
||||
spell UI. Retail addresses refer to the September 2013 EoR named client.
|
||||
Wire layouts were cross-checked against ACE and the existing holtburger-derived
|
||||
`PlayerDescriptionParser`.
|
||||
|
||||
## Cast request ownership
|
||||
|
||||
Retail: `ClientMagicSystem::CastSpell` `0x00568040` and
|
||||
`ClientMagicSystem::FreeHandsAndCastSpell` `0x00566EF0`.
|
||||
|
||||
```text
|
||||
CastSpell(spellId, useSelectedTarget):
|
||||
spell = SpellTable[spellId]
|
||||
if components are required:
|
||||
formula = GetAppropriateSpellFormula(spell)
|
||||
if any required component is absent:
|
||||
display "You do not have all of this spell's components."
|
||||
return
|
||||
|
||||
if spell targets self:
|
||||
target = local player id (or untargeted when no SmartBox exists)
|
||||
FreeHandsAndCastSpell(spellId, target)
|
||||
return
|
||||
|
||||
if spell has no target type:
|
||||
MaybeStopCompletely()
|
||||
send CastUntargetedSpell(spellId)
|
||||
increment UI busy count
|
||||
return
|
||||
|
||||
target = selected object
|
||||
if target == 0:
|
||||
display "You must select a suitable target."
|
||||
return
|
||||
if the selected GUID no longer resolves to an object:
|
||||
return silently
|
||||
if ObjectCompatibleWithSpell(target, spellId):
|
||||
FreeHandsAndCastSpell(spellId, target)
|
||||
reselect target
|
||||
|
||||
FreeHandsAndCastSpell(spellId, target):
|
||||
MaybeStopCompletely()
|
||||
if target == 0: send CastUntargetedSpell(spellId)
|
||||
else: send CastTargetedSpell(target, spellId)
|
||||
increment UI busy count
|
||||
```
|
||||
|
||||
The request is sent immediately. ACE remains authoritative for turning,
|
||||
casting motion, component consumption, mana use, fizzle, impact, damage, and
|
||||
enchantment state. The client does not invent a parallel gameplay outcome.
|
||||
|
||||
### Formula selection and target compatibility
|
||||
|
||||
Retail: `ClientMagicSystem::GetAppropriateSpellFormula` `0x00567D50`,
|
||||
`CSpellBase::InqScarabOnlyFormula` `0x00597050`,
|
||||
`ACCWeenieObject::MagicPackIsOwned` `0x0058CB80`, and
|
||||
`ClientMagicSystem::ObjectCompatibleWithSpellTargetType` `0x00567230`.
|
||||
|
||||
```text
|
||||
GetAppropriateSpellFormula(spell):
|
||||
schoolFocusWcid = SchoolOfMagic2WCID(spell.school)
|
||||
infusedProperty = [War 0x129, Life 0x128, Item 0x127,
|
||||
Creature 0x126, Void 0x148][school]
|
||||
if infusedProperty > 0 OR a directly carried side-pack has schoolFocusWcid:
|
||||
retain every power component in formula order
|
||||
strongest = highest retained component power
|
||||
append prismatic taper SCID 0xBC:
|
||||
power 1 => 1; power 2 => 2; power 3/4/7 => 3;
|
||||
power 5/6/8/9/10 => 4
|
||||
return result
|
||||
return account-customized formula
|
||||
|
||||
ObjectCompatibleWithSpellTargetType(target, targetMask):
|
||||
special = targetMask & 0x8107
|
||||
reject self when special == 0
|
||||
reject stacks
|
||||
require (targetMask & target.type) != 0 OR special != 0
|
||||
require target.IsPlayer OR target.publicBitfield has BF_ATTACKABLE
|
||||
require target.petOwner == 0
|
||||
```
|
||||
|
||||
The final Player/Attackable and pet gates apply after both the ordinary type
|
||||
match and the special-mask bypass. Formula entries are SCIDs; inventory and
|
||||
favorite-component events use WCIDs, resolved through DualEnumIDMap
|
||||
`0x27000002`. The magic-school focus map is retail enum category `0x28`, key
|
||||
`0x10000001`.
|
||||
|
||||
## Authoritative enchantment events
|
||||
|
||||
Retail dispatchers:
|
||||
|
||||
- `CM_Magic::DispatchUI_UpdateEnchantment` `0x006A32F0`
|
||||
- `CM_Magic::DispatchUI_RemoveEnchantment` `0x006A2FB0`
|
||||
- `CM_Magic::DispatchUI_DispelEnchantment` `0x006A2F20`
|
||||
|
||||
```text
|
||||
0x02C2 UpdateEnchantment:
|
||||
unpack one Enchantment record
|
||||
|
||||
0x02C4 UpdateMultipleEnchantments:
|
||||
u32 count
|
||||
repeat count times: unpack Enchantment record
|
||||
|
||||
Enchantment record (60 or 64 bytes):
|
||||
u16 spellId
|
||||
u16 layer
|
||||
u16 spellCategory
|
||||
u16 hasSpellSetId
|
||||
u32 powerLevel
|
||||
f64 startTime
|
||||
f64 duration
|
||||
u32 casterGuid
|
||||
f32 degradeModifier
|
||||
f32 degradeLimit
|
||||
f64 lastDegraded
|
||||
u32 statModType
|
||||
u32 statModKey
|
||||
f32 statModValue
|
||||
if hasSpellSetId: u32 spellSetId
|
||||
|
||||
0x02C3 RemoveEnchantment:
|
||||
u16 spellId
|
||||
u16 layer
|
||||
|
||||
0x02C7 DispelEnchantment:
|
||||
u16 spellId
|
||||
u16 layer
|
||||
|
||||
0x02C5 / 0x02C8 Remove/DispelMultipleEnchantments:
|
||||
u32 count
|
||||
repeat count times: u16 spellId, u16 layer
|
||||
|
||||
0x02C6 PurgeEnchantments:
|
||||
no payload; clear the authoritative active set
|
||||
```
|
||||
|
||||
## Magic combat page (spell bar)
|
||||
|
||||
Retail: `gmSpellcastingUI::PostInit` `0x004C5D00`,
|
||||
`SpellCastSubMenu::Init` `0x004C5A90`, and `gmSpellcastingUI::Cast`
|
||||
`0x004C6050`.
|
||||
|
||||
The spell bar is the authored Magic page inside combat layout `0x21000073`.
|
||||
It is not an advanced-combat placeholder.
|
||||
|
||||
```text
|
||||
combat basic page 0x1000005C
|
||||
spellcasting page 0x10000061
|
||||
spellcasting content 0x100000A2
|
||||
spell name text 0x1000048B
|
||||
spellcasting background 0x100000A0
|
||||
cast button 0x100000B2
|
||||
endowment icon 0x100000B1
|
||||
|
||||
favorite tab buttons 0x100000A3..0x100000A9, 0x100005C2
|
||||
favorite list groups 0x100000AA..0x100000B0, 0x100005C3
|
||||
item list within each group 0x100000B6
|
||||
```
|
||||
|
||||
There are eight favorite tabs. Each list is authoritative character state.
|
||||
Selecting a spell updates its name and current spell. Cast invokes
|
||||
`ClientMagicSystem::CastSpell(selectedSpell, true)`. PageUp/PageDown and the
|
||||
retail spell input actions change tab/selection; the cast-current action casts
|
||||
the current selection. Favorite edits are sent to ACE.
|
||||
|
||||
Outbound character events (`CM_Character`):
|
||||
|
||||
```text
|
||||
0x01E3 AddSpellFavorite(spellId:u32, position:i32, tab:i32)
|
||||
0x01E4 RemoveSpellFavorite(spellId:u32, tab:i32)
|
||||
0x0286 SpellbookFilterEvent(filter:u32)
|
||||
0x0224 SetDesiredComponentLevel(componentDid:u32, amount:i32)
|
||||
```
|
||||
|
||||
## Spellbook and component book
|
||||
|
||||
Retail: `gmSpellbookUI::PostInit` `0x0048B2B0` and
|
||||
`gmSpellComponentUI::PostInit` `0x0048A190`.
|
||||
|
||||
Both pages are authored in layout `0x21000034` (300 by 600 pixels):
|
||||
|
||||
```text
|
||||
window root 0x100002A8
|
||||
spellbook tab 0x100002A9
|
||||
component tab 0x100002AA
|
||||
close button 0x100002AB
|
||||
spellbook page 0x100002AC (base 0x10000294 / 0x21000032)
|
||||
component page 0x100002AD (base 0x100002A7 / 0x21000033)
|
||||
|
||||
spell list 0x10000295
|
||||
school buttons 0x10000298..0x1000029B, 0x100005C0
|
||||
level buttons 1..7 0x1000029C..0x100002A2
|
||||
level button 8 0x1000054E
|
||||
|
||||
component item list 0x10000464
|
||||
component scrollbar 0x10000465
|
||||
```
|
||||
|
||||
Spellbook rows come only from the authoritative learned-spell set. Retail
|
||||
sorts by `CSpellBase::_display_order`; school and level buttons apply the
|
||||
server-persisted filter bitfield. Component rows use the installed component
|
||||
table and the server-persisted desired purchase amounts.
|
||||
|
||||
## Active enchantment panels
|
||||
|
||||
Retail: `gmEffectsUI::PostInit` `0x004B7560` and
|
||||
`gmEffectsUI::RebuildList` `0x004B8350`.
|
||||
|
||||
```text
|
||||
layout 0x2100001B
|
||||
positive root 0x1000011F (inherits 0x10000122)
|
||||
negative root 0x10000121 (inherits 0x10000122)
|
||||
shared effects template 0x10000122
|
||||
effect list 0x10000123
|
||||
information text 0x10000126
|
||||
effect UI type property 0x1000000C
|
||||
```
|
||||
|
||||
The `0x10000184`/`0x10000185` values used by `gmPanelUI` are panel-slot
|
||||
identities, not roots in effects LayoutDesc `0x2100001B`. The two authored
|
||||
effects roots carry their title/close overrides and inherit the list,
|
||||
scrollbar, information area, and remaining chrome from `0x10000122`.
|
||||
|
||||
Retail `gmPanelUI::SetupChildren` (`0x004BC9E0`) registers those children as
|
||||
panel IDs 4 and 5. `RecvNotice_SetPanelVisibility` (`0x004BC6F0`) owns one
|
||||
active child: showing either effect panel hides the previous main panel, while
|
||||
closing the active child hides the shared host. DAT bool property `0x10000049`
|
||||
selects the exceptional save-and-restore-previous behavior. The resolved
|
||||
Helpful/positive root sets it, so closing Helpful restores the preceding
|
||||
ordinary panel. Harmful/negative does not set it and leaves the host empty.
|
||||
|
||||
The reopen path is not the main toolbar. Retail
|
||||
`gmUIElement_EffectsIndicator::PostInit` (`0x004E6930`) reads effect type 1 or
|
||||
2 from property `0x1000000C`, and `Update` (`0x004E6A50`) ghosts the button
|
||||
when no matching enchantment exists. The floating indicators layout is
|
||||
`0x21000071`; Helpful button `0x100000F5` opens panel 4 and Harmful button
|
||||
`0x100000F6` opens panel 5. The docked equivalent is layout `0x21000015`.
|
||||
|
||||
Retail `LayoutDesc::InqFullDesc` (`0x0069A520`) resolves the base first and
|
||||
then calls `ElementDesc::Incorporate` (`0x0069B5A0`). Child tables are merged
|
||||
by element identity: base-only children remain, same-identity children are
|
||||
incorporated recursively, and derived-only children are appended with their
|
||||
read order shifted by the retained base-only count.
|
||||
|
||||
When visible, each panel rebuilds from the authoritative active-enchantment
|
||||
snapshot and refreshes each token's duration at most once per second.
|
||||
`CEnchantmentRegistry::GetEnchantmentsInEffect` (`0x00594540`) merges only the
|
||||
multiplicative and additive registries, so Vitae and cooldown records neither
|
||||
appear in these panels nor enable an indicator. Each incoming entry is dueled
|
||||
against the current winner for its spell category (`Duel @ 0x005942B0`): higher
|
||||
power wins, with later start time breaking an equal-power tie. Positive and
|
||||
negative panels then test the winning spell's Beneficial flag and sort
|
||||
alphabetically.
|
||||
Live `AddEnchantmentToList @ 0x00593DF0` inserts a new identity at the bucket
|
||||
head, while an existing identity refreshes in place; bulk UnPack retains its
|
||||
received head-to-tail order. Since an exact power/start tie keeps the first
|
||||
duel incumbent, the newest incrementally added identity wins an exact tie.
|
||||
|
||||
The Helpful/Harmful indicators intentionally use a different projection.
|
||||
`CountSpellsInList @ 0x00593CD0` feeds the registry's two indicator counters by
|
||||
visiting every raw mult/add node before any category duel. Therefore opposite-
|
||||
sign spells in the same category can enable both indicators while only the duel
|
||||
winner appears in the effects list.
|
||||
Remaining duration is `(startTime + duration) - current game time`. Selecting
|
||||
an active token stores its spell ID, so every layer of that spell highlights
|
||||
together and shows the shared name/description; clicking either layer again
|
||||
clears the selection.
|
||||
|
||||
## Portal presentation boundary
|
||||
|
||||
Recall and portal presentation remains DAT-driven. `/ls` starts the retail
|
||||
recall motion (`0x10000153`); its animation hooks/PES chain supplies the cloud.
|
||||
Remote disappearance and materialization use authoritative Hidden/UnHide state
|
||||
and typed physics scripts. No generic teleport effect, guessed color, or local
|
||||
teleport outcome is added by the spell UI work.
|
||||
Loading…
Add table
Add a link
Reference in a new issue