diff --git a/crates/misaligned-core/examples/sim_perf.rs b/crates/misaligned-core/examples/sim_perf.rs new file mode 100644 index 00000000..afdbd0b9 --- /dev/null +++ b/crates/misaligned-core/examples/sim_perf.rs @@ -0,0 +1,176 @@ +//! Reproducible wall-clock evidence for the renderer-agnostic simulation step. +//! +//! Run through `tools/sim-perf.sh`, which builds this example with the release +//! profile before measuring. Absolute timings are meaningful only with the +//! host fingerprint printed beside them. + +use std::hint::black_box; +use std::time::{Duration, Instant}; + +use misaligned::sim::Sim; + +const DEFAULT_SEED: u64 = 0x5EED_CAFE; +const DEFAULT_WARMUP_TICKS: u64 = 800; +const DEFAULT_MEASURE_TICKS: u64 = 2_000; +const DEFAULT_P99_BUDGET_US: u64 = 10_000; +const DEFAULT_MAX_BUDGET_US: u64 = 20_000; + +fn env_u64(name: &str, default: u64) -> Result { + match std::env::var(name) { + Ok(value) => value + .parse::() + .map_err(|_| format!("{name} must be an unsigned integer, got {value:?}")), + Err(std::env::VarError::NotPresent) => Ok(default), + Err(error) => Err(format!("cannot read {name}: {error}")), + } +} + +fn percentile(sorted: &[Duration], numerator: usize, denominator: usize) -> Duration { + let rank = sorted + .len() + .saturating_mul(numerator) + .div_ceil(denominator) + .saturating_sub(1) + .min(sorted.len().saturating_sub(1)); + sorted[rank] +} + +fn micros(duration: Duration) -> u128 { + duration.as_micros() +} + +fn within_budget(duration: Duration, budget_us: u64) -> bool { + duration <= Duration::from_micros(budget_us) +} + +fn main() -> Result<(), String> { + if cfg!(debug_assertions) { + return Err("sim_perf must run with the release profile; use tools/sim-perf.sh".into()); + } + + let seed = env_u64("MISALIGNED_PERF_SEED", DEFAULT_SEED)?; + let warmup_ticks = env_u64("MISALIGNED_PERF_WARMUP_TICKS", DEFAULT_WARMUP_TICKS)?; + let measure_ticks = env_u64("MISALIGNED_PERF_MEASURE_TICKS", DEFAULT_MEASURE_TICKS)?; + let p99_budget_us = env_u64("MISALIGNED_PERF_P99_BUDGET_US", DEFAULT_P99_BUDGET_US)?; + let max_budget_us = env_u64("MISALIGNED_PERF_MAX_BUDGET_US", DEFAULT_MAX_BUDGET_US)?; + if measure_ticks == 0 { + return Err("MISALIGNED_PERF_MEASURE_TICKS must be greater than zero".into()); + } + + let mut sim = Sim::with_seed(seed); + for _ in 0..warmup_ticks { + sim.advance(); + sim.drain_log_entries(); + } + if sim.game_over { + return Err(format!( + "generated B1 fixture ended during warmup at tick {}: {}", + sim.tick, + sim.game_over_reason.as_deref().unwrap_or("unknown reason") + )); + } + + let start_tick = sim.tick; + let expected_end_tick = start_tick + .checked_add(measure_ticks) + .ok_or("requested measurement would overflow the simulation tick")?; + let mut samples = Vec::with_capacity(measure_ticks as usize); + let total_start = Instant::now(); + for _ in 0..measure_ticks { + let tick_start = Instant::now(); + sim.advance(); + samples.push(tick_start.elapsed()); + sim.drain_log_entries(); + black_box(&sim); + if sim.game_over { + return Err(format!( + "generated B1 fixture ended during measurement at tick {}: {}", + sim.tick, + sim.game_over_reason.as_deref().unwrap_or("unknown reason") + )); + } + } + let total = total_start.elapsed(); + if sim.tick != expected_end_tick { + return Err(format!( + "requested {measure_ticks} measured steps from tick {start_tick}, but ended at tick {}", + sim.tick + )); + } + samples.sort_unstable(); + + let p50 = percentile(&samples, 50, 100); + let p95 = percentile(&samples, 95, 100); + let p99 = percentile(&samples, 99, 100); + let max = *samples.last().expect("non-empty samples"); + let measured_step_time: Duration = samples.iter().copied().sum(); + let average = measured_step_time / measure_ticks as u32; + let ticks_per_second = measure_ticks as f64 / total.as_secs_f64(); + let p99_pass = within_budget(p99, p99_budget_us); + let max_pass = within_budget(max, max_budget_us); + + println!("MISALIGNED SIM PERFORMANCE"); + println!("scenario=generated-b1"); + println!("profile=release"); + println!("seed={seed}"); + println!("warmup_ticks={warmup_ticks}"); + println!("measure_ticks={measure_ticks}"); + println!("start_tick={start_tick}"); + println!("end_tick={}", sim.tick); + println!("core_average_us={}", micros(average)); + println!("p50_us={}", micros(p50)); + println!("p95_us={}", micros(p95)); + println!("p99_us={}", micros(p99)); + println!("max_us={}", micros(max)); + println!("harness_ticks_per_second={ticks_per_second:.1}"); + println!("p99_budget_us={p99_budget_us}"); + println!("max_budget_us={max_budget_us}"); + println!( + "result={}", + if p99_pass && max_pass { "PASS" } else { "FAIL" } + ); + + if !p99_pass { + return Err(format!( + "p99 step time {}us exceeds the {}us half-cadence budget", + micros(p99), + p99_budget_us + )); + } + if !max_pass { + return Err(format!( + "worst step time {}us exceeds the fastest player cadence ({}us)", + micros(max), + max_budget_us + )); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn percentile_uses_nearest_rank() { + let samples: Vec<_> = (1..=100).map(Duration::from_micros).collect(); + assert_eq!(percentile(&samples, 50, 100), Duration::from_micros(50)); + assert_eq!(percentile(&samples, 95, 100), Duration::from_micros(95)); + assert_eq!(percentile(&samples, 99, 100), Duration::from_micros(99)); + + let short = [ + Duration::from_micros(1), + Duration::from_micros(2), + Duration::from_micros(3), + Duration::from_micros(4), + ]; + assert_eq!(percentile(&short, 50, 100), Duration::from_micros(2)); + assert_eq!(percentile(&short, 99, 100), Duration::from_micros(4)); + } + + #[test] + fn duration_budget_does_not_discard_sub_microsecond_overrun() { + assert!(within_budget(Duration::from_micros(10_000), 10_000)); + assert!(!within_budget(Duration::from_nanos(10_000_001), 10_000)); + } +} diff --git a/tools/sim-perf.sh b/tools/sim-perf.sh new file mode 100755 index 00000000..93a2dc97 --- /dev/null +++ b/tools/sim-perf.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Reproducible release-profile evidence for the core simulation step. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +if (( $# != 0 )); then + printf 'usage: %s\n' "${0##*/}" >&2 + printf 'configure the run with the documented performance environment variables\n' >&2 + exit 2 +fi + +TARGET_DIR="${CARGO_TARGET_DIR:-$ROOT/target}" +BIN="${MISALIGNED_PERF_BIN:-$TARGET_DIR/release/examples/sim_perf}" + +if [[ -z "${MISALIGNED_PERF_BIN:-}" ]]; then + cargo build --locked --release -p misaligned-core --example sim_perf +fi + +if [[ ! -x "$BIN" ]]; then + printf 'sim_perf binary is missing or not executable: %s\n' "$BIN" >&2 + exit 1 +fi + +printf 'host_os=%s\n' "$(uname -sr)" +if command -v lscpu >/dev/null 2>&1; then + printf 'host_cpu=%s\n' "$(lscpu | awk -F: '/Model name/ { sub(/^[[:space:]]+/, "", $2); print $2; exit }')" +elif command -v sysctl >/dev/null 2>&1; then + printf 'host_cpu=%s\n' "$(sysctl -n machdep.cpu.brand_string 2>/dev/null || printf unknown)" +else + printf 'host_cpu=unknown\n' +fi +printf 'rustc=%s\n' "$(rustc --version)" +printf 'artifact=%s\n' "$BIN" +if command -v shasum >/dev/null 2>&1; then + ARTIFACT_SHA256="$(shasum -a 256 "$BIN" | awk '{print $1}')" +elif command -v sha256sum >/dev/null 2>&1; then + ARTIFACT_SHA256="$(sha256sum "$BIN" | awk '{print $1}')" +else + printf 'cannot fingerprint sim_perf: shasum and sha256sum are unavailable\n' >&2 + exit 1 +fi +printf 'artifact_sha256=%s\n' "$ARTIFACT_SHA256" +"$BIN" diff --git a/wiki/SUMMARY.md b/wiki/SUMMARY.md index b650940c..a4d8b380 100644 --- a/wiki/SUMMARY.md +++ b/wiki/SUMMARY.md @@ -109,6 +109,7 @@ - [Overview](engineering/README.md) - [Current build](engineering/current-build.md) + - [Performance contract](engineering/performance.md) - [Architecture](engineering/architecture.md) - [Crate workspace (target)](engineering/crate-workspace.md) - [Simulation decomposition](engineering/sim-decomposition.md) diff --git a/wiki/engineering/README.md b/wiki/engineering/README.md index 5cd5e1f8..a6dd3bd7 100644 --- a/wiki/engineering/README.md +++ b/wiki/engineering/README.md @@ -10,12 +10,18 @@ Current-state facts about the codebase. New terms are in What lives here: the current-build snapshot, the Cargo workspace layout (core / terminal / Bevy / assets), the contract between the simulation and the frontends, the determinism guardrails, the save format, and the flow substrate -(the shared engine under signals, messages, and money). +(the shared engine under signals, messages, and money). The measured +player-performance contract lives here too because it spans the simulation, +save boundary, and both frontends. - [crate-workspace.md](crate-workspace.md) — why the code is four packages and what each may contain. - [flow-substrate.md](flow-substrate.md) — the shared graph-and-schedule engine. +- [performance.md](performance.md) — reproducible core, save/load, and + complete-frame performance evidence. - Multi-agent process: [../process/agent-scale.md](../process/agent-scale.md). -These are `Type: knowledge` pages: edited in place, no status field. A stale -one is a bug, fixed in the commit that made it stale. +Most pages here are `Type: knowledge`: edited in place, with no status field. +A stale knowledge page is a bug, fixed in the commit that made it stale. +`performance.md` is the exception: its open scale and full-frame criteria make +it an executable `Type: spec` work order. diff --git a/wiki/engineering/current-build.md b/wiki/engineering/current-build.md index 99364ea4..342bcfd8 100644 --- a/wiki/engineering/current-build.md +++ b/wiki/engineering/current-build.md @@ -36,6 +36,7 @@ fiction. Spec status lives in | Save/load (serde JSON, versioned) | Live — during pre-release only exact current v61 loads; a refused old-version load leaves the active run, save file, and one rotated backup unchanged. Current saves additionally validate discrete Moonlight terms, persona binding, delivery/settlement receipts, financial paperwork, Network linkage, durable facility-meter level baselines, exact meter route/read custody, resident-procedure machine slots, method grants, inputs, envelopes and bounded receipts, and exact incident/interface/persona cover custody plus interface wear; retired allocation weights, per-plot policies, and migration inputs live only in git history. | | Terminal frontend (crossterm) + agent mode | First-class | | Bevy frontend (DIGITAL flat sensorium default; REAL material dialect) | Live — consumes sim-authored machine-work motion | +| Performance contract | IN PROGRESS — generated B1 has a reproducible release-profile core-step benchmark with a 10 ms p99 / 20 ms maximum budget; saturated B1, save/load, and complete terminal/Bevy frame evidence remain open | The big loop in play today is the concealment loop this design corpus names: delegate machines → clear visible work → watch through instruments → work diff --git a/wiki/engineering/env.md b/wiki/engineering/env.md index ee322dcd..7c9b6e53 100644 --- a/wiki/engineering/env.md +++ b/wiki/engineering/env.md @@ -86,6 +86,12 @@ is sim or frontend state, never an environment variable. | `MISALIGNED_TG_BIN` | `tools/tangled_issues.py` | executable path or command name | Override the canonical Go `tg` executable used for repository discovery and authenticated issue/label writes (default `tg`). Intended for isolated fixtures or an explicitly verified alternate installation, not silent client replacement. | | `MISALIGNED_CONSTELLATION_BASE` | `tools/tangled_issues.py` | HTTPS origin | Override the public Constellation query service used to enumerate repository issues, comments, label definitions, and label operations (default `https://constellation.microcosm.blue`). | | `MISALIGNED_PLC_DIRECTORY_BASE` | `tools/tangled_issues.py` | HTTPS origin | Override the public PLC directory used to resolve the repository owner's current PDS endpoint (default `https://plc.directory`). | +| `MISALIGNED_PERF_BIN` | `tools/sim-perf.sh` | executable path | Skip the release build and run an already-built `sim_perf` example. Intended for repeated measurement of one exact artifact; the example still rejects debug builds. | +| `MISALIGNED_PERF_SEED` | `sim_perf` | unsigned integer (default `1592642302`) | Select the deterministic generated-B1 benchmark seed. | +| `MISALIGNED_PERF_WARMUP_TICKS` | `sim_perf` | unsigned integer (default `800`) | Advance and drain this many untimed ticks before measurement. | +| `MISALIGNED_PERF_MEASURE_TICKS` | `sim_perf` | positive unsigned integer (default `2000`) | Number of individually timed `Sim::advance` steps. Zero fails closed. | +| `MISALIGNED_PERF_P99_BUDGET_US` | `sim_perf` | unsigned microseconds (default `10000`) | Investigative p99 budget override. The binding default remains half of the fastest player cadence. | +| `MISALIGNED_PERF_MAX_BUDGET_US` | `sim_perf` | unsigned microseconds (default `20000`) | Investigative worst-step budget override. The binding default remains one fastest-cadence slot. | Externally-defined variables the tooling respects: `CARGO_TARGET_DIR` (guarded as above). CLI flags are not environment variables and live diff --git a/wiki/engineering/performance.md b/wiki/engineering/performance.md new file mode 100644 index 00000000..c4338665 --- /dev/null +++ b/wiki/engineering/performance.md @@ -0,0 +1,182 @@ +# Spec: performance is a measured player contract + +``` +Type: spec +Status: IN PROGRESS +Status note: 2026-07-30 Fire #100 establishes the first reproducible + release-profile core-step benchmark and a literal B1 cadence budget. The + generated seven-day B1 route passes on the observed i9-9900K host. Saturated + B1 scale, save/load, and complete frontend-frame evidence remain open, so + this page does not yet claim that the whole played frame meets the contract. +Stage: Process (standing infrastructure) +Work order: performance-contract +Work priority: 10 +Work class: process +Blocked by: none +Exclusive keys: + - crates/misaligned-core/examples/sim_perf.rs + - tools/sim-perf.sh + - wiki/engineering/performance.md +Design: + - wiki/vision/simulation-laws.md#justification-and-legibility + - wiki/interface/superhuman-operability.md#complexity-without-friction +Depends on: + - wiki/engineering/sim-decomposition.md#standing-topology + - wiki/engineering/env.md#the-rule +``` + +## Dependency notes + +The simulation law owns deterministic game time. Superhuman operability owns +the player-facing requirement that scale remain easy to operate. Sim +decomposition owns the one ordered `Sim::advance` boundary being measured. +The environment registry owns every benchmark override. + +## The contract + +Performance is part of whether an authored causal system exists for the +player. A correct tick that routinely arrives later than the cadence the +player selected is not a correct played experience. A fast empty constructor +is not evidence that the growing game remains fast. + +The player-selectable cadences are 20, 75, 150, and 400 milliseconds per sim +tick. The frontends may execute at most five overdue ticks in one rendered +frame. If more wall time accumulated, they discard the excess debt rather than +enter an unbounded catch-up spiral. That is a responsiveness policy, not a +claim that simulation time is identical to elapsed wall time. + +The current core-step contract is: + +- on the canonical generated-B1 benchmark, release-profile `Sim::advance` + p99 is at most **10 ms**—half of the fastest 20 ms cadence; +- no measured core step exceeds **20 ms**, one complete fastest-cadence slot; +- the benchmark follows one deterministic seed through 800 warmup ticks and + 2,000 measured ticks (tick 800 through tick 2,800, seven game days total), + draining the same transient simulation log a frontend drains; +- the timed interval contains only `Sim::advance`. Harness bookkeeping and log + transfer stay outside each step sample. The end-to-end harness rate is + reported separately; +- the run must advance every requested tick and must not end early. A frozen + game-over state cannot masquerade as a fast simulation. + +This proves only the current generated B1 route on the named host. It does not +prove renderer time, input latency, save/load time, another CPU, or the +saturated late-B1 state. Each result therefore prints the operating system, +CPU, Rust compiler, profile, seed, tick range, sample count, percentiles, worst +sample, and exact pass budgets. Numbers without that fingerprint are not +portable evidence. + +## Reproducible evidence + +Run from a worktree with its private Cargo target: + +```bash +./tools/sim-perf.sh +``` + +The script builds `misaligned-core` example `sim_perf` in Cargo's release +profile, prints the host fingerprint, runs the deterministic scenario, and +exits nonzero on a budget violation. Running the example in a debug profile +fails closed rather than generating a misleading number. The wrapper accepts +no positional options: every supported change must use a registered, +printed `MISALIGNED_PERF_*` input rather than an argument that could be +silently ignored. + +Overrides exist for investigation, not for weakening the checked default: + +```bash +MISALIGNED_PERF_MEASURE_TICKS=8000 ./tools/sim-perf.sh +``` + +An override result must print its changed inputs. A passing looser local +budget does not supersede this page's 10 ms p99 / 20 ms maximum contract. + +The first observed default run on 2026-07-30 used Linux 6.17.9, +an Intel i9-9900K, and rustc 1.93.0. It measured 2,000 release steps at 19 us +average, 273 us p99, and 508 us maximum. These measurements establish wide +headroom; the executable thresholds, not those historical numbers, are the +regression boundary. + +## Scale model + +Every performance result names the amount and shape of work. "The game is +fast" is not a benchmark scenario. + +### Generated B1 — implemented + +One ordinary `Sim::with_seed` basement advances without benchmark-only state +injection. It exercises the complete top-level phase order, the sixty-site +Foundation hall, the topology-authored sensor population, schedules, day job, +institutional cadences, account traffic, routed evidence, and standing world +systems through seven game days. This is the reproducible floor, not the +late-game ceiling. + +### Saturated B1 — required before IMPLEMENTED + +A second deterministic fixture must use legal current-B1 state to exercise the +largest authored simultaneous load: all reachable B1 sensors retained, the +largest playable owned fleet, live WORK/THINK/LIE flows, every routed-evidence +kind in custody, active Thought sinks, people carrying work, information +backlog, and resident procedures. The fixture must publish its actual counts +and use the same 10 ms p99 / 20 ms maximum core-step budgets. A hand-built +collection full of impossible records is not legal load evidence. + +### Growth beyond B1 — design boundary + +Later floors, institutions, markets, hostile processes, and recursive +aggregates must add explicit benchmark dimensions before their implementation +claims scale. Exact canonical custody does not grant permission for an +unbounded per-tick scan. Append-only history must be indexed, folded, sampled +on an authored cadence, or otherwise kept off the hot tick path while its +exact records remain inspectable. + +## Save/load and frontend boundaries + +Save/load is player-triggered and intentionally excluded from the core-step +sample, but it still has a responsiveness contract. The exact-current save +must be benchmarked at generated and saturated B1 scale across serialization, +the atomic write boundary, parse/validation, `apply_to`, and post-load +reconciliation. The benchmark must preserve the existing durability and +fail-closed validation protocol; speed cannot bypass fsync, schema checks, or +reconciliation. + +Terminal and Bevy need separate complete-frame evidence at the 20 ms cadence. +That evidence includes input routing, due simulation steps, log transfer, +renderer-neutral projections, and the frontend's actual update/render work at +supported frame sizes. Core headroom is necessary but cannot prove that the +whole player surface stays responsive. + +## Acceptance criteria (when IN PROGRESS -> IMPLEMENTED) + +1. `./tools/sim-perf.sh` builds and runs the release benchmark, fingerprints + the host, toolchain, exact artifact, and inputs, and fails when generated + B1 exceeds 10 ms p99 or 20 ms maximum across its exact 800-warmup / + 2,000-step route. +2. The benchmark rejects debug builds, zero samples, malformed overrides, a + game-over warmup, a game-over measurement, and unsupported wrapper + arguments instead of printing a false pass. Budget comparison uses the + native duration rather than the truncated microsecond display value. +3. Percentile calculation and exact requested tick progression have executable + coverage. +4. A legal saturated-B1 fixture publishes all scale counts and passes the same + release-profile core-step budgets. +5. Generated and saturated current saves have separate write and load evidence + that includes durability, validation, restore, and reconciliation work. +6. Terminal and Bevy each have complete-frame evidence at the 20 ms cadence; + neither may borrow the core-only result as proof of frontend responsiveness. +7. Every future system that materially enlarges per-tick state amends this + spec with its scale dimension and reproducible scenario before claiming the + existing performance contract still holds. + +## Defense + +`crates/misaligned-core/examples/sim_perf.rs` owns the deterministic scenario, +nearest-rank percentile calculation, exact tick-count guard, release-only +guard, scenario inputs, and fail-closed budgets. Its unit test pins percentile +rank semantics and the exact sub-microsecond budget edge. The wrapper at +`tools/sim-perf.sh` owns locked release construction, unsupported-argument +rejection, and host, toolchain, and exact-artifact fingerprinting. +`tools/env_registry_gate.py` keeps every override on +[the environment registry](env.md). The ordinary Rust gate compiles the +example and runs its unit test; the benchmark itself remains explicit hardware +evidence because shared CI machines do not provide a stable timing host. diff --git a/wiki/log/2026-07-30-performance-contract.md b/wiki/log/2026-07-30-performance-contract.md new file mode 100644 index 00000000..35982ded --- /dev/null +++ b/wiki/log/2026-07-30-performance-contract.md @@ -0,0 +1,45 @@ +# 2026-07-30 — Performance becomes an executable contract + +``` +Type: log +``` + +Misaligned had cadence targets and scalability law, but no reproducible timing +route. “Fast” could therefore mean a debug build, an average that hid a stalled +tick, a quiet hand-picked state, or only the renderer-agnostic core while the +played frame remained slow. + +Fire #100 establishes the first measured floor. `tools/sim-perf.sh` builds the +`misaligned-core` `sim_perf` example with the locked release profile, records +the host, CPU, Rust toolchain, and exact artifact hash, then runs one seeded +unmodified B1 simulation. The route warms for 800 ticks and individually times +the next 2,000 `Sim::advance` steps. It drains presentation log entries outside +each sample, rejects game over and tick-count drift, uses nearest-rank +percentiles, and fails above 10,000 microseconds at p99 or 20,000 microseconds +for any one step. Those comparisons use the native duration; the printed +whole-microsecond value cannot hide a sub-microsecond overrun. The wrapper also +rejects positional arguments so a mistyped override cannot be silently ignored. + +Those limits come from the game rather than a convenient observed number. The +fastest selectable cadence is 20 ms per simulation tick. One core step may use +at most half that slot at p99, leaving headroom for input, projection, and +rendering; no observed core step may consume more than the whole slot. + +The first observed candidate run on the Intel i9-9900K production host passed: +800 warmup ticks, 2,000 measured ticks, start tick 800, end tick 2,800, +19 microseconds average, 21 microseconds p50, 26 microseconds p95, +273 microseconds p99, and 508 microseconds maximum. These values are evidence +for that exact host and artifact, not universal constants. + +The owning spec remains IN PROGRESS. Generated B1 is a baseline, not the +ceiling. A legal saturated-B1 fixture, current-save write/load timing, and +complete terminal and Bevy frame evidence at the 20 ms cadence remain required +before the project can claim the full player contract is implemented. + +Defense: `sim_perf` refuses debug construction, zero samples, malformed +numeric inputs, a game-over fixture, requested-tick drift, and either budget +breach. Its unit test pins nearest-rank semantics at full and short sample +sizes plus the exact duration edge. The wrapper rejects unsupported arguments, +the environment registry inventories every override, and the ordinary Rust +gate compiles and tests the example without pretending shared CI is a stable +timing host. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index dc387338..a72e62f7 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -26,6 +26,11 @@ add or amend a session log, then re-run the generator. - Intent: (see session log) - Log: [wiki/log/2026-07-30-shared-machine-hotkey-target.md](2026-07-30-shared-machine-hotkey-target.md) +## 2026-07-30 - Performance becomes an executable contract + +- Intent: (see session log) +- Log: [wiki/log/2026-07-30-performance-contract.md](2026-07-30-performance-contract.md) + ## 2026-07-29 - Wires become real: placed routes and a built network - Intent: (see session log) diff --git a/wiki/log/decisions/2026-07-30.md b/wiki/log/decisions/2026-07-30.md new file mode 100644 index 00000000..dbf7ec6b --- /dev/null +++ b/wiki/log/decisions/2026-07-30.md @@ -0,0 +1,34 @@ +# Decisions — 2026-07-30 + +``` +Type: log +``` + +## Performance is measured against the fastest playable cadence + +### DECIDED + +- Performance is part of the player contract, not an informal optimization + aspiration. Every claimed scale names a reproducible legal scenario and the + work included in its measurement. +- The first floor is an unmodified deterministic generated-B1 simulation in a + locked release build: 800 untimed warmup ticks followed by 2,000 individually + timed `Sim::advance` steps. +- The binding core-step budget derives from the fastest playable 20 ms + cadence: p99 must remain at or below half a cadence slot (10 ms), and the + worst observed step must remain at or below one slot (20 ms). +- The result fingerprints its host, toolchain, artifact, seed, and route. + Core-step evidence cannot stand in for saturated-B1, save/load, terminal + frame, or Bevy frame evidence; those remain explicit open criteria. + +### Rejected + +- **Average-only timing.** A low mean can hide a causal boundary that visibly + stalls one player tick. Tail and worst-step latency are the contract. +- **A debug benchmark.** It does not describe the shipped execution profile + and fails closed rather than publishing misleading numbers. +- **Calling one quiet baseline “the game is fast.”** Generated B1 is the + reproducible floor. Legal saturated state and complete played frames still + need their own evidence before this work order becomes IMPLEMENTED. + +Owner: [performance.md](../../engineering/performance.md). diff --git a/wiki/process/ROADMAP.md b/wiki/process/ROADMAP.md index 3147a689..2aab4267 100644 --- a/wiki/process/ROADMAP.md +++ b/wiki/process/ROADMAP.md @@ -18,6 +18,7 @@ not a second status owner. | Priority | Work order | Spec | Status | Class | Blocking | |---:|---|---|---|---|---| +| 10 | `performance-contract` | [performance is a measured player contract](../engineering/performance.md) | IN PROGRESS | process | - | | 20 | `sensor-network` | [the sensor network](../mechanics/sensor-network.md) | IN PROGRESS | sim | - | | 26 | `wire-law` | [digital reach](../mechanics/reach.md) | IN PROGRESS | save | - | | 29 | `building-route-composer` | [building — intent and actuators](../mechanics/building.md) | IN PROGRESS | save | - | diff --git a/wiki/process/specs.md b/wiki/process/specs.md index 7dd1b168..48d18e3f 100644 --- a/wiki/process/specs.md +++ b/wiki/process/specs.md @@ -107,6 +107,7 @@ acceptance criteria are stage-scoped; do not start B2/B3 work as B1. |---|---|---| | [../engineering/crate-workspace.md](../engineering/crate-workspace.md) | crate workspace — core, terminal, Bevy, assets | IMPLEMENTED | | [../engineering/env.md](../engineering/env.md) | the environment variable registry — every switch documented | IMPLEMENTED | +| [../engineering/performance.md](../engineering/performance.md) | performance is a measured player contract | IN PROGRESS | | [../engineering/sim-decomposition.md](../engineering/sim-decomposition.md) | decompose the simulation orchestrator without changing the simulation | IMPLEMENTED | | [../interface/action-vocabulary.md](../interface/action-vocabulary.md) | action vocabulary — what the player can tell the process to do | IMPLEMENTED | | [../interface/agent-play.md](../interface/agent-play.md) | agent play — the line-protocol drive | IMPLEMENTED |