refactor(runtime): own combat and magic intent
Move attack build/repeat state, combat-mode policy, authoritative auto-target transitions, and spell-cast intent beneath RuntimeActionState. Keep App as the input, world-query, DAT-policy, transport, and presentation adapter while preserving retail request and busy ordering. Add direct/graphical parity, reset, failure, and instance-isolation coverage. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
parent
81b31857c6
commit
20df9d155d
49 changed files with 1949 additions and 634 deletions
|
|
@ -135,7 +135,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
|
|||
| AP-21 | Entity translucency retains the invented α<0.05 fragment discard. World GfxObj/Setup instances now apply their DAT AlphaBlend/Additive/InvAlpha factors through the retail shared alpha queue, but sealed off-screen WbDrawDispatcher consumers (paperdoll/UI Studio) retain the old immediate normal-alpha pass for all three kinds | `src/AcDream.App/Rendering/Shaders/mesh_modern.frag`; `src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs` (`DrawDeferredAlphaBatch` versus immediate Phase 8) | World presentation needed exact per-surface blend for spell/particle density and translucent-object intersections. Off-screen object previews are isolated render targets and have not shown an authored additive entity surface that justifies splitting their compact immediate pass | A faint world fringe below 5% alpha is discarded; a hypothetical additive/inverse-alpha paperdoll or UI Studio entity composites darker than retail inside that private viewport | `D3DPolyRender::SetSurface`; `D3DPolyRender::RenderMeshSubset`; SurfaceType.Additive → D3DBLEND_ONE |
|
||||
| AP-22 | Invented `setup.Radius` cylinder (height = Height or Radius×2) for shapeless live entities; shape + height formula not from the retail shape walk | `src/AcDream.App/Physics/LiveEntityCollisionBuilder.cs`; `src/AcDream.Core/Physics/ShadowShapeBuilder.cs` | ShadowShapeBuilder (faithful walk) only emits CylSphere/Sphere/Part-BSP; the legacy cylinder preserves prior behavior so rare decorative props don't lose collision | Those props collide with an invented footprint (especially the Radius×2 height guess) — slides/blocks at non-retail distances | `find_obj_collisions` → `CPartArray::FindObjCollisions` pc:286236 |
|
||||
| AP-23 | Invented per-type pickup-radius heuristic (3 m creatures / 2 m doors-lifestones-portals-corpses / 0.6 m rest) for close-range gating plus the speculative local TurnToObject/MoveToObject install through the player's MoveToManager. **R5-V3 narrowed it:** the install threads the target's real Setup radius/height (`GetSetupCylinder`, same as wire mt-6) and the player's real radius; only the radius buckets remain invented. **Use retired from this seam 2026-07-25** and now sends immediately. | `src/AcDream.App/Interaction/WorldSelectionQuery.cs` (`TryGetApproach`/`GetUseRadius`); `src/AcDream.App/Interaction/PlayerInteractionMovementSink.cs` (`BeginApproach`) | The retained pickup presentation reserves a destination slot before the authoritative transfer; its close branch still needs an arrival boundary | A target whose real UseRadius differs from the bucket misjudges the pickup gate — pickup waits forever or fires early into a server "too far" | ACE Player_Move.cs:66; wire MoveToObject (type 6) carries the true radius; `CPhysicsObj::TurnToObject/MoveToObject` callers §9a/§9b |
|
||||
| ~~AP-24~~ | **RETIRED 2026-07-11** — matching v11.4186 x86 disassembly recovered `ATTACK_POWERUP_TIME=1.0` seconds and `DUAL_WIELD_POWERUP_TIME=0.8` seconds from the operands loaded by `GetPowerBarLevel`; jump and combat now share those constants. | `src/AcDream.Core/Combat/CombatModel.cs`; `src/AcDream.App/Input/PlayerMovementController.cs`; `src/AcDream.App/Combat/CombatAttackController.cs` | — | — | `ClientCombatSystem::GetPowerBarLevel @ 0x0056ADE0`; static data `0x007CEFC8/0x007CEFD0` |
|
||||
| ~~AP-24~~ | **RETIRED 2026-07-11** — matching v11.4186 x86 disassembly recovered `ATTACK_POWERUP_TIME=1.0` seconds and `DUAL_WIELD_POWERUP_TIME=0.8` seconds from the operands loaded by `GetPowerBarLevel`; jump and combat now share those constants. | `src/AcDream.Core/Combat/CombatModel.cs`; `src/AcDream.App/Input/PlayerMovementController.cs`; `src/AcDream.Runtime/Gameplay/RuntimeCombatAttackState.cs` | — | — | `ClientCombatSystem::GetPowerBarLevel @ 0x0056ADE0`; static data `0x007CEFC8/0x007CEFD0` |
|
||||
| AP-25 | Run/Jump skill pushed to movement = attributeBonus + Init + Ranks — no augmentations, multipliers, or vitae | `src/AcDream.Core.Net/GameEventWiring.cs:346` | Closest to ACE's CreatureSkill.Current short of porting the full Aug/Multiplier/Vitae chain (K-fix7/13) | A character with augs or post-death vitae predicts wrong local run speed / jump arc — dying would NOT slow the local player though the server moves them slower: drift + snap-back | ACE CreatureSkill.Current; ACE Skill.cs (Jump=22, Run=24) |
|
||||
| AP-26 | DDD interrogation answered with an empty dat-version list (count=0); retail reports actual dat iteration state | `src/AcDream.Core.Net/Messages/DddInterrogationResponse.cs:18` | ACE is satisfied by the empty ack; pattern from holtburger | A dat-patching-enabled server could push a full patch or reject on version mismatch — the lie is harmless only while the server never acts on it | DDD flow 0xF7E5/0xF7E6 |
|
||||
| AP-27 | PlayerDescription trailer: GameplayOptions skipped by a 4-byte-aligned heuristic scan for a valid inventory parse; options blob captured opaque, never decoded (retail decodes + applies UI options) | `src/AcDream.Core.Net/Messages/PlayerDescriptionParser.cs:69` | Variable-length opaque blobs; mirrors holtburger's heuristics; follow-up issue extends when panels consume those sections | An options blob that coincidentally parses as a valid inventory (or inventory not landing at EOF) yields wrong/empty inventory+equipped at login; retail-persisted UI options silently ignored | ACE GameEventPlayerDescription.WriteEventBody; holtburger events.rs:195-218 |
|
||||
|
|
@ -208,7 +208,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
|
|||
| 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, residual social/floating chat, quests/map/options/smartbox, vendor/trade/salvage/tinkering, mini-game gameplay, Link Status NAK/retransmission packet-loss averaging, and D.6 nameplates/floaters. Examination has its independent authored floaty layout, inscription transaction, retail creature stat/rating/animated-preview presentation, default selection-follow, authored local spell subview with appropriate-formula component state, and the full EoR item-report dispatch: appraisal-only unknowns; exact equipment-set/rating/tinkering/weapon/armor/caster/requirement/XP/healer/rare prose and intentional blank section rows; ordinary/enchantment DAT spell descriptions; live material-decorated appropriate titles plus DAT material and creature names; expiry, decorated material/gem descriptions; and portal/PK restrictions with authored item colors. It still lacks item-object preview, player-dependent effective shield projection, live cooldown-remaining projection, localized augmentation-cost `StringInfo`, exhaustive character detail regions, and exact creature appraisal FontInfo-list selection. | `src/AcDream.App/UI/RetailUiRuntime.cs`; `src/AcDream.App/UI/Layout/AppraisalUiController.cs`; `src/AcDream.App/UI/Layout/ItemAppraisalTextFormatter.cs`; `src/AcDream.App/UI/Layout/RetailAppraisalNameResolver.cs`; `src/AcDream.App/UI/Layout/CreatureAppraisalRows.cs`; `src/AcDream.App/Rendering/CreatureAppraisalPresentation.cs`; `src/AcDream.Core.Net/LinkStatusSnapshot.cs`; D.5/D.6 roadmap | Basic combat, M3 magic/Link/Vitae surfaces, and the core examination request/presentation/inscription/creature-preview/item-report loop cover the active loops; the residual examination mechanisms require live player/enchantment/localization state or object-preview ownership rather than fabricated content | Item assessments omit only the listed live/localized/preview projections; enchanted/incomplete creature appraisal rows use the normal authored font until the exact FontInfo list is bound; other absent panels remain unavailable; real packet loss is displayed as 0.00% instead of retail's moving average | `BasicCreatureExamineUI::Init @ 0x004AB9C0`; `CreatureExamineUI::SetAppraiseInfo @ 0x004B3FF0`; `gmExaminationUI::RecvNotice_SelectionChanged @ 0x004AB3D0`; `gmExaminationUI::ExamineSpell @ 0x004B6900`; `SpellExamineUI::ExamineSpell @ 0x004B6210`; `AttributeInfoRegion::Update @ 0x004F1D90`; `gmExaminationUI::SetAppraiseInfo @ 0x004ADAE0`; `ACCWeenieObject::GetObjectName @ 0x0058E6E0`; `ItemExamineUI::SetAppraiseInfo @ 0x004B72B0`; `ItemExamineUI::AddItemInfo @ 0x004AC050`; `ItemExamineUI::Appraisal_ShowCapacity @ 0x004B2680`; `ItemExamineUI::Appraisal_ShowSpecialProperties @ 0x004B0140`; `ItemExamineUI::Appraisal_ShowWeaponAndArmorData @ 0x004B10E0`; `ItemExamineUI::Appraisal_ShowMagicInfo @ 0x004B2E10`; `ItemExamineUI::Appraisal_ShowDescription @ 0x004B6990`; `MaterialTypeEnumMapper::MaterialTypeToString @ 0x005CD500`; `ItemExamineUI::SetInscription @ 0x004AE2F0`; `CM_Writing::Event_SetInscription @ 0x006A98B0`; `CLinkStatusAverages::GetAveragePacketLoss @ 0x00546610`; 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-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.Runtime/Gameplay/RuntimeCombatAttackState.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` |
|
||||
|
||||
| ~~AP-114~~ | **RETIRED 2026-07-14 (protection-effect corrective gate)** — the particle renderer no longer replaces every authored GfxObj with one bounding-box quad. Retail `Always2D` classification preserves mode-1/no-degrade full meshes through the modern shared mesh buffer and leaves only other degrade modes on the billboard path; stable emitter handles balance mesh ownership. | `src/AcDream.App/Rendering/ParticleRenderer.cs`; `RetailParticleGeometryClassifier.cs`; `particle_mesh.vert/.frag` | — | — | `CPhysicsPart::Draw @ 0x0050D7A0`; `CPhysicsPart::Always2D @ 0x0050D8A0`; `ParticleEmitter::SetInfo @ 0x0051CE90`; `docs/research/2026-07-13-retail-projectile-vfx-pseudocode.md` |
|
||||
|
|
|
|||
189
docs/research/2026-07-26-slice-j5-3-combat-magic-intent.md
Normal file
189
docs/research/2026-07-26-slice-j5-3-combat-magic-intent.md
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
# Slice J5.3 — retail combat and magic intent ownership
|
||||
|
||||
## Scope
|
||||
|
||||
This note pins the retail ordering that must remain unchanged while the
|
||||
presentation-independent combat and casting state moves from `AcDream.App` to
|
||||
`AcDream.Runtime`. The move changes ownership and command types only. ACE
|
||||
remains authoritative for combat mode settlement, attacks, casts, damage,
|
||||
motion, component consumption, effects, and completion.
|
||||
|
||||
Primary oracle:
|
||||
`docs/research/named-retail/acclient_2013_pseudo_c.txt`.
|
||||
|
||||
Cross-checks:
|
||||
|
||||
- the ACE/ACViewer request-layout cross-checks pinned in
|
||||
`docs/research/2026-07-11-retail-combat-bar-pseudocode.md` and
|
||||
`docs/research/2026-07-15-retail-magic-ui-and-casting-pseudocode.md`;
|
||||
- the ACE corpse/death-state cross-check pinned in
|
||||
`docs/research/2026-07-12-death-and-auto-target-pseudocode.md`;
|
||||
- existing conformance ports in
|
||||
`src/AcDream.Core/Combat/CombatModel.cs` and
|
||||
`src/AcDream.Core/Spells/RetailSpellTargetPolicy.cs`
|
||||
|
||||
## Named retail mechanisms
|
||||
|
||||
### `ClientCombatSystem::Begin @ 0x0056A460`
|
||||
|
||||
Pseudocode:
|
||||
|
||||
```text
|
||||
combatMode = NonCombat
|
||||
pendingCombatMode = Undefined
|
||||
clear jump and power-bar timing
|
||||
clear request, server-pending, queued-request, attack, and repeat state
|
||||
requestedPower = 0
|
||||
requestedHeight = Medium
|
||||
desiredUiPower = 0.5
|
||||
trackingTarget = false
|
||||
advancedCombat = false
|
||||
```
|
||||
|
||||
This is the session-reset state of the Runtime combat-intent owner.
|
||||
|
||||
### `ClientCombatSystem::StartAttackRequest @ 0x0056C040`
|
||||
|
||||
Pseudocode:
|
||||
|
||||
```text
|
||||
target = GetAttackTarget()
|
||||
if advanced combat or (target exists and ObjectIsAttackable(target)):
|
||||
attackRequestInProgress = true
|
||||
requestedAttackPower = 1.0
|
||||
FinishJump()
|
||||
MaybeStopCompletely()
|
||||
currentBuildIsAutomatic = false
|
||||
AttemptStartBuildingAttack()
|
||||
else:
|
||||
display the retail invalid-target notice
|
||||
```
|
||||
|
||||
The request flags commit before the shared movement owner is asked to finish
|
||||
jump/stop. The host operation preserves that exact boundary and may publish
|
||||
the resulting movement packet before a later attack send.
|
||||
|
||||
### `StartPowerBarBuild @ 0x0056ADB0`,
|
||||
`EndAttackRequest @ 0x0056C0E0`, and `UseTime @ 0x0056C1F0`
|
||||
|
||||
Pseudocode:
|
||||
|
||||
```text
|
||||
StartPowerBarBuild:
|
||||
buildInProgress = true
|
||||
buildStartTime = current game time
|
||||
publish level 0
|
||||
latestPowerBarLevel = 0
|
||||
|
||||
EndAttackRequest:
|
||||
if no request: return
|
||||
requestInProgress = false
|
||||
currentLevel = GetPowerBarLevel()
|
||||
requestedPower = explicit power, advanced current level,
|
||||
or min(desiredUiPower, currentLevel)
|
||||
if server response pending:
|
||||
remember one queued release
|
||||
else if desired power reached or repeating:
|
||||
ExecuteAttack()
|
||||
|
||||
UseTime:
|
||||
while building, publish the current level
|
||||
if the player is not ready, cancel/reset according to repeat state
|
||||
after release, execute once the captured requested level is reached
|
||||
```
|
||||
|
||||
### `AbortAutomaticAttack @ 0x0056AE90`
|
||||
|
||||
If a request, attack, server response, or repeat is active, retail sends
|
||||
`Event_CancelAttack`, clears repeat, and hides the combat power build.
|
||||
`ACCmdInterp::HandleNewForwardMovement @ 0x0058B1F0` invokes this before the
|
||||
new forward movement enters the interpreter. Runtime therefore receives a
|
||||
typed movement-abort command at that existing ordering boundary.
|
||||
|
||||
### `HandleAttackDoneEvent @ 0x0056C500`
|
||||
|
||||
Retail releases the attack busy reference (when held), clears server-pending
|
||||
state, aborts repeat on authoritative error, starts the next automatic build
|
||||
or attack only when repeat remains enabled, and finally drains the one queued
|
||||
release. Runtime must never predict completion.
|
||||
|
||||
### `GetDefaultCombatMode @ 0x0056B310` and
|
||||
`ToggleCombatMode @ 0x0056C8C0`
|
||||
|
||||
From non-combat, retail scans equipped contents in inventory-placement order:
|
||||
missile equipment selects Missile, a caster in Held selects Magic, ordinary
|
||||
melee/two-handed equipment selects Melee, and an incompatible Held item
|
||||
rejects the transition with its item-name notice. With no weapon it selects
|
||||
unarmed Melee. From any combat mode, Toggle selects NonCombat without running
|
||||
the default scan.
|
||||
|
||||
The exact decision remains the existing Core port
|
||||
`CombatInputPlanner.GetDefaultCombatModeDecision`; Runtime owns the command
|
||||
state and App supplies ordered equipment plus transport/presentation sinks.
|
||||
|
||||
### `RecvNotice_SelectionChanged @ 0x0056BD80` and
|
||||
`AutoTarget @ 0x0056BC80`
|
||||
|
||||
When selection becomes zero in Melee/Missile mode and Auto Target is enabled,
|
||||
retail enters its next-target selection path. An authoritative Dead motion
|
||||
makes the current target unavailable and reaches the same selection-change
|
||||
path. Runtime owns the identity transition; App supplies the world query that
|
||||
selects the closest retail-compatible hostile.
|
||||
|
||||
### `ClientMagicSystem::CastSpell @ 0x00568040`
|
||||
|
||||
Pseudocode:
|
||||
|
||||
```text
|
||||
resolve the exact known spell metadata
|
||||
if components are required:
|
||||
resolve the appropriate formula
|
||||
reject when a required component is unavailable
|
||||
resolve self, untargeted, or selected target
|
||||
validate the target against the spell target mask
|
||||
FreeHandsAndCastSpell(spellId, targetId)
|
||||
```
|
||||
|
||||
### `ClientMagicSystem::FreeHandsAndCastSpell @ 0x00566EF0`
|
||||
|
||||
Pseudocode:
|
||||
|
||||
```text
|
||||
MaybeStopCompletely()
|
||||
if targetId == 0:
|
||||
Event_CastUntargetedSpell(spellId)
|
||||
else:
|
||||
Event_CastTargetedSpell(targetId, spellId)
|
||||
IncrementBusyCount()
|
||||
```
|
||||
|
||||
The cast owner therefore stops first, emits exactly one packet, and increments
|
||||
the shared busy reference only after a successful send. It does not locally
|
||||
turn the avatar: retail sends after `MaybeStopCompletely`, and the
|
||||
authoritative server turn/cast motion supplies the visible facing. Send
|
||||
failure clears the remembered request and cannot increment busy.
|
||||
|
||||
`ClientMagicSystem::OnEndCharacterSession @ 0x00566F50` clears selected and
|
||||
targeting spell state, which maps to Runtime session reset.
|
||||
|
||||
## Ownership result
|
||||
|
||||
Runtime owns:
|
||||
|
||||
- attack build/request/repeat/queued-release state and time;
|
||||
- typed attack commands and movement-abort intent;
|
||||
- combat-mode command policy;
|
||||
- authoritative target-death/auto-target identity transitions;
|
||||
- cast validation intent and last requested spell/target;
|
||||
- reset, teardown, revisions, and presentation-independent snapshots.
|
||||
|
||||
App retains:
|
||||
|
||||
- input-key and retained-widget mapping;
|
||||
- ordered equipped-object and closest-hostile world queries;
|
||||
- local movement preparation and outbound transport;
|
||||
- target/spell compatibility data that depends on the live object table;
|
||||
- DAT spell catalog/formulas/components/icons;
|
||||
- messages, bars, animations, audio, particles, and camera presentation.
|
||||
|
||||
No new retail divergence is introduced by this ownership move.
|
||||
Loading…
Add table
Add a link
Reference in a new issue