Scrubbed extract of
Spatial-Cognition-Platform/Epic-S31-Sandbox-Wall-And-Delivery-Lane/IP-SCP-44.10-Sandbox-Tile-Projection-Read-Only.md— cited by the Chronicle (07-the-long-march). Redactions: phone / service detail / secrets / clearance / personal / internal hosts.
type: implementation-plan category: Work project: Spatial-Cognition-Platform priority: High status: review pr: 520 created: ‘2026-09-21’ epic: Epic-S31-Sandbox-Wall-And-Delivery-Lane user-story: IP-SCP-44.10 effort: null points: 2 sizing: repos: 1 files: 4 reads: 3 read_files: 3 deliverables: 6 seams: 1 target_repo: spatial-cognition-xr precedence:
- ‘[[../Epic-S27-Control-Room-Scene-And-Substrate/IP-SCP-40.11-Universal-State-Grammar-Contract|IP-SCP-40.11]]’ seams: [sandbox-tile-projection] parallel_group: lane-core review-profile: standard model: null tokens: 0 verification_class: B verification_rung: R1 needs_unity: false execution_lane: L1-headless stage: ‘3’ tags:
- implementation-plan
- control-room
- sandbox-wall
- asset-pipeline
IP-SCP-44.10: Sandbox Tile Projection Read-Only
Epic: [[README|Epic S31]] · Stage: 3 · Gate: B/R1 · Points: 2
Mission
The engine-free, read-only projection record for the Sandbox Wall: per-server plate state (health, 1 Hz heartbeat breath phase, panic, backup-freshness value or explicit unknown), per-tile state (capability class ∈ {Rounded reversible, Sharp irreversible, Ringed side-effecting, Hatched env-scoped} resolved from live ToolCapability declarations, never a config fallback, declaration-stale flag, enforcement-level frame value), the spark ledger (tool-call start/finish paired by _meta.correlationId → outbound / at-tile / returning / parked), and container inventory — plus the pinned-or-held filter: sparks only for sessions the operator pinned or is holding, everything else aggregated into a per-plate glow scalar. Caps are model invariants: ≤ 120 tiles (overflow counted, not rendered), ≤ 32 concurrent sparks (past-cap calls fold into the glow), ≤ 12 containers (beyond-count handed to the impostor renderer). Inputs are the S4 projections of FR-AIS-023 (registry + tool-call lifecycle with correlation) and FR-MCP-003 (container inventory + capability class); until an FR lands, its field renders the unknown state — never a guess.
Why
Card 12’s honesty rules — never auto-complete a lost call, never show a stale declaration as current, never spark for an unwatched session — are deterministic presentation logic, and the epic’s trust gates (“class is never a config guess”, “no client-side invented state”) must be R1-proven here, before any renderer exists.
Sizing
Declared table estimate: 2 points; 4 source files (tests/generated artifacts excluded), three concrete bootstrap reads, 6 acceptance boxes, one coherent sandbox-tile-projection boundary. Re-score after concrete file census; do not inflate scope to consume points.
Context Bootstrap
C:\Users\coryp\Developer\spatial-cognition-xr\unity\SpatialCognition\Assets\Scp\Core\Scp.App\CommandArmingMachine.cs— engine-free state-machine precedent (fixed step, pure state transitions, tests) for the spark ledger.C:\Users\coryp\Developer\spatial-cognition-xr\unity\SpatialCognition\Assets\Scp\Core\Scp.App\Demo\DemoStageLayout.cs— shape of placement tables the projection reads (ControlRoom rows arrive in IP-SCP-40.15).C:\Users\coryp\Developer\obsidian-sync\Projects\Spatial-Cognition-Platform\Guide-Control-Room-Layout-Constants.md— cadence classes (heartbeats 1 Hz, deltas ≤ 5 Hz) this model budgets against.
Gate Policy
| Trigger | Gate type | Condition |
|---|---|---|
| Core asmdef gains an engine reference | Trust | Stop work — no UnityEngine under Assets/Scp/Core/** (ADR-SCP-010 §1–2). |
| Any edit to AgentOps scene, kits, prefabs or existing Blender families | Trust | Refuse — this epic is additive on the ControlRoom scene only. |
| A figure not in the spec (glow aggregation rate, spark speed default) | Confidence | Mark tunable; the model exposes the parameter, the gym decides the value. |
| Budget row SandboxWall ≤ 3k / caps ≤120/32/12 | Perf | Caps are invariants of this model (renderer can only show what this emits); assert each with an inverted proof. |
Deliverables
- Projection records (plate / tile / spark / container state) created under
Scp.Projection— plain deterministic values, no Unity types, no asset references; the class enum carries the four capability classes and nothing resolves a class from config fallback: an unreachable server yields last-known class + stale-declaration flag (inverted proof). - Spark ledger pairs lifecycle events by
_meta.correlationId; an unmatched start parks forever (hourglass value) — no timer, no auto-complete path anywhere in the code (inverted proof); ≤ 32 concurrent, past-cap calls fold into the plate glow scalar. - Pinned-or-held filter reads the operator’s workspace pin state (ADR-SCP-005) and emits per-session spark streams vs aggregate glow accordingly; determinism proven by golden trace at a fixed 1/72 s step.
- Plate health: breath value derived from the 1 Hz heartbeat cadence; panic flag; backup clock value is a provider-supplied field or an explicit
unknown(no synthetic freshness); enforcement-level frame value projected perMCP_ENVIRONMENTstate. - Container inventory capped at 12 with a beyond-count for the impostor renderer; tile overflow past 120 per class reported as a count, never silently dropped.
-
cd core && dotnet test Scp.Core.slngreen with the new suite (class integrity, park-forever, filter correctness, all three caps, unknown-never-guessed); CI R1 green on the PR (ADR-SCP-015 amended: run id is the proof).
Execution Phases
Isolated worktree feat/IP-SCP-44.10-sandbox-tile-projection from BASE (post-Wave-1 main); red test commit, then the feat commit; PR; review. Lane: cloud (L1-headless); depends on IP-SCP-40.11’s grammar/cue contract landing first (wave C2); parallel with 44.11 — the two share only 40.x substrate.
Verification and evidence
- R1 (CI):
ci.ymlrun 15889, SHAb2273b4— green. - R2-batchmode: n/a —
needs_unity: false/ R1-only (L1-headless); no Editor launched, no scene/kit/prefab/.inputactionsin the diff. - Inverted proofs: 10, all in
SandboxWallProjectionTests(core/tests/Scp.Projection.Tests/SandboxWallProjectionTests.csatb2273b4; names extracted by grepping that file forproof, every one a[Fact]). This unit’s convention is theInvertedProof_prefix, snake-case body.SandboxWallProjectionTests.InvertedProof_no_live_declaration_yields_last_known_class_and_stale_never_a_guess— guard: withliveDeclarations: Array.Emptyand a previous tile, the class carries over asSharpIrreversible,DeclarationStaleis true andDisplayStateisStale— absence never becomes a guess.SandboxWallProjectionTests.InvertedProof_the_resolver_exposes_no_config_default_parameter— guard: the “class is never a config guess” rule is structural — reflection over every public staticResolveoverload asserts no parameter is typedSandboxCapabilityClassorSandboxCapabilityClass?, so no caller can feed one in.SandboxWallProjectionTests.InvertedProof_unknown_backup_freshness_projects_as_unknown_never_a_default— guard:PlateBackupFreshness.UnknownprojectsIsKnownfalse,Agenull andDisplayState == UnknownValue.SandboxWallProjectionTests.InvertedProof_backup_freshness_rejects_a_computed_or_negative_age— guard:PlateBackupFreshness.Known(TimeSpan.FromSeconds(-1))throwsArgumentOutOfRangeException— a client-side computed age is not constructible.SandboxWallProjectionTests.InvertedProof_unmatched_parked_start_lives_forever_with_no_timer_or_autocomplete— guard: a parked spark survives five consecutive reads (Assert.Single(ledger.Sparks)each time), and reflection finds noDateTimeOffset/TimeSpanparameter anywhere onSandboxSparkLedgerand no method named for Timeout/Expire/Autocompl/Sweep/Prune/Evict/Reap.SandboxWallProjectionTests.InvertedProof_unmatched_finish_invents_no_spark— guard: aFinishfor a correlation the ledger never started returns false and leavesSparksempty andTrackedCount0 — no phantom completion.SandboxWallProjectionTests.InvertedProof_container_overflow_is_counted_not_silently_dropped— guard: 30 containers render 12 and reportBeyondCount18, with rendered + beyond asserted equal to the 30 inputs.SandboxWallProjectionTests.InvertedProof_watching_the_stray_session_moves_it_out_of_the_glow— guard: watched set and glow are exact complements, never both — pinning all three sessions gives 3 watched andGlow == 0; pinning none gives 0 watched andGlow == 3.SandboxWallProjectionTests.InvertedProof_tile_overflow_past_one_twenty_is_counted_per_class_never_dropped— guard: 125 tiles render 120 withOverflowTileTotal5 equal to the sum ofOverflowTilesByClass, plus a membership check proving every input tile is accounted for exactly once.SandboxWallProjectionTests.InvertedProof_edited_golden_breaks_the_determinism_trace— guard: the golden is load-bearing, not decoration — a one-character tamper ("glow":1→"glow":2) no longer matches the committedsandbox-wall-determinism.json.
- Local R2-MCP / R3 / R4: n/a — no Unity, no headset, no device (ADR-SCP-015 §3 unchanged).
Related
- [[README#Binding contracts|Epic S31 binding contracts]] — mandatory on every dispatch.
- [[../Feature-Requests/SCFR-025-sandbox-wall-mcp-registry-and-executions|SCFR-025]] — §3B/§3C: read-only, no reasoning objects, the filter is workspace state.
- Provider surfaces read (projected by S4): FR-AIS-023, FR-MCP-003 (numbering sheet cross-project table).
- [[../Prompt-Control-Room-Asset-Build|Prompt-Control-Room-Asset-Build]] · [[../Guide-Asset-Pipeline-Process]] · [[IP-Sizing-Rubric]]
- Predecessor: [[../Epic-S27-Control-Room-Scene-And-Substrate/IP-SCP-40.11-Universal-State-Grammar-Contract|IP-SCP-40.11]] — the state grammar this projection speaks.