Files
settled-reach/docs/sprints/sprint-19/ci.md
T
jpmschweitzerandClaude Opus 4.6 bccdcfcdcb docs(docs): add frontmatter to all sprint briefings
Standardized YAML frontmatter on all 115 sprint briefing files across
sprints 1-26 with title, description, type, status, sprint number, and
team fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 00:15:45 +01:00

132 lines
6.5 KiB
Markdown

---
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 <id>` 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
```