--- title: "Sprint 19 — CI Briefing" description: "Test runner scripts, protocol version handshake, IPC round-trip benchmark" type: sprint status: archived sprint: 19 team: "ci" --- # Sprint 19: Persist — CI Tasks **Goal:** The player can save and resume a game session with per-game directories; the simulation tier system gains eviction and scope pinning; and the first test infrastructure ships with information boundary validation and IPC hardening. **Branch:** `ci` **Agents:** Hoshe (QA/CI), Oscar (networking) ## Carry-over from Sprint 18 None. ## New Tickets | # | Title | Blocked by | |---|-------|------------| | #270 | Test runner bash scripts | — | | #556 | Protocol version handshake: client | #555 (server) | | #342 | IPC round-trip timing benchmark | #555, #556 | | #271 | IPC serialization fixture files | #270 | Use `db/connectors/ticket show ` for full details. ## Key Decisions - `decisions/architecture.md` — D-020 (IPC architecture, MessagePack codec, SimBridge trait), D-030 (three-layer test architecture: fixture / mock-protocol / real-subprocess) ## Notes ### #270 — Test runner bash scripts The test infrastructure has no standardized entry points for CI or agents to invoke. This ticket ships the runner layer. What this ticket must deliver: Six scripts at `tests/`: 1. `tests/run-rust` — runs `cargo test` in `server/`, exits 0/non-zero, JSON stdout summary 2. `tests/run-godot` — runs GUT headlessly (`godot --headless -s client/tests/run_gut.gd`), exits 0/non-zero 3. `tests/run-ipc-fixtures` — Layer 1: reads fixture files from `tests/fixtures/`, validates via Rust + GDScript, exits 0/non-zero 4. `tests/run-ipc-protocol` — Layer 2: runs mock subprocess protocol state machine tests 5. `tests/run-ipc-integration` — Layer 3: starts real server subprocess, runs full round-trip, kills it 6. `tests/run-all` — invokes all five in order, collects exit codes, reports JSON summary Script requirements per ticket description: exit code 0/non-zero, structured JSON stdout, accepts filter arguments (`--filter test_name`), no interactive input, whitelistable for Claude Code agents (no TTY prompts). JSON stdout format (consistent across all scripts): ```json {"suite": "rust", "total": 42, "passed": 42, "failed": 0, "duration_ms": 1230} ``` These scripts are the entry points that `make ci-server`, `make ci-client`, and future CI pipelines call. Coordinate with Makefile targets in `docs/DEVOPS.md`. ### #556 — Protocol version handshake: client Blocked by #555 (server must send `HandshakeMessage` first). What this ticket must deliver: - `client/scripts/protocol/local_bridge.gd` (or `server_process.gd`): after starting the server subprocess, read the first framed message from the IPC channel - Validate it is a `HandshakeMessage` with `protocol_version == Protocol.PROTOCOL_VERSION` (14) - If mismatch: log error "Protocol version mismatch: server=%d, client=%d", emit a `handshake_failed` signal, shut down the server process gracefully - If match: emit `handshake_complete`, begin normal tick loop - Add a timeout: if no handshake message received within 5 seconds of process start, treat as mismatch Current state: `client/scripts/protocol/protocol.gd` already checks `version` in `decode_snapshot()` and logs a mismatch. That check is per-snapshot. The handshake is the startup-time equivalent — validate once at connection, not per tick. Files: `client/scripts/protocol/local_bridge.gd`, `client/scripts/protocol/server_process.gd`. ### #342 — IPC round-trip timing benchmark Sprint exit criterion. Measures the complete latency path from server serialization to client scene update. What this ticket must deliver: - A benchmark script `tests/run-ipc-benchmark` that: 1. Starts the server subprocess 2. Waits for handshake (#555/#556) 3. Sends N `PlayerInput` messages (N = 100 by default) 4. Measures from `rmp_serde::to_vec` (server) to scene update completion (client) 5. Reports p50/p95/p99 latencies in milliseconds 6. Flags if any percentile exceeds 5ms threshold - Output JSON: `{"p50_ms": 1.2, "p95_ms": 2.8, "p99_ms": 4.1, "threshold_ms": 5, "passed": true}` - The benchmark is run as part of `tests/run-ipc-integration` in Layer 3 Implementation approach: server-side timestamps in `ObserverSnapshot` (add `server_emit_tick_ms` field, stripped in production builds), client records receive timestamp via `Time.get_ticks_msec()`. Delta = client receive - server emit. Blocked by #555 and #556 — benchmark requires a working handshake before timing can start cleanly. ### #271 — IPC serialization fixture files Layer 1 test data: pre-generated `.msgpack` fixture files that both Rust and GDScript can read to verify cross-language serialization compatibility. What this ticket must deliver: - A Rust binary (or test in `server/src/`) that generates fixtures to `tests/fixtures/`: - `snapshot_minimal.msgpack` — minimal valid `ObserverSnapshot` (version=14, tick=0, one entity) - `snapshot_full.msgpack` — all optional fields populated (monologue, dialogue, inventory, POIs, KG dump) - `player_input_move.msgpack` — `PlayerInput { tick: 1, action: MoveNorth }` - `player_input_interact.msgpack` — `PlayerInput { tick: 2, action: Interact { target: 99, verb: "Talk" } }` - `malformed.msgpack` — intentionally truncated bytes (tests error handling) - A GDScript test `client/tests/test_ipc_fixtures.gd` that reads each `.msgpack` fixture file, decodes via `Protocol.decode_snapshot()` / `Protocol.decode_player_input()`, and asserts expected field values - Cross-language verification: the same byte stream decoded by both Rust and GDScript must produce identical field values The fixture generator is run once (manually or in CI pre-step) to produce the committed `.msgpack` files. The files live at `tests/fixtures/` and are committed to the repo. Blocked by #270 — fixture tests are invoked by `tests/run-ipc-fixtures`. ## Dependency Chain ``` #555 (server: protocol handshake) → #556 (ci: protocol handshake: client) #555 + #556 → #342 (IPC benchmark: requires working handshake) #270 (test runner scripts) → #271 (fixture files: invoked by run-ipc-fixtures) Parallel starts: #270, #555 (server-side) — both unblocked week 1 #556 starts after #555 is at review #271 starts after #270 merges #342 starts after #555 + #556 both land ``` ## PR Workflow When ready to submit, create a PR with `tea` CLI. **All flags are required** to avoid TTY prompts (see CLAUDE.md "Gitea access" section): ```bash tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "feat(ci): description" --description "body" --base main --head ci ```