Add Stage 4 runtime proxy slots (max 4)
Introduce a controller-local runtime proxy slot manager (ProxyActorSlot) and g_proxySlots map capped at kMaxRuntimeProxyActors = 4. Transition the proxy flow from a single active runtime proxy to per-remote-player slots: sorted remote snapshots, per-slot pre-spawn candidate snapshots, PlaceAtMe-backed spawn attempts (one in-progress spawn globally), isolation/settle/promotion of placed refs into slot.proxyHandle, per-slot movement smoothing/hold logic, and validation/restore of handles. Preserve the single placed fallback reference for one selected player and keep slots session-sticky (no reuse/despawn yet). Update docs (architecture.md, dev-log.md) and header/source (F4TProxyActorController.{h,cpp}) to reflect Stage 4 behavior and diagnostics.
This commit is contained in:
+56
-58
@@ -25,77 +25,79 @@ both send and receive paths:
|
||||
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 performs a one-shot
|
||||
runtime spawn in `F4TTestCell01` from the selected runtime actor base. Stage
|
||||
3.7 makes the preferred runtime spawn strategy PlaceAtMe-backed spawning:
|
||||
the plugin snapshots existing matching candidates, executes a single
|
||||
`player.placeatme <baseFormId> 1` command, isolates the newly spawned actor
|
||||
by comparing pre/post candidate FormIDs, and lets that actor settle before
|
||||
remote movement begins. The custom `F4T_RemotePlayerProxy` base is attempted
|
||||
first, with Codsworth retained only as a diagnostic fallback.
|
||||
12. On the same game-thread update path, the plugin reads a copied remote-player
|
||||
snapshot and resolves one active proxy actor. The runtime-spawned actor is
|
||||
preferred when its handle resolves and validates; otherwise the placed
|
||||
fallback reference `F4TProxyRemotePlayer01REF` is used. Proxy visual updates
|
||||
are independent from local movement log throttling: normal movement is
|
||||
smoothed toward the latest target, while cell changes, worldspace changes,
|
||||
and teleports snap directly.
|
||||
13. When the selected remote player is missing, disconnected, or not in the same
|
||||
test cell, the game-thread proxy controller moves the active proxy to a
|
||||
hidden holding position inside `F4TTestCell01` until a valid same-cell
|
||||
remote player is available again.
|
||||
14. As part of the staged runtime proxy actor manager work, the same game-thread
|
||||
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 sneak observation 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.
|
||||
14. Proxy visual updates are independent per slot. Normal movement is smoothed
|
||||
toward that player’s latest target, while `cell_change`, `worldspace_change`,
|
||||
and `teleport` snap directly. Sneak state is still observed and logged only;
|
||||
no visual crouch/sneak application is attempted.
|
||||
15. When a remote player leaves the cell or disconnects, only that player’s
|
||||
runtime proxy is moved to the hidden holding position inside
|
||||
`F4TTestCell01`. Runtime actors are not disabled, deleted, or reassigned to a
|
||||
different `playerId` during Stage 4.
|
||||
16. 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.
|
||||
17. 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.
|
||||
15. Stage 3.1 adds a temporary diagnostic layer around the single runtime proxy:
|
||||
18. 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.
|
||||
16. Stage 3.2 adds a temporary near-player visibility hold for that same single
|
||||
19. 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.
|
||||
17. Stage 3.3 adds a temporary vanilla actor-base diagnostic layer. It attempts
|
||||
20. 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.
|
||||
18. Stage 3.4 adds a temporary absolute FormID and existing-reference diagnostic
|
||||
21. 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.
|
||||
19. Stage 3.5 adds a temporary PlaceAtMe-equivalent diagnostic after manual
|
||||
22. 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.
|
||||
20. Stage 3.6 refines that temporary PlaceAtMe diagnostic layer. It snapshots
|
||||
23. 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.
|
||||
21. Stage 3.7 promotes the working PlaceAtMe-style spawn pattern into the
|
||||
24. 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 for remote
|
||||
player representation in the test cell. The placed proxy reference remains as a
|
||||
fallback path if runtime spawn, handle validation, actor validation, or lookup
|
||||
fails.
|
||||
|
||||
Stage 4 is still reserved for multiple runtime proxies and a
|
||||
`remotePlayerId -> proxy actor handle` mapping. Stage 3.7 does not add that
|
||||
mapping.
|
||||
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.
|
||||
The placed proxy reference remains as a single fallback path if runtime spawning
|
||||
fails for one selected remote player.
|
||||
|
||||
## Main Components
|
||||
|
||||
@@ -110,11 +112,11 @@ The native plugin is responsible for:
|
||||
- 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 the single active proxy actor from remote-player state on the game
|
||||
thread, preferring the runtime-spawned actor and falling back to the placed
|
||||
reference
|
||||
- Holding that active proxy actor at a hidden in-cell position when no valid
|
||||
same-cell remote player should be represented
|
||||
- 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
|
||||
- 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
|
||||
@@ -128,7 +130,7 @@ The native plugin is responsible for:
|
||||
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 and keeping Stage 4 reserved for later mapping work
|
||||
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
|
||||
@@ -138,13 +140,10 @@ The native plugin is responsible for:
|
||||
- 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 the existing
|
||||
single-proxy movement path
|
||||
|
||||
Planned later:
|
||||
|
||||
- Supporting one proxy actor per represented remote `playerId`
|
||||
- Mapping each represented remote `playerId` to its own proxy actor handle
|
||||
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
|
||||
|
||||
@@ -196,14 +195,15 @@ 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 selects the Stage 3 runtime actor base
|
||||
Game update attempts one PlaceAtMe-backed runtime actor spawn in F4TTestCell01
|
||||
Game update snapshots matching candidates, isolates the new actor, and waits for the Stage 3.7 settle window
|
||||
Older standalone Stage 3.4/3.6 diagnostics stay disabled while Stage 3.7 is active
|
||||
Game update logs temporary Stage 3.1 runtime visibility diagnostics
|
||||
Game update can temporarily hold the Stage 3.2/3.3 runtime proxy near the local player
|
||||
Game update prefers the validated runtime proxy, or falls back to F4TProxyRemotePlayer01REF
|
||||
Game update moves the selected active proxy actor and logs throttled movement 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 stores promoted runtime actors as ObjectRefHandle values in slots
|
||||
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 may use F4TProxyRemotePlayer01REF as one placed fallback if runtime spawning fails
|
||||
```
|
||||
|
||||
The networking receive thread does not directly modify Fallout 4 actors or game
|
||||
@@ -213,8 +213,6 @@ update path.
|
||||
|
||||
## Out Of Scope For Current Prototype
|
||||
|
||||
- Multiple proxy actors
|
||||
- `remotePlayerId -> proxy actor handle` mapping before Stage 4
|
||||
- Animation graph sync or animation application
|
||||
- Combat sync
|
||||
- Quest sync
|
||||
|
||||
@@ -1834,6 +1834,75 @@ movement.
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-02 - Runtime Proxy Spawning Stage 4
|
||||
|
||||
### Summary
|
||||
|
||||
Added the first runtime proxy actor manager: the plugin now keeps a
|
||||
controller-local runtime proxy slot map keyed by remote `playerId`, spawning
|
||||
proxy actors only when valid same-cell remote state needs representation.
|
||||
|
||||
### Files Changed
|
||||
|
||||
- `plugin/include/F4TProxyActorController.h`
|
||||
- `plugin/src/F4TProxyActorController.cpp`
|
||||
- `docs/architecture.md`
|
||||
- `docs/dev-log.md`
|
||||
|
||||
### Details
|
||||
|
||||
- Added `ProxyActorSlot` and `g_proxySlots`, capped by
|
||||
`kMaxRuntimeProxyActors = 4` for this test milestone.
|
||||
- Moved runtime spawning behind remote-player selection: the controller now
|
||||
reads a remote snapshot, sorts by `playerId`, validates transform data, checks
|
||||
same-cell compatibility, then creates or updates a slot for that player.
|
||||
- Converted the PlaceAtMe-backed spawn/candidate/settle flow into slot-local
|
||||
state while keeping a controller-level guard so only one PlaceAtMe spawn
|
||||
attempt runs at a time.
|
||||
- Stores promoted runtime proxies as `RE::ObjectRefHandle` values on the slot
|
||||
and revalidates the handle before movement.
|
||||
- Preserves per-proxy smoothing and snap behavior for `cell_change`,
|
||||
`worldspace_change`, and `teleport`.
|
||||
- Preserves sneak observation as per-slot logging only; no visual sneak/crouch
|
||||
application was added.
|
||||
- Holds only the affected player’s proxy when that player leaves the cell or
|
||||
disappears. Slots stay session-sticky and are not reassigned to other
|
||||
`playerId` values yet.
|
||||
- Keeps `F4TProxyRemotePlayer01REF` as a single placed fallback for one selected
|
||||
remote player if runtime spawning fails.
|
||||
- Networking, protocol, Python server, fake-client tooling, gameplay sync,
|
||||
animation sync, combat, inventory, quest, settlement, weapon/projectile, and
|
||||
interaction sync were intentionally left unchanged.
|
||||
|
||||
### Testing
|
||||
|
||||
- Ran `xmake build` from `plugin`; it succeeded.
|
||||
- Pending manual validation: launch Fallout 4 through F4SE, load
|
||||
`Fallout4Together_Test.esp`, `coc F4TTestCell01`, start the Dev Server GUI,
|
||||
add multiple fake clients, and confirm one independent runtime proxy per
|
||||
`playerId` up to the max.
|
||||
- Pending manual validation: confirm Leave Cell, Return To Cell, and disconnect
|
||||
hold/restore only the affected player’s proxy while other proxies remain
|
||||
active.
|
||||
- Pending manual validation: connect a second real Fallout 4 client and confirm
|
||||
it follows the same `RemotePlayerState[playerId]` path as GUI fake clients.
|
||||
|
||||
### Known Issues
|
||||
|
||||
- Runtime slots are session-sticky and are not reassigned after disconnect yet,
|
||||
so long sessions with many unique player IDs can exhaust the small Stage 4
|
||||
test cap.
|
||||
- Runtime actors are still held instead of safely despawned or deleted.
|
||||
- The placed fallback remains single-player only by design.
|
||||
|
||||
### Next Steps
|
||||
|
||||
- Manually validate Stage 4 with two or more fake clients in `F4TTestCell01`.
|
||||
- Add a later cleanup/reuse milestone once a safe runtime actor cleanup path is
|
||||
confirmed.
|
||||
|
||||
---
|
||||
|
||||
## Entry Template
|
||||
|
||||
Use this format for future updates:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace F4T::ProxyActorController
|
||||
{
|
||||
// Game-thread-only actor controller for the single active runtime proxy and placed fallback.
|
||||
// Game-thread-only actor controller for runtime proxy slots and the single placed fallback.
|
||||
// Networking threads must only update plain remote-player state.
|
||||
void Update();
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user