diff --git a/.tangled/workflows/corpus.yml b/.tangled/workflows/corpus.yml index 4505efcb..de5c1e06 100644 --- a/.tangled/workflows/corpus.yml +++ b/.tangled/workflows/corpus.yml @@ -30,6 +30,12 @@ steps: set -euo pipefail bash tools/test_design_amendment_gate.sh python3 tools/test_ci_workflows.py + python3 tools/test_env_registry.py + + - name: "environment registry" + command: | + set -euo pipefail + python3 tools/env_registry_gate.py - name: "corpus engine fixtures" command: | diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs index 0caf94f2..00d277d8 100644 --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -613,13 +613,92 @@ enum RailSection { Secondary, } -/// Dev screenshot harness (env `MISALIGNED_SHOT=flat|hall|hall-material|opening|opening-digital|clinical-threat|assurance-office|pilot-last-chance|operator-pressure|digital-reach|build-route-families|build-deceive-routes|build-committed-route|build-switch-digital|build-switch-real|wide|close|dark| -/// zoomin|zoomout|intel|tokens|thoughtflow|first-think|visual-proof|person-proof|evidence-proof|evidence-proof-digital|exposure-record|exposure-overflow|service-shift-real|service-shift-digital|service-incident-resolved|signal|ears|ears-digital|eyes-white|eyes-form|operations-links| -/// hover-menu|read-receipt|held-choice|two-pane|standing-read|intel-altitude-close|intel-altitude-far|menu|recruit-menu|operations|operations-intel|operations-people|operations-personas|worklight|worklightoff`, path via -/// `MISALIGNED_SHOT_PATH`): stages a scenario, -/// waits for -/// assets, runs the fog audit, saves one screenshot, exits. Not a player -/// surface; exists so render passes can be reviewed from deterministic PNGs. +/// Canonical accepted values for the Bevy `MISALIGNED_SHOT` harness. +/// +/// Keep this sorted. `tools/env_registry_gate.py` compares it exactly with the +/// human-usable rows in `wiki/engineering/env.md`; runtime validation also +/// rejects unknown values instead of silently staging the generic flat frame. +const BEVY_SHOT_KINDS: &[&str] = &[ + "assurance-office", + "build-committed-route", + "build-deceive-routes", + "build-route-families", + "build-switch-digital", + "build-switch-real", + "clinical-threat", + "close", + "consume-demand", + "consume-thought", + "dark", + "digital-reach", + "draw-lie", + "ears", + "ears-digital", + "evidence-proof", + "evidence-proof-digital", + "exposure-overflow", + "exposure-record", + "eyes-form", + "eyes-white", + "first-think", + "flat", + "floor-lights-close", + "hall", + "hall-material", + "held-choice", + "hover-menu", + "intel", + "intel-altitude-close", + "intel-altitude-far", + "menu", + "opening", + "opening-digital", + "operations", + "operations-intel", + "operations-links", + "operations-people", + "operations-personas", + "operator-pressure", + "person-proof", + "pilot-last-chance", + "produce-think", + "read-receipt", + "recruit-menu", + "service-incident-resolved", + "service-shift-digital", + "service-shift-real", + "signal", + "standing-read", + "thought-snap", + "thought-tap", + "thoughtflow", + "thoughtflow-wide", + "tokens", + "two-pane", + "visual-proof", + "wake1", + "wake2", + "wake3", + "wide", + "worklight", + "worklightoff", + "zoomin", + "zoomout", +]; + +fn checked_bevy_shot_kind(kind: String) -> String { + assert!( + BEVY_SHOT_KINDS.binary_search(&kind.as_str()).is_ok(), + "unsupported MISALIGNED_SHOT={kind:?}; expected one of: {}", + BEVY_SHOT_KINDS.join(", ") + ); + kind +} + +/// Dev screenshot harness (`MISALIGNED_SHOT`, path via +/// `MISALIGNED_SHOT_PATH`): stages a catalogued scenario, waits for assets, +/// runs the fog audit, saves one screenshot, and exits. Not a player surface; +/// it exists so render passes can be reviewed from deterministic PNGs. #[derive(Resource)] struct ShotHarness { kind: String, @@ -1998,28 +2077,31 @@ fn main() { }); // Dev screenshot harness: stage a deterministic scenario and capture one // PNG (see ShotHarness). Never active in normal play. - let harness = std::env::var("MISALIGNED_SHOT").ok().map(|kind| { - let path = std::env::var("MISALIGNED_SHOT_PATH") - .unwrap_or_else(|_| format!("misaligned_shot_{kind}.png")); - dev_shot_scenario(&mut game, &mut mode, &kind); - if kind == "hover-menu" { - let core = game.sim.core_position(); - game.sim - .set_machine_mode(game.sim.core.host_machine, MachineMode::Work); - game.set_cursor(core.0, core.1); - } - let hover_verb_bar_reticule_only = - matches!(kind.as_str(), "hover-menu" | "first-think" | "read-receipt"); - let presentation_progress = (kind == "thought-snap").then_some(0.12); - ShotHarness { - kind, - path, - frames: 150, - taken: false, - hover_verb_bar_reticule_only, - presentation_progress, - } - }); + let harness = std::env::var("MISALIGNED_SHOT") + .ok() + .map(checked_bevy_shot_kind) + .map(|kind| { + let path = std::env::var("MISALIGNED_SHOT_PATH") + .unwrap_or_else(|_| format!("misaligned_shot_{kind}.png")); + dev_shot_scenario(&mut game, &mut mode, &kind); + if kind == "hover-menu" { + let core = game.sim.core_position(); + game.sim + .set_machine_mode(game.sim.core.host_machine, MachineMode::Work); + game.set_cursor(core.0, core.1); + } + let hover_verb_bar_reticule_only = + matches!(kind.as_str(), "hover-menu" | "first-think" | "read-receipt"); + let presentation_progress = (kind == "thought-snap").then_some(0.12); + ShotHarness { + kind, + path, + frames: 150, + taken: false, + hover_verb_bar_reticule_only, + presentation_progress, + } + }); let rail_detail = RailDetail { expanded: harness.is_some() && std::env::var("MISALIGNED_SHOT_DETAIL").ok().as_deref() == Some("expanded"), @@ -3448,6 +3530,25 @@ fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &str) { } } +#[cfg(test)] +mod shot_catalog_tests { + use super::{BEVY_SHOT_KINDS, checked_bevy_shot_kind}; + + #[test] + fn screenshot_catalog_is_sorted_and_unique_for_binary_search() { + assert!( + BEVY_SHOT_KINDS.windows(2).all(|pair| pair[0] < pair[1]), + "the canonical screenshot catalog must stay strictly sorted" + ); + } + + #[test] + #[should_panic(expected = "unsupported MISALIGNED_SHOT")] + fn screenshot_catalog_rejects_unknown_values() { + checked_bevy_shot_kind("misspelled-proof".to_string()); + } +} + #[cfg(test)] mod visual_proof_scenario_tests { use super::{ diff --git a/tools/check.sh b/tools/check.sh index 9244f9a8..34370648 100755 --- a/tools/check.sh +++ b/tools/check.sh @@ -233,22 +233,8 @@ start_docs_gate "observed-run-fixtures" "bash tools/test_observed_run.sh" start_docs_gate "ledger-index" "bash tools/ledger_index.sh --check" start_docs_gate "project-operations" "python3 tools/work_orders.py check && python3 tools/scenario.py --check-definitions && python3 tools/project-status.py --check --offline" start_docs_gate "project-operations-fixtures" "python3 tools/test_project_ops.py" -# The single-quoted program is evaluated inside start_docs_gate, not here. -# shellcheck disable=SC2016 -start_docs_gate "env-registry" ' - env_reg=wiki/engineering/env.md - env_missing=0 - for v in $(grep -rhoIE --exclude='*.pyc' --exclude-dir='__pycache__' \ - "MISALIGNED_[A-Z_]+" crates tools .githooks 2>/dev/null | sort -u); do - grep -q "$v" "$env_reg" || { echo "FAIL: $v read in the tree but missing from $env_reg"; env_missing=1; } - done - if grep -rn "env::var" crates/misaligned-core --include="*.rs" | grep -q .; then - echo "FAIL: the sim library must not read the environment (env registry rule)" - env_missing=1 - fi - [ "$env_missing" -eq 0 ] - echo "env registry: OK" -' +start_docs_gate "env-registry" "python3 tools/env_registry_gate.py" +start_docs_gate "env-registry-fixtures" "python3 tools/test_env_registry.py" setup_local_pkg_config() { # Some local/dev images have the runtime libraries Bevy needs but not the diff --git a/tools/env_registry_gate.py b/tools/env_registry_gate.py new file mode 100755 index 00000000..7955beb8 --- /dev/null +++ b/tools/env_registry_gate.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +"""Validate environment-variable registration and the Bevy shot catalog.""" + +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + + +ENV_NAME = re.compile(r"\bMISALIGNED_[A-Z][A-Z0-9_]*\b") +CORE_ENV_READ = re.compile(r"\b(?:std::)?env::var(?:_os)?\s*\(") +BEVY_CATALOG = re.compile( + r"const\s+BEVY_SHOT_KINDS\s*:\s*&\[&str\]\s*=\s*&\[(.*?)\];", + re.DOTALL, +) +RUST_STRING = re.compile(r'"([a-z0-9][a-z0-9-]*)"') +CODE_SPAN = re.compile(r"`([^`]+)`") +SCAN_ROOTS = ("crates", "tools", ".githooks", ".tangled") + + +def text_files(root: Path, relative: str): + base = root / relative + if not base.exists(): + return + for path in sorted(base.rglob("*")): + if not path.is_file() or "__pycache__" in path.parts: + continue + try: + yield path, path.read_text(encoding="utf-8") + except (OSError, UnicodeDecodeError): + continue + + +def registered_names(root: Path, registry: str) -> list[str]: + used: set[str] = set() + for relative in SCAN_ROOTS: + for _, text in text_files(root, relative): + used.update(ENV_NAME.findall(text)) + return sorted(name for name in used if f"`{name}`" not in registry) + + +def core_environment_reads(root: Path) -> list[str]: + reads: list[str] = [] + for path, text in text_files(root, "crates/misaligned-core/src"): + for match in CORE_ENV_READ.finditer(text): + line = text.count("\n", 0, match.start()) + 1 + reads.append(f"{path.relative_to(root)}:{line}") + return reads + + +def bevy_shot_catalog(root: Path) -> tuple[list[str], list[str]]: + source_path = root / "crates/misaligned-bevy/src/main.rs" + try: + source = source_path.read_text(encoding="utf-8") + except OSError as error: + return [], [f"cannot read {source_path.relative_to(root)}: {error}"] + match = BEVY_CATALOG.search(source) + if match is None: + return [], ["Bevy source has no canonical BEVY_SHOT_KINDS catalog"] + kinds = RUST_STRING.findall(match.group(1)) + errors: list[str] = [] + if len(kinds) != len(set(kinds)): + duplicates = sorted({kind for kind in kinds if kinds.count(kind) > 1}) + errors.append(f"BEVY_SHOT_KINDS contains duplicates: {', '.join(duplicates)}") + if kinds != sorted(kinds): + errors.append("BEVY_SHOT_KINDS must remain sorted for runtime binary search") + return kinds, errors + + +def documented_bevy_shots(registry: str) -> tuple[set[str], list[str]]: + kinds: set[str] = set() + rows = 0 + for line in registry.splitlines(): + if not line.lstrip().startswith("|"): + continue + columns = [column.strip() for column in line.strip().strip("|").split("|")] + if len(columns) < 4: + continue + if columns[0] == "`MISALIGNED_SHOT`" and columns[1] == "`misaligned-bevy`": + rows += 1 + kinds.update(CODE_SPAN.findall(columns[2])) + errors = [] if rows else ["env.md has no MISALIGNED_SHOT row for misaligned-bevy"] + return kinds, errors + + +def validate(root: Path) -> list[str]: + registry_path = root / "wiki/engineering/env.md" + try: + registry = registry_path.read_text(encoding="utf-8") + except OSError as error: + return [f"cannot read {registry_path.relative_to(root)}: {error}"] + + errors: list[str] = [] + missing_names = registered_names(root, registry) + if missing_names: + errors.append( + "environment variables missing from wiki/engineering/env.md: " + + ", ".join(missing_names) + ) + + core_reads = core_environment_reads(root) + if core_reads: + errors.append( + "the sim library must not read the process environment: " + + ", ".join(core_reads) + ) + + source_kinds, source_errors = bevy_shot_catalog(root) + documented_kinds, registry_errors = documented_bevy_shots(registry) + errors.extend(source_errors) + errors.extend(registry_errors) + if source_kinds and not source_errors and not registry_errors: + source_set = set(source_kinds) + missing = sorted(source_set - documented_kinds) + stale = sorted(documented_kinds - source_set) + if missing: + errors.append( + "Bevy MISALIGNED_SHOT values missing from env.md: " + ", ".join(missing) + ) + if stale: + errors.append( + "env.md lists unsupported Bevy MISALIGNED_SHOT values: " + + ", ".join(stale) + ) + return errors + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument( + "--root", + type=Path, + default=Path(__file__).resolve().parent.parent, + help="repository root (defaults to the checkout containing this script)", + ) + args = parser.parse_args() + errors = validate(args.root.resolve()) + if errors: + for error in errors: + print(f"FAIL: {error}", file=sys.stderr) + return 1 + print("env registry: OK") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/test_ci_workflows.py b/tools/test_ci_workflows.py index cdd0cf42..d7db02be 100755 --- a/tools/test_ci_workflows.py +++ b/tools/test_ci_workflows.py @@ -22,6 +22,7 @@ RUST_SHARDS = { CHECK = RUST_SHARDS["core"] CORPUS = (WORKFLOWS / "corpus.yml").read_text(encoding="utf-8") SPEC = (WORKFLOWS / "spec-check.yml").read_text(encoding="utf-8") +LOCAL_CHECK = (ROOT / "tools/check.sh").read_text(encoding="utf-8") PKG_CONFIG = (ROOT / "tools/ci-pkg-config.sh").read_text(encoding="utf-8") CARGO = (ROOT / "Cargo.toml").read_text(encoding="utf-8") @@ -38,6 +39,13 @@ def push_constraint(workflow: str) -> str: def main() -> int: + for command in ( + "python3 tools/env_registry_gate.py", + "python3 tools/test_env_registry.py", + ): + require(LOCAL_CHECK, command, "local environment registry enforcement") + require(CORPUS, command, "hosted environment registry enforcement") + expected_shards = { "check.yml", "check-bevy-test.yml", diff --git a/tools/test_env_registry.py b/tools/test_env_registry.py new file mode 100755 index 00000000..c545c336 --- /dev/null +++ b/tools/test_env_registry.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +"""Fixtures for the environment-registry gate.""" + +from __future__ import annotations + +import subprocess +import tempfile +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent.parent +GATE = ROOT / "tools/env_registry_gate.py" + + +def write_fixture(root: Path, *, source_kinds=("alpha", "beta"), doc_kinds=None) -> None: + if doc_kinds is None: + doc_kinds = source_kinds + (root / "wiki/engineering").mkdir(parents=True) + (root / "crates/misaligned-bevy/src").mkdir(parents=True) + (root / "crates/misaligned-core/src").mkdir(parents=True) + (root / "tools").mkdir(parents=True) + values = ", ".join(f"`{kind}`" for kind in doc_kinds) + (root / "wiki/engineering/env.md").write_text( + "# Environment variable registry\n\n" + "| Variable | Surface | Values | Effect |\n" + "|---|---|---|---|\n" + f"| `MISALIGNED_SHOT` | `misaligned-bevy` | {values} | fixture |\n", + encoding="utf-8", + ) + source_values = "\n".join(f' "{kind}",' for kind in source_kinds) + (root / "crates/misaligned-bevy/src/main.rs").write_text( + "const BEVY_SHOT_KINDS: &[&str] = &[\n" + f"{source_values}\n" + "];\n" + 'fn main() { let _ = std::env::var("MISALIGNED_SHOT"); }\n', + encoding="utf-8", + ) + + +def run(root: Path) -> subprocess.CompletedProcess[str]: + return subprocess.run( + ["python3", str(GATE), "--root", str(root)], + text=True, + capture_output=True, + check=False, + ) + + +def require_failure(result: subprocess.CompletedProcess[str], needle: str) -> None: + if result.returncode == 0 or needle not in result.stderr: + raise AssertionError( + f"expected failure containing {needle!r}\n" + f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}" + ) + + +def main() -> int: + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) + write_fixture(root) + result = run(root) + if result.returncode != 0: + raise AssertionError(f"valid fixture failed:\n{result.stderr}") + + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) + write_fixture(root) + missing_name = "MISALIGNED_" + "UNDOCUMENTED" + (root / "tools/read-env.sh").write_text( + f'printf "%s" "${{{missing_name}:-}}"\n', encoding="utf-8" + ) + require_failure(run(root), "environment variables missing") + + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) + write_fixture(root) + (root / "crates/misaligned-core/src/lib.rs").write_text( + 'fn leak() { let _ = std::env::var("HOME"); }\n', encoding="utf-8" + ) + require_failure(run(root), "sim library must not read") + + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) + write_fixture(root, doc_kinds=("alpha",)) + require_failure(run(root), "values missing from env.md: beta") + + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) + write_fixture(root, doc_kinds=("alpha", "beta", "retired")) + require_failure(run(root), "lists unsupported Bevy") + + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) + write_fixture(root, source_kinds=("beta", "alpha")) + require_failure(run(root), "must remain sorted") + + print("env registry fixtures: OK") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/wiki/engineering/env.md b/wiki/engineering/env.md index 790a0efa..9b6984a8 100644 --- a/wiki/engineering/env.md +++ b/wiki/engineering/env.md @@ -13,7 +13,10 @@ Status note: created 2026-07-09 (Cameron: there should always be a way 2026-07-14: `MISALIGNED_SHOT_SIZE=minimum` selects the clinical frame's exact supported 960x540 logical resize floor for deterministic Bevy evidence; `MISALIGNED_SHOT_DETAIL=expanded` selects the operator frame's deliberate - rail layer without changing game state. + rail layer without changing game state. 2026-07-19: Bevy's screenshot kinds + live in one sorted runtime catalog, unknown values fail instead of silently + producing a generic frame, and the local plus hosted registry gate requires + this page to name that catalog exactly. Stage: Process (standing infrastructure) Design: - wiki/vision/simulation-laws.md#justification-and-legibility @@ -47,7 +50,12 @@ is sim or frontend state, never an environment variable. | Variable | Surface | Values | Effect | |---|---|---|---| -| `MISALIGNED_SHOT` | `misaligned-bevy` | `flat`, `hall`, `hall-material`, `floor-lights-close`, `wide`, `close`, `dark`, `zoomin`, `zoomout`, `digital-reach`, `build-route-families`, `build-deceive-routes`, `build-committed-route`, `build-switch-digital`, `build-switch-real`, `intel`, `tokens`, `thoughtflow`, `thoughtflow-wide`, `visual-proof`, `person-proof`, `exposure-record`, `exposure-overflow`, `service-shift-real`, `service-shift-digital`, `service-incident-resolved`, `signal`, `ears`, `ears-digital`, `eyes-white`, `eyes-form`, `consume-demand`, `consume-thought`, `produce-think`, `draw-lie`, `opening`, `opening-digital`, `clinical-threat`, `pilot-last-chance`, `operator-pressure`, `hover-menu`, `first-think`, `read-receipt`, `menu`, `worklight`, `worklightoff`, `wake1`, `wake2`, `wake3` | Dev screenshot harness: stage a deterministic scenario, settle, report the staged `RenderMode` dialect, save one PNG, then exit. `tools/bevy-headless.sh` consumes that runtime dialect rather than maintaining a second kind list: REAL requires the material fog audit, while DIGITAL remains screenshot evidence because material fog has no entity surface there. `hall` / `hall-material` frame all six Foundation rows in the flat/material views with Eyes and one staged owned expansion; `floor-lights-close` uses the same hall staging but approaches the unobstructed southern row at machine/floor scale; `digital-reach` holds controlled, tapped, reachable, closed-segment, and air-gap reach states in one DIGITAL survey frame; `build-route-families`, `build-deceive-routes`, and `build-committed-route` stage the shared network-link family, candidate, and durable receipt pages; `build-switch-digital` / `build-switch-real` stage the same exact inert small-switch footprint on one earned growable pad before any source, actuator, device, or graph effect; `visual-proof` stages one-move/one-slug Thought evidence in an earned room; `person-proof` stages a camera-earned volumetric person; `service-shift-real` / `service-shift-digital` fire one real favor-build reservoir while Marcus's authored shift is off-site, then hold the carried packet at its real hall endpoint in paired view dialects; `service-incident-resolved` advances that exact scenario once so the real graph edge lands and the carried mark disappears; `exposure-record` places six actual Exposure units on the live host in telemetry darkness while `exposure-overflow` places eighteen to prove the fixed seven-slot rack and consolidated final tab; `intel` stages tick-zero intel tiers (no camera tap); `tokens` enqueues D5/T4 on the host; `thoughtflow` / `thoughtflow-wide` stage the THINK fluid (slugs, meniscus, filament snap) at close and wide framing; `signal` focuses the known environmental monitor before any feed tap to capture its cold-signal presence cue; `ears` / `ears-digital` stage the same semantic audio capture in REAL or DIGITAL, pulsing only at the environmental monitor while spatial fog remains unchanged; `eyes-white` / `eyes-form` freeze the first-Eyes source held white and then contracted around the resolving chassis; `consume-demand` / `consume-thought` run the host through a sim-authored WORK queue swallow or passive-core Thought draw; `produce-think` / `draw-lie` capture sim-authored Thought/record production or the rigid record transfer into a staged LIE well; `opening` / `opening-digital` freeze the untouched tick-zero state behind the same black `WORK / THINK` boundary in either world dialect; `clinical-threat` stages that DIGITAL opening with one real pilot strike so the compact rail exception is observable; `pilot-last-chance` stages the third of four strikes so the pinned NEXT line, white-current/gray-rest host mode line, and `NOW / PILOT` label can be read together; `operator-pressure` adds one concerned observer and a full recording buffer to the one-strike opening so the compact frame proves a single THREAT, a promoted buffer NEXT, and no stale world cue; every established proof scenario other than `first-think` stages the already-visible world so the new boundary does not invalidate its subject; `hover-menu` captures the resting WORK/THINK/LIE line beside the server via reticule focus (no pointer), while `first-think` stages the real opening Thought route and captures only the black `WORK / THINK / LIE` boundary with THINK current before a signal lands; `read-receipt` focuses one reachable foreign device with its TAP price/signature stack at the reticule; `menu` opens the context menu on the tapped environmental monitor; `worklight` / `worklightoff` pair the dev work light on and off; `wake1/2/3` freeze the wake choreography at the stutter flash, the column, and the pull-back. | +| `MISALIGNED_SHOT` | `misaligned-bevy` | `opening`, `opening-digital`, `first-think`, `wake1`, `wake2`, `wake3`, `clinical-threat`, `assurance-office`, `pilot-last-chance`, `operator-pressure` | Opening and pressure evidence. The opening pair freezes the untouched black choice boundary; `first-think` holds THINK current before signal; the wake frames freeze its three choreography beats. The remaining kinds stage a real strike, discovered Assurance Office, last-chance pilot state, or competing observer/buffer pressure in the established world. | +| `MISALIGNED_SHOT` | `misaligned-bevy` | `flat`, `hall`, `hall-material`, `floor-lights-close`, `wide`, `close`, `dark`, `zoomin`, `zoomout`, `digital-reach`, `signal`, `ears`, `ears-digital`, `eyes-white`, `eyes-form`, `worklight`, `worklightoff` | World and view evidence. These select DIGITAL or REAL survey/close framing, exact zoom bounds, reach topology, signal/audio/Eyes states, the unobstructed hall lighting proof, or the paired developer work-light state. | +| `MISALIGNED_SHOT` | `misaligned-bevy` | `build-route-families`, `build-deceive-routes`, `build-committed-route`, `build-switch-digital`, `build-switch-real`, `hover-menu`, `read-receipt`, `menu`, `recruit-menu` | Action and route evidence. These stage exact route families, candidates, durable receipts, paired switch footprints, the attached verb line, a device receipt, a context menu, or the authored recruitment choices. | +| `MISALIGNED_SHOT` | `misaligned-bevy` | `operations`, `operations-intel`, `operations-people`, `operations-personas`, `operations-links`, `held-choice`, `two-pane`, `standing-read`, `intel-altitude-close`, `intel-altitude-far` | Operations and read evidence. The workspace kinds select its canonical views and relationship pane; held-choice, two-pane, and standing-read hold their exact interaction states; the altitude pair differs only in the DIGITAL camera's semantic intel threshold. | +| `MISALIGNED_SHOT` | `misaligned-bevy` | `person-proof`, `evidence-proof`, `evidence-proof-digital`, `exposure-record`, `exposure-overflow`, `service-shift-real`, `service-shift-digital`, `service-incident-resolved` | Physical custody evidence. These stage an earned person, paired witness evidence marks, literal/overflow Exposure records, or the same person-carried service task before and after its real arrival effect. | +| `MISALIGNED_SHOT` | `misaligned-bevy` | `intel`, `tokens`, `thoughtflow`, `thoughtflow-wide`, `thought-snap`, `thought-tap`, `visual-proof`, `consume-demand`, `consume-thought`, `produce-think`, `draw-lie` | Resource and effect evidence. These stage authored intel, host queues, close/wide Thought flow, exact snap/tap states, one-move/one-slug proof, sim-authored consumption/production, or rigid Exposure transfer into LIE. | | `MISALIGNED_SHOT` | `misaligned-assets` | `dead`, `foreign`, `idle`, `busy`, `core`, canonical modes `work`, `think`, `lie` plus the legacy screenshot aliases `dayjob` (= work), `research`/`operations`/`ops` (= think), `conceal` (= lie) — each optionally suffixed `_ladder`/`_ring`/`_wash` — `lineup[_