Move placed proxy from remote-player snapshot

Enable game-thread proxy actor movement driven by a copied remote-player snapshot and update related networking/validation, docs, and test client.

- Plugin: add F4T::ProxyActorController::Update() (alias UpdateSafetyTest preserved) and implement remote-state-driven movement for the single placed proxy (F4TProxyRemotePlayer01REF) in F4TTestCell01. Selects lowest available playerId, validates same-cell/worldspace for movement, and caches represented playerId. Adds helper functions (ParseHexFormId, IsRemotePlayerInSameLocation, SelectRemotePlayerToRepresent, MoveProxyToRemotePlayer) and improved throttled logging.
- Networking: tighten transform packet required-field checking (requires type, playerId, x, y, z, angleZ, cellId), add GetMissingRequiredTransformField, make movementType/worldspaceId/clientTime/serverTime optional with sensible defaults, and switch LogThrottledWarning to accept string_view.
- main.cpp: call ProxyActorController::Update on game-thread tasks.
- fake_client.py: mirror new packet validation and provide defaults for optional fields.
- Docs/dev-log/protocol/setup: update architecture, dev log, protocol, and setup notes to describe the single-proxy remote-state milestone, required packet fields, and testing checklist.

This change keeps networking thread limited to parsing/updating plain remote-player state and moves all actor access to the game-thread controller. Dynamic spawning, multiple proxies, and interpolation remain out of scope.
This commit is contained in:
2026-05-31 22:21:39 +12:00
parent 49819813da
commit 46b2fa8b9b
9 changed files with 376 additions and 48 deletions
+6 -7
View File
@@ -22,18 +22,17 @@ def warn(message: str) -> None:
def update_remote_player_state(packet: dict[str, Any]) -> None:
required_fields = (
"type",
"playerId",
"x",
"y",
"z",
"angleZ",
"movementType",
"cellId",
"worldspaceId",
)
missing_fields = [field_name for field_name in required_fields if field_name not in packet]
if missing_fields:
warn(f"Ignoring transform packet missing {', '.join(missing_fields)}: {packet}")
warn(f"Ignoring transform packet missing required field {missing_fields[0]}: {packet}")
return
try:
@@ -52,11 +51,11 @@ def update_remote_player_state(packet: dict[str, Any]) -> None:
"y": y,
"z": z,
"angleZ": angle_z,
"movementType": packet["movementType"],
"movementType": packet.get("movementType", "normal"),
"cellId": packet["cellId"],
"worldspaceId": packet["worldspaceId"],
"clientTime": packet.get("clientTime"),
"serverTime": packet.get("serverTime"),
"worldspaceId": packet.get("worldspaceId", ""),
"clientTime": packet.get("clientTime", 0.0),
"serverTime": packet.get("serverTime", 0.0),
"lastReceivedLocalTime": time.time(),
}