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

6.5 KiB

title, description, type, status, sprint, team
title description type status sprint team
Sprint 19 — CI Briefing Test runner scripts, protocol version handshake, IPC round-trip benchmark sprint archived 19 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):

{"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.msgpackPlayerInput { tick: 1, action: MoveNorth }
    • player_input_interact.msgpackPlayerInput { 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):

tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "feat(ci): description" --description "body" --base main --head ci