docs(architecture): plan GameWindow update-frame extraction
This commit is contained in:
parent
d4ecac1da3
commit
a36a7015c4
6 changed files with 594 additions and 37 deletions
|
|
@ -8,18 +8,23 @@ Retail oracle: Asheron's Call v11.4186 (September 2013 EoR)
|
|||
This note pins the control-flow order needed by R6. It deliberately separates
|
||||
two kinds of animation-hook work:
|
||||
|
||||
- **control hooks** such as `AnimDoneHook`, whose result changes the motion
|
||||
- the immediate **`AnimDoneHook` control path**, whose result changes the motion
|
||||
queues observed later in the same object update; and
|
||||
- **pose-dependent presentation hooks** such as particles and sounds, whose
|
||||
App-layer delivery may wait until the final root/part pose has been
|
||||
published without changing motion state.
|
||||
- **all other captured animation hooks**, which acdream currently defers until
|
||||
the final root/part pose has been published. This includes presentation
|
||||
hooks such as particles and sounds, but also semantically relevant
|
||||
`CallPES`, default-script chaining, and translucency hooks.
|
||||
|
||||
That split is an acdream presentation boundary. It does not change retail's
|
||||
logical hook order.
|
||||
That split is an acdream presentation boundary. Authored FIFO order is
|
||||
preserved within acdream's deferred non-`AnimationDone` stream, but moving that
|
||||
stream past retail's manager tail changes manager-relative timing; that is the
|
||||
registered TS-50 divergence.
|
||||
|
||||
## Named-retail sources
|
||||
|
||||
- `CPhysicsObj::process_hooks` `0x00511550`
|
||||
- `CPhysics::UseTime` `0x00509950`
|
||||
- `CPhysicsObj::animate_static_object` `0x00513DF0`
|
||||
- `CPhysicsObj::UpdatePositionInternal` `0x00512C30`
|
||||
- `CPhysicsObj::UpdateObjectInternal` `0x005156B0`
|
||||
- `CPhysicsObj::update_object` `0x00515D10`
|
||||
|
|
@ -58,6 +63,17 @@ CPhysicsObj::update_object
|
|||
-> PositionManager::UseTime
|
||||
-> ParticleManager::UpdateParticles
|
||||
-> ScriptManager::UpdateScripts
|
||||
|
||||
CPhysics::UseTime
|
||||
-> ordinary object table in the order above
|
||||
-> static_animating_objects
|
||||
-> CPhysicsObj::animate_static_object
|
||||
-> CPartArray::Update
|
||||
-> UpdatePartsInternal
|
||||
-> UpdateChildrenInternal
|
||||
-> ScriptManager::UpdateScripts
|
||||
-> ParticleManager::UpdateParticles
|
||||
-> CPhysicsObj::process_hooks
|
||||
```
|
||||
|
||||
## Pseudocode
|
||||
|
|
@ -66,11 +82,16 @@ CPhysicsObj::update_object
|
|||
|
||||
```text
|
||||
if parent != null or cell == null or state contains Frozen:
|
||||
clear Active transient state
|
||||
return
|
||||
|
||||
refresh active/view-distance state
|
||||
elapsed = current_physics_time - last_update_time
|
||||
|
||||
if elapsed <= FrameEpsilon (0.000199999995 seconds):
|
||||
last_update_time = current_physics_time
|
||||
return
|
||||
|
||||
if elapsed > HugeQuantum:
|
||||
last_update_time = current_physics_time
|
||||
return
|
||||
|
|
@ -85,8 +106,19 @@ if elapsed > MinQuantum:
|
|||
UpdateObjectInternal(elapsed)
|
||||
```
|
||||
|
||||
The comparisons are strict (`>`), not `>=`. A sub-minimum remainder stays in
|
||||
the clock for a later call.
|
||||
The comparisons are strict (`>`), not `>=`. Elapsed time at or below the
|
||||
roughly 0.2-millisecond frame epsilon is explicitly rebased/discarded. Above
|
||||
that epsilon, a remainder at or below `MinQuantum` stays in the clock for a
|
||||
later call; `MaxQuantum` controls subdivision and `HugeQuantum` controls stale
|
||||
discard. These are separate gates.
|
||||
|
||||
`UpdateObjectInternal` then branches on the transient Active bit. An inactive
|
||||
object skips position integration, hook processing, Detection, Target,
|
||||
Movement, PartArray, and PositionManager, but still advances its
|
||||
ParticleManager and ScriptManager tails. A Hidden object that remains Active
|
||||
is different: it enters `UpdatePositionInternal`, suppresses only PartArray
|
||||
advance and physics integration, and retains PositionManager adjustment,
|
||||
hook processing, and the complete manager tail.
|
||||
|
||||
### `CPhysicsObj::UpdatePositionInternal(dt, candidate)`
|
||||
|
||||
|
|
@ -120,13 +152,25 @@ manager tail.
|
|||
### `CPhysicsObj::process_hooks`
|
||||
|
||||
```text
|
||||
execute each queued physics hook in FIFO order
|
||||
clear physics-hook queue
|
||||
for each physics hook present in the linked list walk:
|
||||
completed = hook.Execute(owner)
|
||||
if completed:
|
||||
unlink and destroy that hook
|
||||
else:
|
||||
retain it for a later object update
|
||||
|
||||
execute each queued animation hook in FIFO order
|
||||
clear animation-hook queue
|
||||
for i from zero while i < current animation-hook count:
|
||||
animation_hooks[i].Execute(owner)
|
||||
// the bound is live: a hook appended during execution can run in this drain
|
||||
shrink animation-hook storage and set the count to zero
|
||||
```
|
||||
|
||||
The physics-hook walk snapshots each current node's `next` pointer before
|
||||
execution. A completed hook is removed; an incomplete hook remains linked.
|
||||
The animation-hook loop deliberately rereads `m_num` after each callback, so
|
||||
reentrant append is consumed in the same drain. Neither collection is an
|
||||
unconditional snapshot-and-clear queue.
|
||||
|
||||
For an `AnimDoneHook`:
|
||||
|
||||
```text
|
||||
|
|
@ -175,8 +219,52 @@ if script_manager != null:
|
|||
```
|
||||
|
||||
The tail is reached for both visible and Hidden objects when the object quantum
|
||||
runs. A missing manager is simply skipped. No manager is reordered around
|
||||
another manager based on local/remote/player/NPC classification.
|
||||
runs and the object is Active. An inactive object skips this position/manager
|
||||
body but still reaches Particle then Script. A missing manager is simply
|
||||
skipped. No manager is reordered around another manager based on
|
||||
local/remote/player/NPC classification.
|
||||
|
||||
### `CPhysicsObj::animate_static_object`
|
||||
|
||||
```text
|
||||
if cell == null:
|
||||
return
|
||||
|
||||
elapsed = current_time - update_time
|
||||
if elapsed <= FrameEpsilon (0.000199999995 seconds):
|
||||
update_time = current_time
|
||||
return
|
||||
if elapsed > HugeQuantum:
|
||||
update_time = current_time
|
||||
return
|
||||
|
||||
if part_array != null:
|
||||
if state contains HasDefaultAnim:
|
||||
part_array.Update(elapsed)
|
||||
rotate root by the current angular vector
|
||||
UpdatePartsInternal()
|
||||
UpdateChildrenInternal()
|
||||
|
||||
if state contains HasDefaultScript and script_manager != null:
|
||||
script_manager.UpdateScripts()
|
||||
|
||||
if particle_manager != null:
|
||||
particle_manager.UpdateParticles()
|
||||
|
||||
process_hooks()
|
||||
|
||||
update_time = current_time
|
||||
```
|
||||
|
||||
The static workset has no ordinary-object `MinQuantum` remainder gate or
|
||||
`MaxQuantum` subdivision loop: it uses only the frame epsilon and
|
||||
`HugeQuantum`, then passes the complete admitted elapsed time to PartArray.
|
||||
It therefore has a distinct Script → Particle → hooks tail, not ordinary
|
||||
objects' Particle → Script tail. acdream currently shares one
|
||||
ParticleSystem and one PhysicsScriptRunner across every owner and advances
|
||||
them once after the complete ordinary/static worksets. That is the registered
|
||||
TS-51 adaptation and must not be silently reordered by an ownership-only
|
||||
extraction.
|
||||
|
||||
## acdream conformance boundary
|
||||
|
||||
|
|
@ -186,9 +274,11 @@ unsupported `DetectionManager` is represented by an absent callback rather
|
|||
than an invented behavior.
|
||||
|
||||
Animation-hook capture performs `AnimationDone` control effects immediately,
|
||||
at the `process_hooks` boundary. Pose-dependent routing remains deferred until
|
||||
the current frame's root/part poses are published. Deferred routing must never
|
||||
invoke `AnimationDone` a second time and must never own
|
||||
at the `process_hooks` boundary. Every other captured animation hook currently
|
||||
remains deferred until the final root/part poses are published, including
|
||||
particle/audio/light/translucency hooks, `CallPES`, and default-script
|
||||
chaining. Deferred routing must never invoke `AnimationDone` a second time and
|
||||
must never own
|
||||
`MotionTableManager.UseTime`; that belongs only to the PartArray manager-tail
|
||||
slot.
|
||||
|
||||
|
|
@ -199,17 +289,32 @@ slot.
|
|||
2. Target runs before Movement; Movement runs before PartArray; PartArray runs
|
||||
before PositionManager.
|
||||
3. A missing manager does not shift or duplicate another callback.
|
||||
4. Hidden objects skip sequence advance but retain the same manager tail.
|
||||
5. A visual hook sees a pose published after capture, while an `AnimDoneHook`
|
||||
changes motion state at capture time.
|
||||
6. Deferred visual-hook drain never completes an animation twice and never
|
||||
performs a zero-tick motion sweep.
|
||||
7. A sub-MinQuantum frame performs no object-tail call; accumulated time is
|
||||
consumed by a later quantum.
|
||||
4. Parented, cell-less, and Frozen objects return before clock admission.
|
||||
5. Inactive objects skip position/hooks/manager time but retain Particle then
|
||||
Script; Hidden + Active objects skip sequence/physics but retain adjustment,
|
||||
hooks, and the manager tail.
|
||||
6. A non-`AnimationDone` hook sees a pose published after capture, while an
|
||||
`AnimDoneHook` changes motion state at capture time.
|
||||
7. Deferred hook drain never completes an animation twice and never performs
|
||||
a zero-tick motion sweep; representative `CallPES`, default-script, and
|
||||
translucency hooks retain their current deferred path.
|
||||
8. An incomplete physics hook remains linked, and an animation hook appended
|
||||
by another animation hook executes in the same retail drain.
|
||||
9. Static objects retain their distinct Script → Particle → hooks order.
|
||||
10. Ordinary epsilon/Min/Max/Huge boundaries and static epsilon/Huge
|
||||
boundaries preserve their separate strict retail comparisons.
|
||||
|
||||
## Cross-reference notes
|
||||
|
||||
ACE's physics/motion ports remain interpretation aids for manager ownership
|
||||
and state naming; the retail binary above is authoritative for order. The
|
||||
existing `RemoteChaseEndToEndHarnessTests` hand-codes acdream's pre-R6 order
|
||||
and is therefore a migration target, not an oracle.
|
||||
ACE's `PhysicsObj.cs` corroborates ordinary Particle → Script and static
|
||||
Script → Particle → hooks order. It does **not** corroborate the retail clock
|
||||
or hook-lifetime gates: ACE uses its server `TickRate`/Min/Max policy and
|
||||
clears physics hooks unconditionally, unlike retail's epsilon/Huge behavior
|
||||
and completion-based hook retention. ACViewer exposes the same client-shaped
|
||||
methods and manager order but shares ancestry with ACE, so that agreement is
|
||||
corroboration rather than a second oracle. holtburger's native protocol crates
|
||||
have no client physics-loop equivalent (its vendored ACViewer copy is not
|
||||
independent). The retail binary above is authoritative for every boundary.
|
||||
The existing
|
||||
`RemoteChaseEndToEndHarnessTests` hand-codes acdream's pre-R6 order and is
|
||||
therefore a migration target, not an oracle.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue