Scrubbed extract of
Spatial-Cognition-Platform/Epic-S32-Coherence-Board-And-Channel-Presence/IP-SCP-45.11-Channel-Presence-Model.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: 514 created: ‘2026-09-21’ epic: Epic-S32-Coherence-Board-And-Channel-Presence user-story: IP-SCP-45.11 effort: null points: 1 sizing: repos: 1 files: 2 reads: 3 read_files: 3 deliverables: 5 seams: 1 target_repo: spatial-cognition-xr precedence:
- ‘[[../Epic-S27-Control-Room-Scene-And-Substrate/IP-SCP-40.11-Universal-State-Grammar-Contract]]’ seams: [channel-presence-contract] 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
- channel-presence
IP-SCP-45.11: Channel Presence Model
Epic: [[README|Epic S32]] · Stage: 3 · Gate: B/R1 · Points: 1
Mission
Engine-free channel-presence model: per-session etch state — where the session was born and which channel is watching it now, from the five spec channels (browser, editor, chat, hook, headset) mapped over the ADR-003 source enum with the shipped XR member (FR-AIS-007) and the resolver channel on trust_gate.resolved; a door-pip presence set for Zone F with a pulse flag while a remote channel is actively resolving; and the advisory handoff reducer — a 400 ms pinch-hold on the handoff verb (wrist strip or Critical slab) issues a deep-link handoff (FR-AIS-025 token, accepted by OUI via FR-OUI-005), the object shows “held by remote” until the first reconciled resolve event returns it, taking it back is the same verb, and an undeliverable link springs the verb back. No lock is modelled anywhere — both channels may act and the first reconciled event wins; presence unknown greys, never guesses.
Why
Card 18’s value — “the headset is one of several honest surfaces” — is entirely a projection-and-reducer question before it is ever a shader question; the etch glyphs, pip pulses and springback in 45.21 can only render what this model decides, and the sound pair (whoosh out / tick back) is emitted here as data.
Sizing
Declared table estimate: 1 point; 2 source files (tests/generated artifacts excluded), three concrete bootstrap reads, 5 acceptance boxes, one coherent channel-presence-contract 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— the hold-window reducer precedent for the 400 ms pinch-hold detector.C:\Users\coryp\Developer\spatial-cognition-xr\unity\SpatialCognition\Assets\Scp\Core\Scp.App\Demo\DemoStageLayout.cs— shape reference for the Zone F pip-cluster placement row the ControlRoom profile carries (IP-SCP-40.15 owns the table).C:\Users\coryp\Developer\obsidian-sync\Projects\Spatial-Cognition-Platform\Guide-Control-Room-Layout-Constants.md— Zone E/F anchoring, exclusion-volume rule keeping pips out of the doorway volume.
Gate Policy
| Trigger | Gate type | Condition |
|---|---|---|
| Ambiguity / missing contract | Confidence | Resolve from the epic binding contracts; record remaining uncertainty rather than invent behavior. |
| Scope exceeds declared size | Confidence | Re-score concrete files/seams; above 3 points requires decomposition before expansion. |
| Failing or missing verification artifact | Trust | No acceptance or status promotion; exit code alone is insufficient. |
| Core asmdef gains an engine reference | Trust | Stop work — no UnityEngine under Assets/Scp/Core/** (ADR-SCP-010). |
| Any edit to AgentOps scene/kits/prefabs/Blender families | Trust | Refuse; this IP touches engine-free source only. |
| A figure not in the spec (pip spacing, pulse rate) | Confidence | Mark tunable; resolve in the gym. |
| Budget rows from the manifest | Perf | Replication rides existing session deltas — no new polling loop; the 400 ms hold window is the spec’s. |
Deliverables
-
ChannelPresence.cs(per-session etch: born-channel + watching-channel over the five spec channels plus unknown/grey; pip set with per-entry remote-resolving pulse flag) andChannelHandoffReducer.csunderScp.Projection/Scp.App— no Unity types, deterministic 1/72 s steps. - 400 ms pinch-hold detector: a hold shorter than the window never issues; exactly the window (spec value, pinned once) issues the deep-link handoff; release-after-springback can re-issue (no latch).
- Advisory-race tests with inverted proofs: two resolve events from different channels → the first reconciled one wins and the second is absorbed silently; no state anywhere in the model expresses “locked”.
- Springback: undeliverable-link input returns the verb to idle and leaves the slab object untouched; foreign-born sessions carry their origin etch and XR never overwrites it.
- Cue data: reducer emits
whooshon departure toward the door pip andtickon return — staleness-adjacent silence is 45.10’s rule, not duplicated here; independent tests red-first,dotnet test Scp.Core.slngreen on the stage PR.
Execution Phases
Worktree feat/IP-SCP-45.11-channel-presence-model from BASE (post-Wave-1 main); red test commit then feat commit, PR, review; cloud lane (L1-headless) per frontmatter. Standard TDD/review per [[ADR-017-Human-Review-Branching-And-TDD-Workflow|ADR-017]].
Verification and evidence
-
R1 (CI):
ci.ymlrun 15862, SHA23d62a5— green. -
R2-batchmode: n/a —
needs_unity: false/ R1-only (L1-headless); no Editor launched, no scene/kit/prefab/.inputactionsin the diff. -
Inverted proofs: 19 proof-named tests; the
InvertedProof_prefix is this unit’s convention, and the count matches the implementer’s own report exactly. Names grepped from this unit’s own test files at its documented head:ChannelHandoffReducerTests.InvertedProof_APressTargetingAnUnknownChannelIsRejectedRatherThanGuessed— guard: (not characterised; read the named test)ChannelHandoffReducerTests.InvertedProof_AnUnattributableResolveGreysTheWatcherRatherThanGuessingWeb— guard: (not characterised; read the named test)ChannelHandoffReducerTests.InvertedProof_BothChannelsMayActOnTheSameSessionWithoutRefusal— guard: (not characterised; read the named test)ChannelHandoffReducerTests.InvertedProof_EquivalentStepSequencesProduceIdenticalStates— guard: (not characterised; read the named test)ChannelHandoffReducerTests.InvertedProof_HoldShorterThanTheWindowNeverIssues— guard: (not characterised; read the named test)ChannelHandoffReducerTests.InvertedProof_NoStateAnywhereExpressesLocked— guard: (not characterised; read the named test)ChannelHandoffReducerTests.InvertedProof_NonMonotonicTicksAndRateChangesAreRejected— guard: (not characterised; read the named test)ChannelHandoffReducerTests.InvertedProof_PresenceCuesCarryNoStalenessOrFrostVocabulary— guard: (not characterised; read the named test)ChannelHandoffReducerTests.InvertedProof_ReleaseBeforeTheWindowAbandonsTheHandoff— guard: (not characterised; read the named test)ChannelHandoffReducerTests.InvertedProof_SecondPressWhileHeldByRemoteDoesNotStackASecondIssue— guard: (not characterised; read the named test)ChannelHandoffReducerTests.InvertedProof_TheWindowIsPinnedToTheSpecValueAndCeilsAtSeventyTwoHertz— guard: (not characterised; read the named test)ChannelHandoffReducerTests.InvertedProof_TwoResolveEventsFromDifferentChannelsFirstReconciledWinsAndSecondIsAbsorbedSilently— guard: (not characterised; read the named test)ChannelHandoffReducerTests.InvertedProof_XrNeverOverwritesAForeignBornEtch— guard: (not characterised; read the named test)ChannelPresenceTests.InvertedProof_AbsentOrUnrecognizedSourceGreysInsteadOfDefaultingToWeb— guard: (not characterised; read the named test)ChannelPresenceTests.InvertedProof_KnownPresenceContributesNoLifecycleStateToTheGrammar— guard: (not characterised; read the named test)ChannelPresenceTests.InvertedProof_PipPulsesOnlyWhileARemoteResolveIsPending— guard: (not characterised; read the named test)ChannelPresenceTests.InvertedProof_PresenceSurfaceIsEngineFree— guard: (not characterised; read the named test)ChannelPresenceTests.InvertedProof_TheHeadsetPipNeverPulsesBecauseTheHeadsetIsNotRemote— guard: (not characterised; read the named test)ChannelPresenceTests.InvertedProof_ThePipSetHasNoGreyEntryToGuessWith— guard: (not characterised; read the named test)
-
Local R2-MCP / R3 / R4: n/a — no Unity, no headset, no device (ADR-SCP-015 §3 unchanged).
Related
- [[README#Binding contracts|Epic S32 binding contracts]] — mandatory on this dispatch.
- [[../Feature-Requests/SCFR-028-channel-presence-and-cross-surface-handoff|SCFR-028]] — the source proposal.
- [[../Prompt-Control-Room-Asset-Build|Control Room asset-build prompt]] · [[../Guide-Asset-Pipeline-Process|asset pipeline guide]] · [[IP-Sizing-Rubric]].
- Provider reads: FR-AIS-007 (
source: XR, done), FR-AIS-025, FR-OUI-005 (AI-Service-Implementation/ADP-Orchestration-UIInterface-Requests/). - Forward: [[../Epic-S27-Control-Room-Scene-And-Substrate/IP-SCP-40.11-Universal-State-Grammar-Contract|IP-SCP-40.11]] — declared predecessor; [[IP-SCP-45.21-Channel-Etch-And-Door-Pips-View|IP-SCP-45.21]] — first consumer of
channel-presence-contract.