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.
|
9. The server broadcasts `disconnect` packets when clients disconnect.
|
||||||
10. The plugin and fake client remove disconnected players from their remote
|
10. The plugin and fake client remove disconnected players from their remote
|
||||||
player tables.
|
player tables.
|
||||||
11. On the game-thread update path, the proxy controller performs a one-shot
|
11. On the game-thread update path, the proxy controller reads a copied
|
||||||
runtime spawn in `F4TTestCell01` from the selected runtime actor base. Stage
|
remote-player snapshot before attempting any actor work. The snapshot is
|
||||||
3.7 makes the preferred runtime spawn strategy PlaceAtMe-backed spawning:
|
sorted by `playerId`, and only remote players with valid transform data in
|
||||||
the plugin snapshots existing matching candidates, executes a single
|
the same cell are eligible for runtime proxy representation.
|
||||||
`player.placeatme <baseFormId> 1` command, isolates the newly spawned actor
|
12. For each eligible remote `playerId`, the controller maintains a
|
||||||
by comparing pre/post candidate FormIDs, and lets that actor settle before
|
`remotePlayerId -> runtime proxy slot` mapping. Each slot stores its
|
||||||
remote movement begins. The custom `F4T_RemotePlayerProxy` base is attempted
|
`ObjectRefHandle`, lifecycle state, PlaceAtMe-backed spawn/candidate state,
|
||||||
first, with Codsworth retained only as a diagnostic fallback.
|
movement smoothing state, and sneak observation state. Stage 4 caps runtime
|
||||||
12. On the same game-thread update path, the plugin reads a copied remote-player
|
proxy slots at four actors for testing.
|
||||||
snapshot and resolves one active proxy actor. The runtime-spawned actor is
|
13. Runtime proxies are demand-driven. A slot executes the Stage 3.7
|
||||||
preferred when its handle resolves and validates; otherwise the placed
|
PlaceAtMe-backed spawn flow only when that remote player needs a same-cell
|
||||||
fallback reference `F4TProxyRemotePlayer01REF` is used. Proxy visual updates
|
representation, and only one PlaceAtMe spawn attempt may run at a time so
|
||||||
are independent from local movement log throttling: normal movement is
|
candidate isolation remains unambiguous.
|
||||||
smoothed toward the latest target, while cell changes, worldspace changes,
|
14. Proxy visual updates are independent per slot. Normal movement is smoothed
|
||||||
and teleports snap directly.
|
toward that player’s latest target, while `cell_change`, `worldspace_change`,
|
||||||
13. When the selected remote player is missing, disconnected, or not in the same
|
and `teleport` snap directly. Sneak state is still observed and logged only;
|
||||||
test cell, the game-thread proxy controller moves the active proxy to a
|
no visual crouch/sneak application is attempted.
|
||||||
hidden holding position inside `F4TTestCell01` until a valid same-cell
|
15. When a remote player leaves the cell or disconnects, only that player’s
|
||||||
remote player is available again.
|
runtime proxy is moved to the hidden holding position inside
|
||||||
14. As part of the staged runtime proxy actor manager work, the same game-thread
|
`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
|
proxy controller also logs whether `Fallout4Together_Test.esp` is loaded and
|
||||||
whether the actor base `F4T_RemotePlayerProxy` resolves by editor ID.
|
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
|
post-spawn visibility data, loaded-3D pointer checks where safe, console
|
||||||
helper logs, active source logs, and throttled post-movement diagnostics.
|
helper logs, active source logs, and throttled post-movement diagnostics.
|
||||||
The diagnostic mode can force the runtime proxy near the local player to
|
The diagnostic mode can force the runtime proxy near the local player to
|
||||||
prove whether the created actor is visibly renderable.
|
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,
|
runtime proxy. During the hold, remote state is still selected and validated,
|
||||||
but runtime proxy restore and remote movement are deferred long enough to
|
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
|
test whether a runtime-created actor ref can load visible 3D while kept near
|
||||||
the local player.
|
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
|
to resolve Codsworth as a visible vanilla `TESNPC` candidate, then passes
|
||||||
that actor base into the same runtime spawn, handle validation, near-player
|
that actor base into the same runtime spawn, handle validation, near-player
|
||||||
hold, and visibility diagnostic path. This isolates custom actor-base issues
|
hold, and visibility diagnostic path. This isolates custom actor-base issues
|
||||||
from runtime-spawn/init issues without changing the final architecture.
|
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
|
layer. It compares Codsworth base-form runtime spawning against moving the
|
||||||
existing placed Codsworth reference near the local player as a visibility
|
existing placed Codsworth reference near the local player as a visibility
|
||||||
control. This separates runtime actor creation/init problems from vanilla
|
control. This separates runtime actor creation/init problems from vanilla
|
||||||
actor-base or placed-reference movement problems and does not change the
|
actor-base or placed-reference movement problems and does not change the
|
||||||
final architecture.
|
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
|
console validation proved `player.placeatme 000179FF 1` can spawn visible
|
||||||
Codsworth in `F4TTestCell01`. The plugin executes that console placement path
|
Codsworth in `F4TTestCell01`. The plugin executes that console placement path
|
||||||
from the local player, resolves the newly created Codsworth actor near the
|
from the local player, resolves the newly created Codsworth actor near the
|
||||||
player, and holds/logs it separately from the `CreateReferenceAtLocation`
|
player, and holds/logs it separately from the `CreateReferenceAtLocation`
|
||||||
runtime proxy path.
|
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
|
known Codsworth refs before executing `player.placeatme 000179FF 1`, accepts
|
||||||
only a newly isolated Codsworth ref for the primary diagnostic, and preserves
|
only a newly isolated Codsworth ref for the primary diagnostic, and preserves
|
||||||
that actor at the original console-spawned location during a short settle
|
that actor at the original console-spawned location during a short settle
|
||||||
window. This tests whether the actor becomes visible naturally or requires a
|
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
|
render/process refresh such as the manually observed pause-menu or alt-tab
|
||||||
behavior. Stage 3.6 does not change the final architecture.
|
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
|
preferred single runtime proxy spawn path. The proxy controller uses the
|
||||||
resolved runtime FormID for `F4T_RemotePlayerProxy` when available, falls
|
resolved runtime FormID for `F4T_RemotePlayerProxy` when available, falls
|
||||||
back to Codsworth only for diagnostics, defers active proxy selection during
|
back to Codsworth only for diagnostics, defers active proxy selection during
|
||||||
candidate isolation and settle, then promotes the settled actor into the
|
candidate isolation and settle, then promotes the settled actor into the
|
||||||
existing single-proxy movement pipeline.
|
existing single-proxy movement pipeline.
|
||||||
|
|
||||||
The Fallout 4 plugin now dynamically spawns one runtime proxy actor for remote
|
The Fallout 4 plugin now dynamically spawns one runtime proxy actor per
|
||||||
player representation in the test cell. The placed proxy reference remains as a
|
represented remote `playerId` in the test cell, up to a small Stage 4 test cap.
|
||||||
fallback path if runtime spawn, handle validation, actor validation, or lookup
|
The placed proxy reference remains as a single fallback path if runtime spawning
|
||||||
fails.
|
fails for one selected remote player.
|
||||||
|
|
||||||
Stage 4 is still reserved for multiple runtime proxies and a
|
|
||||||
`remotePlayerId -> proxy actor handle` mapping. Stage 3.7 does not add that
|
|
||||||
mapping.
|
|
||||||
|
|
||||||
## Main Components
|
## Main Components
|
||||||
|
|
||||||
@@ -110,11 +112,11 @@ The native plugin is responsible for:
|
|||||||
- Throttling normal transform sends separately from readable local movement logs
|
- Throttling normal transform sends separately from readable local movement logs
|
||||||
- Receiving server packets on a background thread
|
- Receiving server packets on a background thread
|
||||||
- Storing assigned and remote `playerId` state internally
|
- Storing assigned and remote `playerId` state internally
|
||||||
- Moving the single active proxy actor from remote-player state on the game
|
- Moving runtime proxy actors from per-`playerId` remote-player state on the
|
||||||
thread, preferring the runtime-spawned actor and falling back to the placed
|
game thread, preferring runtime slots and falling back to the placed reference
|
||||||
reference
|
for one selected player only if runtime spawning fails
|
||||||
- Holding that active proxy actor at a hidden in-cell position when no valid
|
- Holding only the affected proxy actor at a hidden in-cell position when its
|
||||||
same-cell remote player should be represented
|
remote player disconnects or leaves the same cell
|
||||||
- Logging staged runtime proxy actor base lookup diagnostics on the same
|
- Logging staged runtime proxy actor base lookup diagnostics on the same
|
||||||
game-thread controller path
|
game-thread controller path
|
||||||
- Attempting one game-thread-only PlaceAtMe-backed runtime spawn from the
|
- 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
|
can load visible 3D before remote movement resumes
|
||||||
- Temporarily comparing a vanilla visible NPC actor base against the custom
|
- Temporarily comparing a vanilla visible NPC actor base against the custom
|
||||||
proxy actor base during Stage 3.3, while preserving the same runtime spawn
|
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
|
- Temporarily resolving Codsworth base and placed-reference absolute FormIDs
|
||||||
during Stage 3.4, then moving the existing placed reference near the player as
|
during Stage 3.4, then moving the existing placed reference near the player as
|
||||||
a visibility control separate from runtime spawning
|
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
|
- Temporarily isolating the newly spawned PlaceAtMe ref during Stage 3.6 and
|
||||||
testing render/process settle behavior without changing active proxy mapping
|
testing render/process settle behavior without changing active proxy mapping
|
||||||
- Using Stage 3.7 to isolate a PlaceAtMe-backed runtime proxy, preserve it
|
- 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
|
during a short settle window, and then promote it into proxy movement
|
||||||
single-proxy movement path
|
- 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
|
||||||
Planned later:
|
progress at a time
|
||||||
|
|
||||||
- Supporting one proxy actor per represented remote `playerId`
|
|
||||||
- Mapping each represented remote `playerId` to its own proxy actor handle
|
|
||||||
|
|
||||||
### External Server
|
### External Server
|
||||||
|
|
||||||
@@ -196,14 +195,15 @@ Networking thread receives packets
|
|||||||
Networking thread stores remote player state
|
Networking thread stores remote player state
|
||||||
Game update reads a copied remote player snapshot
|
Game update reads a copied remote player snapshot
|
||||||
Game update resolves/logs proxy actor base diagnostics
|
Game update resolves/logs proxy actor base diagnostics
|
||||||
Game update selects the Stage 3 runtime actor base
|
Game update sorts remote players by playerId
|
||||||
Game update attempts one PlaceAtMe-backed runtime actor spawn in F4TTestCell01
|
Game update validates transform data and same-cell compatibility
|
||||||
Game update snapshots matching candidates, isolates the new actor, and waits for the Stage 3.7 settle window
|
Game update creates or reuses a runtime proxy slot per eligible remote playerId
|
||||||
Older standalone Stage 3.4/3.6 diagnostics stay disabled while Stage 3.7 is active
|
Game update attempts at most one PlaceAtMe-backed runtime actor spawn at a time
|
||||||
Game update logs temporary Stage 3.1 runtime visibility diagnostics
|
Game update snapshots matching candidates, isolates the new actor, and waits for the settle window
|
||||||
Game update can temporarily hold the Stage 3.2/3.3 runtime proxy near the local player
|
Game update stores promoted runtime actors as ObjectRefHandle values in slots
|
||||||
Game update prefers the validated runtime proxy, or falls back to F4TProxyRemotePlayer01REF
|
Game update moves each valid proxy from its assigned remote player state
|
||||||
Game update moves the selected active proxy actor and logs throttled movement diagnostics
|
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
|
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
|
## Out Of Scope For Current Prototype
|
||||||
|
|
||||||
- Multiple proxy actors
|
|
||||||
- `remotePlayerId -> proxy actor handle` mapping before Stage 4
|
|
||||||
- Animation graph sync or animation application
|
- Animation graph sync or animation application
|
||||||
- Combat sync
|
- Combat sync
|
||||||
- Quest 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
|
## Entry Template
|
||||||
|
|
||||||
Use this format for future updates:
|
Use this format for future updates:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace F4T::ProxyActorController
|
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.
|
// Networking threads must only update plain remote-player state.
|
||||||
void Update();
|
void Update();
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user