diff --git a/codebase_map.md b/codebase_map.md index 79c4d7c..127c405 100644 --- a/codebase_map.md +++ b/codebase_map.md @@ -9,15 +9,19 @@ This document provides a comprehensive mapping of all the classes, functions, an - [Architecture & Interaction Diagrams](#-architecture--interaction-diagrams) - [High-Level Core System](#1-high-level-core-system-diagram) - [Client Handoff Flow](#2-client-handoff-flow-diagram) - - [MITM Sniffer Architecture](#3-mitm-sniffer-architecture-diagram) + - [Spectator Watch Flow](#3-spectator-watch-flow-diagram) + - [MITM Sniffer Architecture](#4-mitm-sniffer-architecture-diagram) +- [Cross-Cutting Behavior](#-cross-cutting-behavior) - [Detailed File Mapping](#-detailed-file-mapping) - [Root Scripts & Configuration](#1-root-scripts--configuration) — `src/index.js`, `src/config.js` - - [session — Session State Machine](#2-session--session-state-machine) — `SessionManager`, `ServerConnection`, `ChunkAckManager`, `MovementRelay`, `handoffFlow` + - [session — Session State Machine](#2-session--session-state-machine) — `SessionManager`, `ServerConnection`, `BotIdleBehavior`, `ChunkAckManager`, `MovementRelay`, `handoffFlow` - [proxy — Client Connection Proxy](#3-proxy--client-connection-proxy) — `ProxyServer`, `ClientBridge` - - [state — World State Caching](#4-state--world-state-caching) — `WorldStateCache`, `ChunkCache`, `EntityCache`, `PlayerStateCache`, `InventoryCache`, `MiscCache`, `JoinSyncCache`, `WorldBorderCache`, `ScoreboardCache` - - [replay — Client Handoff Replay](#5-replay--client-handoff-replay) — `StateReplayer`, `replayChunks`, `replayHelpers` - - [utils — Helper Utilities](#6-utils--helper-utilities) — `angles`, `chatRelay`, `clientDisconnect`, `handoffSync`, `logger`, `positionSync` - - [sniffer — MITM Packet Sniffer](#7-sniffer--mitm-packet-sniffer) — `MitmProxy`, `TransparentProxy`, `StreamTap`, `PacketLog`, and relay modules + - [spectator — Watch-Only Multi-Client Proxy](#4-spectator--watch-only-multi-client-proxy) — `SpectatorProxyServer`, `SpectatorHub`, `spectatorPackets` + - [state — World State Caching](#5-state--world-state-caching) — `WorldStateCache`, `ChunkCache`, `chunkMerge`, entity/player/inventory/misc caches + - [replay — Client Handoff Replay](#6-replay--client-handoff-replay) — `StateReplayer`, `replayChunks`, `replayHelpers` + - [utils — Helper Utilities](#7-utils--helper-utilities) — `angles`, `chatRelay`, `clientDisconnect`, `handoffSync`, `logger`, `positionSync` + - [constants — Shared Packet Sets](#8-constants--shared-packet-sets) — `rawPackets`, `spectatorPackets` + - [sniffer — MITM Packet Sniffer](#9-sniffer--mitm-packet-sniffer) — `MitmProxy`, `TransparentProxy`, `StreamTap`, `PacketLog`, and relay modules --- @@ -25,7 +29,7 @@ This document provides a comprehensive mapping of all the classes, functions, an ### 1. High-Level Core System Diagram -This diagram shows how the core components of FlayerProxy (`SessionManager`, `ProxyServer`, `ServerConnection`, `WorldStateCache`, `StateReplayer`, and `ClientBridge`) cooperate to manage the proxy's dual-mode lifecycle. +This diagram shows how the core components cooperate across **play** (single client, port 25566), **spectator** (multi-client watch-only, port 25568), and **bot mode** (idle behaviour when no player is connected). ```mermaid graph TD @@ -33,31 +37,42 @@ graph TD classDef helper fill:#f5f0d4,stroke:#7a6b1a,stroke-width:1px; classDef external fill:#e1d5e7,stroke:#9673a6,stroke-width:1px; - Client["Minecraft Client (Port 25566)"]:::external + Client["Play Client (25566, max 1)"]:::external + Specs["Spectator Clients (25568, max N)"]:::external Server["Target Minecraft Server"]:::external subgraph FlayerProxy ["FlayerProxy Core"] SM["[SessionManager]"]:::main SC["[ServerConnection]"]:::main PS["[ProxyServer]"]:::main + SPS["[SpectatorProxyServer]"]:::main + SH["[SpectatorHub]"]:::main WSC["[WorldStateCache]"]:::main SR["[StateReplayer]"]:::main CB["[ClientBridge]"]:::main + IB["[BotIdleBehavior]"]:::helper end SM -->|Orchestrates| SC SM -->|Orchestrates| PS + SM -->|Orchestrates| SPS SM -->|Orchestrates| WSC SM -->|Orchestrates| SR - SM -->|Coordinates Handoff| CB + SM -->|Handoff| CB + SPS -->|on join| SH + SH -->|Uses| SR + SH -->|Fans out S2C from| SC SC -->|Holds session with| Bot["Mineflayer Bot"]:::external + SC -->|BOT_MODE| IB + IB -->|onSwing → botVisual| SH Bot -->|Connects to| Server SC -->|Captures play packets to| WSC - PS -->|Listens for incoming| Client - SR -->|Replays cache from WSC to| Client - CB -->|Pipes C2S / S2C play packets| Client - CB -->|Pipes C2S / S2C play packets| SC + PS -->|Single slot login| Client + SR -->|replay / replaySpectator| Client + SR -->|replaySpectator| Specs + CB -->|Bidirectional play| Client + CB -->|Bidirectional play| SC ``` --- @@ -79,6 +94,7 @@ sequenceDiagram Note over SM,SC: State: BOT_MODE (Bot AI holds session) Player->>PS: Connect to Proxy (Port 25566) + Note over PS: login reserves single activeClient slot PS->>SM: _onClientConnect(client) Note over SM: State -> HANDOFF SM->>SC: setBotControl(false) [Disable Bot physics/AI] @@ -110,7 +126,40 @@ sequenceDiagram --- -### 3. MITM Sniffer Architecture Diagram +### 3. Spectator Watch Flow Diagram + +Spectators connect on a **separate port** (default 25568). They receive a spectator replay, then a read-only fan-out of upstream S2C packets. Movement is blocked; the camera is locked to the bot entity. Idle arm swings are synthesized locally (the server does not echo the bot's own swing). + +```mermaid +sequenceDiagram + autonumber + actor Spec as Spectator Client + participant SPS as SpectatorProxyServer + participant SH as SpectatorHub + participant SR as StateReplayer + participant SC as ServerConnection + participant IB as BotIdleBehavior + + Note over SC: BOT_MODE (bot control + idle) + Spec->>SPS: Connect (Port 25568) + SPS->>SH: addSpectator(client) + SH->>SR: replaySpectator(client) + SR->>Spec: login, terrain, entities (spectator gamemode + camera) + SH->>Spec: camera lock + position snap to bot + SH->>SC: listen serverPacket + botVisual + loop Live watch + SC->>SH: serverPacket (chunks, entities, position, …) + SH->>Spec: forward S2C (writeRaw where needed) + IB->>SC: swingArm → emit botVisual animation + SC->>SH: botVisual animation + SH->>Spec: entity swing animation + Spec--xSH: movement C2S dropped / camera re-lock + end +``` + +--- + +### 4. MITM Sniffer Architecture Diagram The Packet Sniffer has two modes of operation: @@ -141,13 +190,51 @@ graph LR --- +## 🔑 Cross-Cutting Behavior + +Summary of policies that span multiple modules (not obvious from individual class tables). + +### Ports and connection policy + +| Port (default) | Listener | Max clients | When accepted | +| :--- | :--- | :--- | :--- | +| **25566** | [ProxyServer](file:///home/seb/flayerproxy/src/proxy/ProxyServer.js) | **1** | `BOT_MODE` only; slot reserved on `login`, released on reject/disconnect | +| **25568** | [SpectatorProxyServer](file:///home/seb/flayerproxy/src/proxy/SpectatorProxyServer.js) | **20** (configurable) | Upstream connected and not `INIT`/`HANDOFF`; allowed in `BOT_MODE` (bot control) or `CLIENT_MODE` | + +Disable spectators with `spectator.enabled: false` in `config.json`. + +### Chunk cache vs live forwarding + +| Phase | `map_chunk` behavior | +| :--- | :--- | +| **Bot session (cache)** | [ChunkCache](file:///home/seb/flayerproxy/src/state/ChunkCache.js) stores only chunks within view distance of the bot view center (`update_view_position` or player position + `update_view_distance`). Out-of-view chunks are skipped on ingest and pruned via `forgetOutsideView`. | +| **Handoff replay** | [StateReplayer](file:///home/seb/flayerproxy/src/replay/StateReplayer.js) + `getChunksForReplay()` send **only** in-view cached chunks (nearest-first). | +| **CLIENT_MODE bridge** | [ClientBridge](file:///home/seb/flayerproxy/src/proxy/ClientBridge.js) forwards **all** upstream `map_chunk` packets. May send `update_view_position` so the client accepts chunks outside its local cache radius. | + +Chunks are **server-pushed**; the bot/client mainly acks via `chunk_batch_received` and movement/view packets. + +### Block/light merge and `structuredClone` + +`map_chunk` binary fields (`chunkData`, heightmaps, etc.) must stay as Node `Buffer` instances for prismarine-chunk / smart-buffer. `structuredClone` in the cache path can turn them into `Uint8Array`, which breaks merges (`Invalid Buffer provided in SmartBufferOptions`). [chunkMerge.js](file:///home/seb/flayerproxy/src/state/chunkMerge.js) normalizes via `asBuffer()` / `normalizeMapChunkPacket()` before column load and merge. + +### Spectator watch (movement and visuals) + +- **Movement:** Vanilla spectator free-cam is largely client-side. The proxy cannot rely on dropping C2S alone; [SpectatorHub](file:///home/seb/flayerproxy/src/spectator/SpectatorHub.js) locks the camera to the bot entity (`camera` packet), snaps position on movement C2S, and runs a 1s correction loop. +- **Idle swing:** The server does not echo the bot’s own arm swing. [BotIdleBehavior](file:///home/seb/flayerproxy/src/session/BotIdleBehavior.js) → [ServerConnection._emitBotSwingAnimation](file:///home/seb/flayerproxy/src/session/ServerConnection.js) → `botVisual` → spectator `animation` fan-out. + +### Bot idle (no play client) + +While in `BOT_MODE` with bot control enabled, [BotIdleBehavior](file:///home/seb/flayerproxy/src/session/BotIdleBehavior.js) runs random look / sneak / swing on a timer (`config.bot.antiAfk*`). Started/stopped with [ServerConnection.setBotControl](file:///home/seb/flayerproxy/src/session/ServerConnection.js). + +--- + ## 🗂️ Detailed File Mapping ### 1. Root Scripts & Configuration #### 📄 [src/index.js](file:///home/seb/flayerproxy/src/index.js) -The main entry point for the application. Loads system config and handles errors, starts the core [SessionManager](file:///home/seb/flayerproxy/src/session/SessionManager.js), hooks process event signals (`SIGINT`, `SIGTERM`) for a graceful shutdown, and captures `uncaughtException` / `unhandledRejection` warnings. +The main entry point for the application. Loads system config, logs play and spectator proxy ports, starts [SessionManager](file:///home/seb/flayerproxy/src/session/SessionManager.js), hooks `SIGINT`/`SIGTERM` for graceful shutdown, and captures `uncaughtException` / `unhandledRejection`. | Function | Description | |---|---| @@ -159,7 +246,9 @@ Handles configuration loading, validation, and parsing. | Function | Description | |---|---| -| `loadConfig()` | Synchronously reads `config.json`, validates host, port, version, and auth configuration, applies default options, and returns the configuration object. | +| `loadConfig()` | Synchronously reads `config.json`, validates host, port, version, and auth configuration, applies defaults for `proxy`, `spectator`, `bot` (anti-Afk intervals), and `cache`, and returns the configuration object. | + +**Default config sections:** `proxy` (port 25566, `maxClients: 1`), `spectator` (port 25568, `maxClients: 20`, `enabled: true`), `bot` (`antiAfk`, `antiAfkMinInterval`, `antiAfkMaxInterval`, `viewDistance`). --- @@ -171,14 +260,17 @@ Orchestrates the dual-mode proxy state machine: `INIT` ↔ `BOT_MODE` ↔ `HANDO | Method | Description | |---|---| -| `constructor(config)` | Initializes state machine with configuration. | -| `start()` | Establishes connections to the upstream Minecraft server and starts the proxy server. | +| `constructor(config)` | Initializes state machine, play proxy, optional spectator proxy/hub, and replayer. | +| `start()` | Connects upstream bot, starts play proxy (25566), and spectator proxy (25568) if enabled. | | `_scheduleReconnect(delaySec)` | Schedules a timed reconnect sequence if the connection is lost. | | `_setupServerEvents()` | Hooks upstream server events (`connected`, `disconnected`, `kicked`, `error`, `death`, `respawn`). | | `_primeChunksNearBot()` | Triggers server movement packets to verify that the chunk cache is loaded near the bot before handing off. | | `_refreshClientAfterBotRespawn()` | Re-aligns position and client view in case the bot respawns while a client is connected. | -| `_onClientConnect(client)` | Initiates the handoff sequence to transition from `BOT_MODE` to `CLIENT_MODE`. | -| `_cleanupClient()` | Stops the packet bridge and cleans up client-related event listeners. | +| `_clientSlotStatus()` | Returns whether the play port may accept a new login (single client, `BOT_MODE` only). | +| `_spectatorSlotStatus()` | Returns whether the spectator port may accept logins (connected, not `INIT`/`HANDOFF`; `BOT_MODE` with bot control or `CLIENT_MODE`). | +| `_rejectClient(client, reason)` | Kicks play client and releases `ProxyServer.activeClient` slot. | +| `_onClientConnect(client)` | Validates play slot, then handoff `BOT_MODE` → `CLIENT_MODE`. | +| `_cleanupClient()` | Stops bridge, releases play client slot, clears `currentClient`. | | `_transitionTo(newState)` | Transitions the machine state and logs status summaries. | | `stop()` | Gracefully halts all services. | @@ -192,8 +284,9 @@ Manages the persistent Mineflayer bot connection to the target server. | `connect()` | Spawns the bot connection via Mineflayer. | | `_setupConfigCapture()` | Caches configuration-phase registry data and tags. | | `_setupPacketCapture()` | Hooks play packets and routes them directly to the state cache. | -| `_setupBotEvents()` | Listens for bot lifecycle events (`spawn`, `end`, `kicked`, `error`, `death`, and chat logging). | -| `setBotControl(enabled)` | Enables/disables physics and AI on the Mineflayer bot. | +| `_setupBotEvents()` | Listens for bot lifecycle events; creates [BotIdleBehavior](file:///home/seb/flayerproxy/src/session/BotIdleBehavior.js) on spawn. | +| `_emitBotSwingAnimation(hand)` | Emits `botVisual` (`animation` packet) so spectators see idle swings (server does not echo self-swing). | +| `setBotControl(enabled)` | Enables/disables physics; starts/stops [BotIdleBehavior](file:///home/seb/flayerproxy/src/session/BotIdleBehavior.js). | | `setClientDrivesChunkBatchAck(clientDrives)` | Delegates chunk batch acknowledgement control between client and Mineflayer. | | `flushChunkBatchAck()` | Unblocks the server chunk sender. | | `refreshProxyClientPermissions(client)` | Sends player permissions status packets. | @@ -204,6 +297,8 @@ Manages the persistent Mineflayer bot connection to the target server. | `writeToServer(name, data)` | Writes raw packets directly upstream. | | `disconnect()` | Safely disconnects the bot. | +**Events:** `connected`, `disconnected`, `kicked`, `error`, `death`, `respawn`, `serverPacket` `(name, data, buffer)`, `botVisual` `(name, data)` (synthetic S2C for spectators). + #### 🧩 [ChunkAckManager](file:///home/seb/flayerproxy/src/session/ChunkAckManager.js) `class` Intercepts Mineflayer's chunk batch acknowledgement listeners to prevent double-acknowledging packets during the client session. @@ -229,6 +324,20 @@ Intercepts Mineflayer's chunk batch acknowledgement listeners to prevent double- |---|---| | `performHandoff({...})` | Coordinates the sequential handoff sequence: installs temporary upstream forwarding rules, primes nearby chunks, triggers the [StateReplayer](file:///home/seb/flayerproxy/src/replay/StateReplayer.js), aligns player coordinates/permissions, and spawns the [ClientBridge](file:///home/seb/flayerproxy/src/proxy/ClientBridge.js). | +#### 🧩 [BotIdleBehavior](file:///home/seb/flayerproxy/src/session/BotIdleBehavior.js) `class` + +Random look / sneak / swing while the bot holds the session (`BOT_MODE`, no play client). Started from [ServerConnection.setBotControl(true)](file:///home/seb/flayerproxy/src/session/ServerConnection.js). + +| Method | Description | +|---|---| +| `constructor(bot, botConfig, hooks)` | Optional `onSwing(hand)` hook for spectator animation relay. | +| `start()` / `stop()` | Enables/disables timed idle actions (`config.bot.antiAfk`). | +| `_scheduleNext()` | Random delay between `antiAfkMinInterval` and `antiAfkMaxInterval`. | +| `_tick()` | Picks random action: look, sneak, or swing. | +| `_randomLook()` | `bot.look()` with small yaw/pitch delta. | +| `_randomSneak()` | Toggles sneak via `setControlState` for a random duration. | +| `_randomSwing()` | `bot.swingArm()` then `onSwing` callback. | + --- ### 3. proxy — Client Connection Proxy @@ -239,10 +348,11 @@ Listens for connection attempts from standard Minecraft Java clients. | Method | Description | |---|---| -| `constructor(config, onClientConnect, worldState)` | Initializes proxy server with config, client callback, and world state. | -| `start()` | Initializes the local minecraft-protocol server (`mc.createServer`), configures pre-login listeners to replay raw config packets, and registers joining players. | +| `constructor(config, onClientConnect, worldState, canAcceptClient)` | Play proxy; optional `canAcceptClient()` gate from [SessionManager](file:///home/seb/flayerproxy/src/session/SessionManager.js). | +| `releaseClient(client)` | Clears `activeClient` if it matches (used on reject/disconnect). | +| `start()` | `mc.createServer` on `config.proxy.port`; **reserves single slot on `login`**; replays raw config on `login_acknowledged`; `playerJoin` only for reserved client. | | `updateRegistryCodec(codec)` | Replaces the registry codec object in the protocol handler options. | -| `stop()` | Halts client listening sockets. | +| `stop()` | Disconnects active client and closes listener. | #### 🧩 [ClientBridge](file:///home/seb/flayerproxy/src/proxy/ClientBridge.js) `class` @@ -261,9 +371,59 @@ Manages bidirectional packet pipelines when in `CLIENT_MODE`. | `_shouldForwardPlayerInfo(data)` | Filters latency updates for unknown players. | | `stop()` | Tears down the forwarding pipe. | +**Play client forwarding:** All upstream `map_chunk` packets are forwarded (no radius filter). `update_view_position` may be sent before chunks outside the client cache radius. + --- -### 4. state — World State Caching +### 4. spectator — Watch-Only Multi-Client Proxy + +#### 🧩 [SpectatorProxyServer](file:///home/seb/flayerproxy/src/proxy/SpectatorProxyServer.js) `class` + +Separate `mc.createServer` on `config.spectator.port` (default **25568**). Allows **multiple** simultaneous spectators; no upstream connection per spectator. + +| Method | Description | +|---|---| +| `constructor(config, hub, worldState, canAcceptClient)` | Spectator listener with slot/status callback from [SessionManager._spectatorSlotStatus](file:///home/seb/flayerproxy/src/session/SessionManager.js). | +| `start()` | Binds port, replays raw config packets, calls `hub.addSpectator` on `playerJoin`. | +| `updateRegistryCodec(codec)` | Same registry injection as play proxy. | +| `stop()` | Closes spectator listener. | + +#### 🧩 [SpectatorHub](file:///home/seb/flayerproxy/src/spectator/SpectatorHub.js) `class` + +Manages spectator clients: replay, S2C fan-out, movement block, camera lock. + +| Method | Description | +|---|---| +| `constructor(serverConn, worldState, replayer, config)` | Subscribes to `serverConn` `serverPacket` and `botVisual` events. | +| `addSpectator(client)` | `replaySpectator`, camera lock, position snap, join fan-out set. | +| `removeSpectator(client)` | Removes client; detaches fan-out when last spectator leaves. | +| `kickAll(reason)` / `stop()` | Disconnects all spectators; removes listeners. | +| `_installClientGuard(client, state)` | Whitelist C2S only; on movement packets, re-lock camera + snap position. | +| `_lockCamera(client)` | Sends `camera` with bot `entityId`. | +| `_snapPosition(client, state)` | Sends clientbound `position` from bot entity. | +| `_startSnapLoop()` / `_stopSnapLoop()` | Periodic camera/position correction (1s). | +| `_forwardToSpectators(name, data, buffer)` | Fans upstream S2C to all spectators (`writeRaw` for chunk packets). | + +#### 📄 [spectatorPackets.js](file:///home/seb/flayerproxy/src/constants/spectatorPackets.js) `constants` + +| Export | Description | +|---|---| +| `SPECTATOR_GAMEMODE` | Gamemode id `3` for replay. | +| `SPECTATOR_ALLOWED_C2S` | Whitelist: `chunk_batch_received`, `teleport_confirm`, `keep_alive`, etc. | +| `SPECTATOR_BLOCKED_S2C` | Dropped S2C fan-out (e.g. `tracked_waypoint` — mid-join UPDATE without prior TRACK crashes clients). | +| `SPECTATOR_MOVEMENT_C2S` | Movement packets that trigger camera re-lock. | +| `ANIMATION_SWING_MAIN_HAND` / `ANIMATION_SWING_OFF_HAND` | Clientbound `animation` ids for idle swing relay. | + +**ServerConnection events used by spectators:** + +| Event | Payload | Description | +|---|---|---| +| `serverPacket` | `(name, data, buffer)` | Same stream as play cache (from upstream bot). | +| `botVisual` | `(name, data)` | Synthetic S2C (e.g. idle `animation`) not echoed by server. | + +--- + +### 5. state — World State Caching #### 🧩 [WorldStateCache](file:///home/seb/flayerproxy/src/state/WorldStateCache.js) `class` @@ -278,27 +438,41 @@ Master coordinator for world cache segments. Integrates and clears sub-caches on | `getRawConfigPacketsForReplay()` | Filters and returns config-phase buffers for client replay. | | `hasRawConfigPackets()` | Returns `true` if raw configuration buffers are cached. | | `handleServerPacket(name, data, buffer)` | Evaluates incoming play packets and routes them to sub-caches. | +| `_getChunkViewContext()` | Resolves view center (`update_view_position` or player position) + view distance for cache retention. | +| `_forgetChunksOutsideView()` | Drops cached chunks outside [isChunkWithinViewDistance](file:///home/seb/flayerproxy/src/utils/positionSync.js). | | `getSummary()` | Returns cache sizing info for log monitoring. | | `clear()` | Wipes all cached structures. | #### 🧩 [ChunkCache](file:///home/seb/flayerproxy/src/state/ChunkCache.js) `class` -Manages loaded map chunks, light maps, and block overlays using an LRU cache. +Manages loaded map chunks, light maps, and block overlays using an LRU cache. Merges block/light updates via [chunkMerge.js](file:///home/seb/flayerproxy/src/state/chunkMerge.js) (prismarine-chunk columns). | Method | Description | |---|---| -| `constructor(maxChunks)` | Initializes chunk storage with LRU capacity limit. | +| `constructor(maxChunks, options)` | LRU storage; `version` + `getWorldBounds()` for column decode. | | `_key(x, z)` | Computes string key for map lookups. | -| `handleMapChunk(data, rawBuffer)` | Caches chunk data and marks it as active in the LRU tracking queue. | -| `handleUpdateLight(data, rawBuffer)` | Caches light updates. | +| `forgetOutsideView(centerChunkX, centerChunkZ, viewDistance)` | Removes chunks outside server `ChunkTrackingView` distance. | +| `handleMapChunk(data, rawBuffer, view?)` | Skips store if outside view; prunes cache; normalizes Buffers after `structuredClone`. | +| `handleUpdateLight(data)` | Merges light into cached column via `applyUpdateLight`. | | `handleUnloadChunk(data)` | Evicts chunk records from cache. | -| `handleBlockChange(data)` | Appends single block changes as an overlay. | -| `handleMultiBlockChange(data)` | Appends multiblock edits as an overlay. | -| `_buildChunkEntry(chunkData)` | Assembles raw data, block edits, and light arrays. | -| `getChunksForReplay(centerChunkX, centerChunkZ, viewDistance)` | Returns cached chunks sorting closest-first. | +| `handleBlockChange(data)` | Merges single block into column; logs merge failures. | +| `handleMultiBlockChange(data)` | Merges section block records into column. | +| `_ensureColumn(stored)` | Lazy-loads prismarine column from `map_chunk` packet data. | +| `_syncPacketFromColumn(stored)` | Re-exports merged column to `packetData`. | +| `getChunksForReplay(centerChunkX, centerChunkZ, viewDistance)` | Returns in-view chunks only (same distance test as forget), nearest-first. | | `hasChunkAtBlock(x, z)` | Verifies if a chunk is loaded. | | `clear()` | Wipes chunk maps. | +#### 📄 [chunkMerge.js](file:///home/seb/flayerproxy/src/state/chunkMerge.js) `functions` + +| Function | Description | +|---|---| +| `asBuffer(value)` | Coerces `Buffer` / `Uint8Array` for prismarine-chunk / smart-buffer. | +| `normalizeMapChunkPacket(packet)` | Fixes binary fields after `structuredClone`. | +| `loadColumnFromMapChunk(packet, version, worldBounds)` | Builds prismarine column from `map_chunk`. | +| `exportMapChunkPacket(column, packet)` | Dumps column back to `map_chunk` shape. | +| `applyBlockChange` / `applyUpdateLight` / `applyMultiBlockChange` | Merge incremental updates into column. | + #### 🧩 [EntityCache](file:///home/seb/flayerproxy/src/state/EntityCache.js) `class` Tracks entities, positions, gear, and status effects. @@ -427,16 +601,17 @@ Caches teams, scores, objectives, and scoreboard layouts. --- -### 5. replay — Client Handoff Replay +### 6. replay — Client Handoff Replay #### 🧩 [StateReplayer](file:///home/seb/flayerproxy/src/replay/StateReplayer.js) `class` -Replays the cached world state to a connecting client. +Replays the cached world state to a connecting play or spectator client. | Method | Description | |---|---| | `constructor(worldState, serverConn)` | Initializes replayer with world state and server connection references. | -| `replay(client)` | Coordinates sequential packet delivery (see replay sequence below). | +| `replaySpectator(client)` | Calls `replay(client, { spectator: true })` — gamemode 3, `camera` lock, no inventory. | +| `replay(client, options)` | Sequential packet delivery; `options.spectator` adjusts abilities/gamemode/camera. | **`replay()` sequence:** @@ -471,9 +646,11 @@ Replays the cached world state to a connecting client. | `splitMiscReplayPackets(packets)` | Splits misc packets into early configurations, level coordinates, and weather variables. | | `waitForClientTeleportConfirm(client)` | Awaits the client's `teleport_confirm` packet. | +**Spectator replay differences:** Sends `game_state_change` (gamemode 3), `camera` (bot entity id), flying ability flags; skips full inventory and permission `entity_status`. + --- -### 6. utils — Helper Utilities +### 7. utils — Helper Utilities #### 📄 [angles.js](file:///home/seb/flayerproxy/src/utils/angles.js) `functions` @@ -528,7 +705,21 @@ Replays the cached world state to a connecting client. --- -### 7. sniffer — MITM Packet Sniffer +### 8. constants — Shared Packet Sets + +#### 📄 [rawPackets.js](file:///home/seb/flayerproxy/src/constants/rawPackets.js) + +| Export | Description | +|---|---| +| `RAW_FORWARD_PACKETS` | Play packets forwarded with `writeRaw` (chunks, lights, view position, batch markers). Used by [ClientBridge](file:///home/seb/flayerproxy/src/proxy/ClientBridge.js) and [SpectatorHub](file:///home/seb/flayerproxy/src/spectator/SpectatorHub.js). | + +#### 📄 [spectatorPackets.js](file:///home/seb/flayerproxy/src/constants/spectatorPackets.js) + +See [spectator — Watch-Only Multi-Client Proxy](#4-spectator--watch-only-multi-client-proxy). + +--- + +### 9. sniffer — MITM Packet Sniffer #### 📄 [src/sniffer/index.js](file:///home/seb/flayerproxy/src/sniffer/index.js) diff --git a/src/constants/spectatorPackets.js b/src/constants/spectatorPackets.js index ae94702..855a1de 100644 --- a/src/constants/spectatorPackets.js +++ b/src/constants/spectatorPackets.js @@ -14,6 +14,13 @@ const SPECTATOR_ALLOWED_C2S = new Set([ 'ping_request', ]); +/** + * S2C packets not forwarded to spectators. + * tracked_waypoint (journeys/locator bar) is session-ordered (track → update); + * mid-join spectators only see updates and disconnect. + */ +const SPECTATOR_BLOCKED_S2C = new Set(['tracked_waypoint']); + /** Movement-ish C2S — trigger camera lock + position snap when received. */ const SPECTATOR_MOVEMENT_C2S = new Set([ 'position', @@ -33,6 +40,7 @@ const SPECTATOR_MOVEMENT_C2S = new Set([ module.exports = { SPECTATOR_GAMEMODE, SPECTATOR_ALLOWED_C2S, + SPECTATOR_BLOCKED_S2C, SPECTATOR_MOVEMENT_C2S, ANIMATION_SWING_MAIN_HAND, ANIMATION_SWING_OFF_HAND, diff --git a/src/spectator/SpectatorHub.js b/src/spectator/SpectatorHub.js index 66d8365..c217e09 100644 --- a/src/spectator/SpectatorHub.js +++ b/src/spectator/SpectatorHub.js @@ -2,6 +2,7 @@ const { createLogger } = require('../utils/logger'); const { RAW_FORWARD_PACKETS } = require('../constants/rawPackets'); const { SPECTATOR_ALLOWED_C2S, + SPECTATOR_BLOCKED_S2C, SPECTATOR_MOVEMENT_C2S, } = require('../constants/spectatorPackets'); const { @@ -191,6 +192,7 @@ class SpectatorHub { _forwardToSpectators(name, data, buffer) { if (this._spectators.size === 0) return; + if (SPECTATOR_BLOCKED_S2C.has(name)) return; const block = this._botBlockCoords(); const viewDistance = this._getViewDistance();