# 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 player’s 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 player’s 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