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
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