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>
6.4 KiB
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.mdanddocs/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.csandsrc/AcDream.Core/Spells/RetailSpellTargetPolicy.cs
Named retail mechanisms
ClientCombatSystem::Begin @ 0x0056A460
Pseudocode:
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:
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:
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:
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:
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.