Files
Commonwealth-Online-Public/docs/architecture.md
T
andrew 44ea1b98da Add data-only proxy animation debug logging
Track and log per-slot animation/movement diagnostics for runtime proxy actors without applying any visual animation. Adds a ProxyAnimationSpeedBucket enum and GetProxyAnimationSpeedBucket/GetProxyAnimationSpeedBucketLabel helpers, a new UpdateProxyAnimationStateDebug() routine, and fields on ProxyActorSlot to remember last-observed moving, sprinting, sneaking, jumping, weapon-drawn, movement type, and coarse speed-bucket state. Resets observation state when slots are created/reused and emits initial/transition-only logs including playerId and proxy FormID. Documentation and dev-log entries updated; visual animation, animation graph changes, and AI/movement behavior remain intentionally unmodified.
2026-06-03 11:36:38 +12:00

277 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Architecture
## Current Architecture
```text
Fallout 4 Plugin ↔ Local Python Server ↔ Other Clients
Plugin Remote Player State
```
The current system is still a local prototype, but the Fallout 4 plugin now has
both send and receive paths:
1. The Fallout 4 plugin reads local player transform data.
2. The plugin derives basic movement state on the game-thread polling path and
sends it as optional data on transform packets.
3. The plugin sends transform packets to the local Python server on
`127.0.0.1:7777`. Normal movement sends are throttled separately from
movement logs, targeting roughly 10 Hz while the player is moving.
4. The server assigns `playerId` values and sends `welcome` packets.
5. The plugin receives its `welcome` packet and stores its assigned `playerId`.
6. The server adds `playerId` and `serverTime` to transform packets.
7. The server broadcasts transform packets to other connected clients.
8. The plugin and `server/fake_client.py` store remote player state by `playerId`.
9. The server broadcasts `disconnect` packets when clients disconnect.
10. The plugin and fake client remove disconnected players from their remote
player tables.
11. On the game-thread update path, the proxy controller reads a copied
remote-player snapshot before attempting any actor work. The snapshot is
sorted by `playerId`, and only remote players with valid transform data in
the same cell are eligible for runtime proxy representation.
12. For each eligible remote `playerId`, the controller maintains a
`remotePlayerId -> runtime proxy slot` mapping. Each slot stores its
`ObjectRefHandle`, lifecycle state, PlaceAtMe-backed spawn/candidate state,
movement smoothing state, and data-only animation debug state. Stage 4 caps runtime
proxy slots at four actors for testing.
13. Runtime proxies are demand-driven. A slot executes the Stage 3.7
PlaceAtMe-backed spawn flow only when that remote player needs a same-cell
representation, and only one PlaceAtMe spawn attempt may run at a time so
candidate isolation remains unambiguous. Stage 4 captures the remote
player's current valid transform before spawning and moves the isolated
runtime actor to that transform immediately, so normal remote representation
does not visually start at the local player offset.
14. Runtime proxies receive a small game-thread neutralization pass when a
PlaceAtMe-backed actor is promoted, when a reusable slot is reassigned, while
active on a throttle, and when moved or kept in holding. The pass only uses
confirmed CommonLibF4 actor APIs/fields to stop combat, clear combat target
handles, reset safe hostile/attack state, suppress combat-oriented process
flags, clear exposed AI process movement targets, and attempt safe
package/pathing interruption with a do-nothing package when pathing, flee,
alarm, bump, or package movement intent is observed. It does not disable,
hide, delete, despawn, alpha-fade, or force animation graph events on the
actor.
15. Proxy visual updates are independent per slot. Normal movement is smoothed
toward that players latest target, while `cell_change`, `worldspace_change`,
and `teleport` snap directly. Movement and animation-related remote state is
still observed and logged only; no visual crouch/sneak, jump, weapon drawn,
locomotion, animation graph, or actor-state application is attempted.
16. When a remote player leaves the cell or disconnects, only that players
runtime proxy is moved to the hidden holding position inside
`F4TTestCell01`. Runtime actors are not disabled or deleted. Connected
players who leave the cell keep their slot reserved, while disconnected
held slots become reusable only after the configured grace period. Runtime
slots use per-slot holding positions with spacing and a game-thread
correction check so held actors do not stack or drift back into view.
17. The placed fallback reference `F4TProxyRemotePlayer01REF` remains a single
backup representation. If runtime spawning fails for one deterministic
selected remote player, the fallback can represent that one player only; it
is not used for multiple remote players.
18. As part of the staged runtime proxy actor manager work, the same game-thread
proxy controller also logs whether `Fallout4Together_Test.esp` is loaded and
whether the actor base `F4T_RemotePlayerProxy` resolves by editor ID.
19. Stage 3.1 adds a temporary diagnostic layer around the single runtime proxy:
post-spawn visibility data, loaded-3D pointer checks where safe, console
helper logs, active source logs, and throttled post-movement diagnostics.
The diagnostic mode can force the runtime proxy near the local player to
prove whether the created actor is visibly renderable.
20. Stage 3.2 adds a temporary near-player visibility hold for that same single
runtime proxy. During the hold, remote state is still selected and validated,
but runtime proxy restore and remote movement are deferred long enough to
test whether a runtime-created actor ref can load visible 3D while kept near
the local player.
21. Stage 3.3 adds a temporary vanilla actor-base diagnostic layer. It attempts
to resolve Codsworth as a visible vanilla `TESNPC` candidate, then passes
that actor base into the same runtime spawn, handle validation, near-player
hold, and visibility diagnostic path. This isolates custom actor-base issues
from runtime-spawn/init issues without changing the final architecture.
22. Stage 3.4 adds a temporary absolute FormID and existing-reference diagnostic
layer. It compares Codsworth base-form runtime spawning against moving the
existing placed Codsworth reference near the local player as a visibility
control. This separates runtime actor creation/init problems from vanilla
actor-base or placed-reference movement problems and does not change the
final architecture.
23. Stage 3.5 adds a temporary PlaceAtMe-equivalent diagnostic after manual
console validation proved `player.placeatme 000179FF 1` can spawn visible
Codsworth in `F4TTestCell01`. The plugin executes that console placement path
from the local player, resolves the newly created Codsworth actor near the
player, and holds/logs it separately from the `CreateReferenceAtLocation`
runtime proxy path.
24. Stage 3.6 refines that temporary PlaceAtMe diagnostic layer. It snapshots
known Codsworth refs before executing `player.placeatme 000179FF 1`, accepts
only a newly isolated Codsworth ref for the primary diagnostic, and preserves
that actor at the original console-spawned location during a short settle
window. This tests whether the actor becomes visible naturally or requires a
render/process refresh such as the manually observed pause-menu or alt-tab
behavior. Stage 3.6 does not change the final architecture.
25. Stage 3.7 promotes the working PlaceAtMe-style spawn pattern into the
preferred single runtime proxy spawn path. The proxy controller uses the
resolved runtime FormID for `F4T_RemotePlayerProxy` when available, falls
back to Codsworth only for diagnostics, defers active proxy selection during
candidate isolation and settle, then promotes the settled actor into the
existing single-proxy movement pipeline.
The Fallout 4 plugin now dynamically spawns one runtime proxy actor per
represented remote `playerId` in the test cell, up to a small Stage 4 test cap.
Disconnected held slots can be reassigned to later server `playerId` values after
the grace period, but slots held for connected players outside the cell are not
reused.
The placed proxy reference remains as a single fallback path if runtime spawning
fails for one selected remote player.
Current animation-state handling is data/debug only:
```mermaid
flowchart LR
RemotePlayerState[RemotePlayerState] --> ProxyActorSlot[ProxyActorSlot animation debug state]
ProxyActorSlot --> Logs[Initial and transition logs]
ProxyActorSlot --> FutureVisualAnimation[Future visual animation application]
```
## Main Components
### F4SE Plugin
The native plugin is responsible for:
- Loading into Fallout 4
- Reading local player state
- Sending local player data to the server
- Adding basic data-only movement state to transform packets
- Throttling normal transform sends separately from readable local movement logs
- Receiving server packets on a background thread
- Storing assigned and remote `playerId` state internally
- Moving runtime proxy actors from per-`playerId` remote-player state on the
game thread, preferring runtime slots and falling back to the placed reference
for one selected player only if runtime spawning fails
- Holding only the affected proxy actor at a hidden in-cell position when its
remote player disconnects or leaves the same cell
- Assigning each runtime proxy slot a unique hidden holding position and
correcting held actors back to that position if collision, AI, or physics drift
moves them away
- Reusing only disconnected runtime proxy slots after a grace period; slots held
because a still-connected remote player left the cell remain reserved for that
original `playerId`
- Logging staged runtime proxy actor base lookup diagnostics on the same
game-thread controller path
- Attempting one game-thread-only PlaceAtMe-backed runtime spawn from the
selected Stage 3 actor base in `F4TTestCell01`, normally
`F4T_RemotePlayerProxy`
- Capturing the remote player's current valid transform before Stage 4 runtime
spawn and applying it as soon as the PlaceAtMe-backed actor is isolated
- Neutralizing only resolved runtime proxy actors on the game-thread controller
path after spawn promotion, during reusable-slot reassignment, periodically
while active, and while held so they remain passive visual puppets rather than
independent combat actors
- Logging initial and transition-only data/debug animation state per resolved
runtime proxy slot from `RemotePlayerState`, including moving, speed bucket,
sprinting, sneaking, jumping, weapon drawn, and movement type
- Suppressing exposed AI movement intent for runtime proxies by clearing safe
process target handles and using validated package interruption/do-nothing
calls when combat, pathing, flee/alarm, bump, or package movement state is
observed
- Logging temporary Stage 3.1 visibility and movement diagnostics for the single
runtime proxy, including active proxy source, runtime FormID, local distance,
and safe loaded-3D checks
- Holding the single runtime proxy near the local player during temporary Stage
3.2 diagnostics so the plugin can determine whether runtime-created actor refs
can load visible 3D before remote movement resumes
- Temporarily comparing a vanilla visible NPC actor base against the custom
proxy actor base during Stage 3.3, while preserving the same runtime spawn
path before the Stage 4 mapping work
- Temporarily resolving Codsworth base and placed-reference absolute FormIDs
during Stage 3.4, then moving the existing placed reference near the player as
a visibility control separate from runtime spawning
- Temporarily testing a local-player `player.placeatme`-equivalent console path
during Stage 3.5, then comparing the resulting visible actor behavior against
the existing `CreateReferenceAtLocation` runtime actor path
- Temporarily isolating the newly spawned PlaceAtMe ref during Stage 3.6 and
testing render/process settle behavior without changing active proxy mapping
- Using Stage 3.7 to isolate a PlaceAtMe-backed runtime proxy, preserve it
during a short settle window, and then promote it into proxy movement
- Using Stage 4 to manage one runtime proxy slot per represented remote
`playerId`, with a small max proxy cap and a single PlaceAtMe spawn attempt in
progress at a time
### External Server
The server is responsible for:
- Accepting client connections
- Assigning player IDs
- Sending welcome packets
- Receiving player state
- Broadcasting player state
- Tracking disconnects
- Broadcasting disconnect packets
### Fake Client
The fake client is responsible for:
- Connecting to the local server
- Receiving welcome packets
- Receiving broadcast transform packets
- Storing remote player state by `playerId`
- Removing remote players when disconnect packets arrive
The fake client is a temporary receiver for testing the networking lifecycle. It
does not spawn remote actors or represent playable multiplayer.
### Creation Kit Plugin
The Creation Kit side is planned later and may be responsible for:
- Test cells
- Placeholder actors
- Optional Papyrus helper scripts
- Debug objects
- Controlled test environments
## Threading Model
Networking should run separately from game update logic.
Current approach:
```text
Game update reads local player transform
Game update derives basic movement state
Game update sends thresholded transform packets around 10 Hz while moving
Game update logs local movement around 1 Hz
Networking thread receives packets
Networking thread stores remote player state
Game update reads a copied remote player snapshot
Game update resolves/logs proxy actor base diagnostics
Game update sorts remote players by playerId
Game update validates transform data and same-cell compatibility
Game update creates or reuses a runtime proxy slot per eligible remote playerId
Game update attempts at most one PlaceAtMe-backed runtime actor spawn at a time
Game update snapshots matching candidates, isolates the new actor, and waits for the settle window
Game update moves isolated Stage 4 runtime spawn candidates to the remote player's current transform
Game update stores promoted runtime actors as ObjectRefHandle values in slots
Game update logs data-only animation debug state for resolved runtime proxy slots
Game update neutralizes resolved runtime proxy actors after spawn promotion, reuse, active throttled checks, and holding
Game update suppresses exposed AI movement/pathing intent without forcing animation graph events
Game update moves each valid proxy from its assigned remote player state
Game update holds only proxies whose assigned remote player left the cell or disappeared
Game update corrects held proxy slots back to their per-slot hidden positions
Game update marks disconnected held slots reusable after the grace period
Game update may reassign reusable disconnected slots to later server playerIds
Game update may use F4TProxyRemotePlayer01REF as one placed fallback if runtime spawning fails
```
The networking receive thread does not directly modify Fallout 4 actors or game
objects. It parses newline-separated JSON, updates plain C++ remote-player
state, and leaves actor access to `F4TProxyActorController` on the game-thread
update path.
## Out Of Scope For Current Prototype
- Animation graph sync or animation application
- Combat sync
- Quest sync
- Inventory sync
- Settlement sync
- Public servers or matchmaking