fix(combat): match retail live attack feedback

Keep AttackDone control statuses out of chat, preserve dead motion across zero-velocity position updates, render the authored power meter from right to left, and retain the rotatable viewer offset during target tracking.

Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-11 21:10:01 +02:00
parent 33cc9aa16a
commit 5276a83087
16 changed files with 231 additions and 67 deletions

View file

@ -0,0 +1,74 @@
# Combat live-gate corrections pseudocode
Sources: Sept 2013 EoR named retail client and ACE server source.
- `ClientCombatSystem::HandleAttackDoneEvent` (`0x0056C500`)
- `gmCombatUI::RecvNotice_SetPowerbarLevel` (`0x004CC0E0`)
- `CPhysicsObj::MoveOrTeleport` (`0x00516330`)
- `CameraSet::Rotate` (`0x00458310`)
- ACE `Player.OnAttackDone`, `Player_Melee.cs`
- ACE `WeenieError.YouChargedTooFar`, `Player_Move.cs`
## Attack completion versus visible errors
```text
HandleAttackDoneEvent(sequence, error):
consume error as combat-controller state
decide whether repeat may continue or the attack aborts
do not write an error line to chat
ACE OnAttackDone(final sequence):
send AttackDone(ActionCancelled) to reset the client power meter
if the player must see a failure:
send a separate WeenieError event
```
Therefore `AttackDone(ActionCancelled)` is silent. A separate
`YouChargedTooFar` (`0x003D`) remains visible and is formatted by the ordinary
WeenieError path. ACE emits it when the charge exceeds 15 metres; target
selection itself is not range-limited by retail.
## Power meter direction
```text
RecvNotice_SetPowerbarLevel(level):
find nested meter 0x10000050
set meter scalar attribute 0x69 to level
authored meter direction = 1
visible fill = rightmost (width * clamp(level)) pixels
```
The red meter grows from the Power label at the right toward Speed at the
left. The green desired-power thumb remains an independent scrollbar child.
Clipping the rightmost portion must retain the full tiled image's UV offset.
## Persistent death motion
```text
on UpdatePosition(position, velocity):
update physics pose and velocity
do not derive an interpreted motion command from velocity
on UpdateMotion(Dead):
apply the persistent Dead motion cycle through the motion interpreter
```
A corpse legitimately sends zero velocity. Turning that velocity into Ready
overrides the server-authored Dead cycle and makes the creature stand again.
Retail keeps position/velocity transport separate from interpreted motion.
## Keep in View orbit
See `2026-07-11-combat-target-camera-pseudocode.md`. The corrective detail is:
```text
targetHeadingFrame = frame looking from player pivot toward target point
viewerOffset.xy = the independently rotatable camera boom offset
eye = pivot + targetHeadingFrame.transform(viewerOffset)
forward = normalize(pivot - eye) // LOOK_AT_PIVOT
```
Tracking changes the frame that transforms `viewerOffset`; it does not reset
or discard the offset. Manual camera orbit therefore remains usable while
Keep in View is active.

View file

@ -36,6 +36,10 @@ CameraSet.TrackTarget(targetId):
SetTargetForOffset(viewerOffset), while targeting:
camera.targetStatus = LOOK_AT_OBJECT | LOOK_AT_PIVOT
CameraSet.Rotate(angle), including while targeting:
viewerOffset.xy = rotate(viewerOffset.xy, angle)
SetTargetForOffset(viewerOffset)
```
## Per-frame pose
@ -60,14 +64,17 @@ damp current viewer toward (desiredEye, desiredForward)
The target therefore changes the direction of the boom around the player; it
does not replace the player as the boom's pivot. `LOOK_AT_PIVOT` keeps the
player centered while the target-facing boom keeps the combat target in view.
The viewer offset remains independently rotatable: manual camera orbit rotates
that offset inside the target-facing frame rather than being discarded or
snapped back to zero.
## Port mapping
- `GameWindow.GetCombatCameraTargetPoint` applies the option/mode/valid-target
gates and transforms the target-local 0.5 metre Z offset.
- `RetailChaseCamera.ComputeTrackedHeading` ports the pivot-to-target direction.
- Existing `RetailChaseCamera` boom construction and damping consume that
heading unchanged.
- `RetailChaseCamera.ComputeDesiredPose` ports the heading-frame transform and
preserves the viewer-offset yaw while tracking.
- The diagnostic legacy `ChaseCamera` remains intentionally non-retail under
existing divergence row TS-19; the production retail camera owns this behavior.