From 396990539afdbadc26c3002afcf3c595f019b5f0 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 31 Jul 2026 01:43:31 +0200 Subject: [PATCH] =?UTF-8?q?T06:=20kernel=20spec=20=E2=80=94=20crate=20boun?= =?UTF-8?q?daries,=20event-sourced=20pipeline,=20determinism=20contract,?= =?UTF-8?q?=2012=20acceptance=20metrics=20tied=20to=20CB-RES-0001?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- specs/GameKernel.md | 157 +++++++++++++++++++++++++++++ workplans/CB-WP-0001-inner-loop.md | 2 +- 2 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 specs/GameKernel.md diff --git a/specs/GameKernel.md b/specs/GameKernel.md new file mode 100644 index 0000000..5a9ae5a --- /dev/null +++ b/specs/GameKernel.md @@ -0,0 +1,157 @@ +# Game Kernel — specification and acceptance metrics + +Status: **v0.1 draft** — implements ADR-0002 (reimplement in Rust, +assimilate patterns). Governs crates `cb-kernel`, `cb-events`, +`cb-game-runtime`, and the first game package `games/ground`. +Baselines referenced: research/CB-RES-0001-game-kernel.md. +Instruments: specs/MetricsAndScenarios.md. Rules content: +specs/GroundRules.md. + +The kernel is the **authoritative semantic layer** of the Clay-Borg +architecture (Blueprint §1): headless, deterministic, replayable. No +rendering, no physics, no networking — those attach later through ports +and must never leak types into anything specified here (M-D4-LEAK = 0). + +--- + +## 1. Crate boundaries + +| Crate | Owns | Must not know about | +|---|---|---| +| `cb-kernel` | canonical IDs, RNG service, command/event/fold traits, clock-free tick model | any concrete game, serialization format details | +| `cb-events` | event envelope, append-only log, snapshots, canonical serialization, state hashing | game semantics | +| `cb-game-runtime` | round/phase machinery, simultaneous commit windows, per-player projections, scenario-runner API | GROUND specifics | +| `games/ground` | GROUND rules: state aggregate, commands, events, reducers per specs/GroundRules.md | anything below `cb-game-runtime`'s public API | + +Dependency rule: `games/ground → cb-game-runtime → cb-events → cb-kernel`. +No cycle, no skip that bypasses a public API. External crates allowed in +the headless kernel workspace: serde (+format crate), a seedable RNG +(e.g. chacha), a hash (sha2), thiserror-class error derive — target total +transitive crates ≤ 20 (AM-4). + +## 2. Canonical model + +### 2.1 Identifiers (`cb-kernel`) + +Newtyped, copyable, ordered: `GameId`, `PlayerId`, `EntityId` +(problems, tokens, relations), `CommandId`, `EventId` (monotonic +`u64` sequence per game), `Seed` (`u64`). No UUIDs inside the hot +path; string forms only at the boundary. + +### 2.2 The mutation pipeline (assimilated: cqrs-es pattern) + +```text +Command (actor-tagged intent) + → validate(&State, &Command) → Result, Rejection> + → fold(&mut State, &Event) # infallible, total + → EventLog::append(events) # with per-event hash chaining +``` + +- **K1** All state change flows through events; there is no other mutator. + `fold` is infallible: every validation that could fail happens in + `validate`, so a logged event always applies. +- **K2** Commands carry: `CommandId`, issuing `PlayerId` (or `System`), + round number, and payload. Duplicate `CommandId` within a game is + rejected (idempotency). +- **K3** Rejections are typed and testable (`Rejection::StressGate`, + `::NotYourTarget`, `::SlotOccupied`, …); scenarios assert on them + (`expect.rejects`). +- **K4** Events are closed enums per game package, serialized through a + versioned envelope: `{seq, game_id, round, kind, payload, schema_ver}`. + +### 2.3 Determinism (assimilated: Rune's enforced discipline) + +- **K5** The only randomness source is the kernel RNG service, seeded from + `Seed`; RNG draws are themselves events (or deterministically derived + from the event sequence) so replay never re-rolls. +- **K6** No ambient time, no OS entropy, no pointer/hash iteration order: + all iterated collections in semantic state are ordered (`BTreeMap`/ + `Vec`); `HashMap` is forbidden in `games/*` state. Enforced by lint/deny + in CI, not convention (AM-8). +- **K7** State hash = SHA-256 over the canonical serialization of the + aggregate; computed on demand and recorded at round End events. +- **K8** Double-run invariant: the scenario runner executes every scenario + twice with the same seed and fails on any hash divergence + (MetricsAndScenarios §2). + +### 2.4 Snapshots and replay (`cb-events`) + +- **K9** A snapshot is the canonical serialization of the full aggregate + + the `EventId` it includes. `snapshot + remaining events → state` must be + hash-identical to a from-genesis fold (AM-7). +- **K10** A replay bundle (`.cbreplay`, MetricsAndScenarios §4) contains + manifest, command log, initial snapshot, and failed expectations; the + runner's `--replay` re-executes it bit-identically. +- **K11** Log format is append-only, length-prefixed, versioned; a + truncated tail is detected, not silently accepted. + +### 2.5 Simultaneity and hidden information (`cb-game-runtime`) + +- **K12** Commit window primitive (assimilated: GROUND's Select/Reveal, + boardgame.io `activePlayers` shape): the runtime opens a window naming + the players who must submit; submissions are recorded as **commitment + events** whose payload is hidden in projections until the window's + reveal event; late/duplicate submissions are Rejections. +- **K13** Projection (assimilated: OpenSpiel information states): for each + `PlayerId` (and `Spectator`), a total function `project(&State) → + PlayerView` that structurally cannot include: other players' unrevealed + commitments, face-down problem identities, other players' hands. + Projections are derived views — never inputs to `validate`/`fold`. +- **K14** The GROUND round (GR-R01..R09) is expressed through runtime + primitives: one commit window per Select, reveal, then fixed-order + resolution steps with Lead-order iteration (GR-R06/R07) driven by + system commands. + +### 2.6 GROUND aggregate (`games/ground`) + +- **K15** State implements specs/GroundRules.md §1 exactly; every GR-rule + is realized in `validate`/`fold` and cross-referenced by rule ID in doc + comments, giving a greppable rule→code→scenario chain. +- **K16** U-item defaults (GroundRules §Underdetermined) are implemented + behind clearly named functions so a ground-game ruling is a localized + change; their scenarios carry `provisional: true`. + +## 3. Scenario runner and CLI surface + +- **K17** `cb-game-runtime` exposes the scenario runner as a library; a + thin binary (`cb-sim`, precursor of `cb sim`) runs + `scenarios/ground/*.yaml` per MetricsAndScenarios §2: setup presets, + ordered actor-tagged commands, partial end-state assertions, `covers`, + `rejects`, optional `state_hash`. +- **K18** Benchmarks are Criterion benches driving the same scenario + format at scale (MetricsAndScenarios §3); the synthetic workload + mirrors the CB-RES-0001 harness shape (3 players, commit/reveal + rounds) for same-shape comparison. + +--- + +## 4. Acceptance metrics (the code loop's exit condition) + +Per InnerLoop step 4/5: T08 iterates until every row meets its target; +evidence lands in `evidence/CB-EV-0001-game-kernel.md` with no +`unmeasured`. Baselines from CB-RES-0001; cited-only rows cap at parity. + +| ID | Metric | Baseline (CB-RES-0001) | Target | Verdict basis | +|---|---|---|---|---| +| AM-1 | M-D1-COV: GR-rules covered by ≥1 passing scenario | no candidate has any (observation) | **100%** of GR + U rules | measured by runner report | +| AM-2 | M-D1-SPL: spec lines per rule in `games/ground` rules code (impl LOC ÷ rule count) | boardgame.io ~36 LOC for the 2-move synthetic game | ≤ 40 LOC/rule, paired with AM-1 (anti-gaming pair) | measured (tokei + rule count) | +| AM-3 | Synthetic-workload definition size: LOC to express the CB-RES-0001 synthetic game on our kernel | ~36 LOC (boardgame.io, measured) | ≤ 50 LOC | measured | +| AM-4 | M-D2-DEP: transitive crates, headless workspace | 120 npm packages | **≤ 20** | measured (cargo tree) | +| AM-5 | M-D2-BLD: clean release build of headless workspace | n/a (npm install ~seconds; not comparable) | ≤ 60 s on bnt-lap001, recorded not gated | measured | +| AM-6 | M-D3-THR: applied events/s, synthetic workload, same machine | boardgame.io ~1,100–1,900 moves/s (best config, degrading) | **≥ 100,000/s** (stipulated target, ADR-0002) | measured | +| AM-7 | M-D3 scaling: throughput @100k events vs @5k; and snapshot+replay of 100k events | boardgame.io 0.45–0.66× @20–40k, DNF @100k | **≥ 0.9×** (flat), replay of 100k events ≤ 5 s, hash-identical | measured | +| AM-8 | Determinism invariant: N=10 same-seed replays, bit-identical hashes; HashMap-in-state deny lint clean | Rune: enforced by tooling (cited) | zero divergence, lint clean in CI | measured (invariant, not a verdict row) | +| AM-9 | M-D3-MEM: peak RSS, 100k-event synthetic run | boardgame.io ~100→232 MB @5k→40k (indicative) | ≤ 64 MB, flat with history given snapshot interval | measured (indicative label, same method) | +| AM-10 | M-D4-LEAK: foreign types in `cb-*-api`-visible signatures | boardgame.io: JS-ecosystem-locked | **0** | measured (grep/deny rule) | +| AM-11 | M-D4-SWAP: null + reference impls passing one conformance suite | no candidate has the pattern | RNG and log storage each have ≥2 impls (real + test/null) under one suite | measured (bool) | +| AM-12 | M-D2-TOK / M-D2-CST: tokens and USD per completed task | n/a — first pass sets our own baseline | recorded per task in the evidence cost log (price sheet 2026-07-31) | recorded, not gated | + +Comparisons against the event-sourcing 10⁵–10⁶/s estimate stay **parity** +until a local Rust comparator is measured (open follow-up from the +adversarial review). + +## 5. Out of scope for this pass + +Networking/session protocol, WIT/Wasm game boundary, ECS world layer, +rendering, persistence beyond file snapshots, host migration, and any +second game. Each arrives through its own loop pass with its own survey. diff --git a/workplans/CB-WP-0001-inner-loop.md b/workplans/CB-WP-0001-inner-loop.md index 7695511..db467e3 100644 --- a/workplans/CB-WP-0001-inner-loop.md +++ b/workplans/CB-WP-0001-inner-loop.md @@ -132,7 +132,7 @@ here. Each numbered rule maps to at least one scenario. ```task id: CB-WP-0001-T06 -status: todo +status: done priority: medium state_hub_task_id: "fc3fa174-8a1f-46ac-a922-5d7b80231d5f" ```