> For the complete documentation index, see [llms.txt](https://faction-os.gitbook.io/faction-os-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://faction-os.gitbook.io/faction-os-docs/.spec_system/archive/phases/phase_22/prd_phase_22.md).

# PRD Phase 22: Projection Foundation

**Status**: Complete **Sessions**: 8 (initial estimate) **Estimated Duration**: 4-8 days **Progress**: 8/8 sessions (100%)

***

## Overview

Create the foundation for FactionOS game projection state: one versioned, serializable `gameProjection.ts` aggregate in `apps/web`, folded from the existing event stream, persisted locally with replay/mock safeguards, and covered by reducer, store, persistence, erasure, and honesty tests.

Phase 22 renders no new battlefield, audio, camp, report, or ceremony surface. It establishes the state authority that later game phases read without inventing parallel progress, attention, or enemy state.

The former ongoing-project projection docs have been folded into this Phase 22 PRD and the Phase 22 session stubs. This PRD is the standalone source of truth. References to "Phase 1" in source material mean the first game-projection implementation phase, tracked by `.spec_system` as Phase 22.

***

## Progress Tracker

| Session | Name                        | Status   | Est. Tasks | Validated  |
| ------- | --------------------------- | -------- | ---------- | ---------- |
| 01      | Projection Contract         | Complete | \~12-25    | 2026-07-05 |
| 02      | Hero Attention Reducer      | Complete | \~12-25    | 2026-07-05 |
| 03      | Legion Enemy Reducer        | Complete | \~12-25    | 2026-07-05 |
| 04      | Camp Link Boundaries        | Complete | \~12-25    | 2026-07-05 |
| 05      | Store Folding And Selectors | Complete | \~12-25    | 2026-07-05 |
| 06      | Persistence Lifecycle       | Complete | \~12-25    | 2026-07-05 |
| 07      | Honesty Regression Tests    | Complete | \~12-25    | 2026-07-05 |
| 08      | Erasure Docs And Validation | Complete | \~12-25    | 2026-07-05 |

***

## Completed Sessions

* Session 01: Projection Contract - Completed 2026-07-05.
* Session 02: Hero Attention Reducer - Completed 2026-07-05.
* Session 03: Legion Enemy Reducer - Completed 2026-07-05.
* Session 04: Camp Link Boundaries - Completed 2026-07-05.
* Session 05: Store Folding And Selectors - Completed 2026-07-05.
* Session 06: Persistence Lifecycle - Completed 2026-07-05.
* Session 07: Honesty Regression Tests - Completed 2026-07-05.
* Session 08: Erasure Docs And Validation - Completed 2026-07-05.

***

## Upcoming Sessions

* None. Phase 22 is complete.

***

## Objectives

1. Establish a complete v1 `GameProjection` contract, initial state, pure reducer, alert-focus derivation, and defensive browser-storage helpers.
2. Implement Phase 22 reducer slices for hero lifecycle, attention, live enemy state, and camp-link boundaries without progress farming.
3. Wire projection folding into the existing web store event-apply path with narrow selectors and reset behavior.
4. Add guarded local persistence with debounce, max-latency/page-lifecycle flushing, replay guards, mock guards, and browser local-erasure coverage.
5. Prove replay, reconnect, mock, and snapshot traffic cannot mint persisted progress numbers.

## Prerequisites

* Phase 21 completed.
* This PRD and the relevant session stub are reviewed before each session.
* Existing stores remain authoritative for faction selection, scrolls, achievements, War Room membership, settings, Notice Board messages, replay buffers, animation state, audio state, and raw prompts.

## Dependencies And Enables

* Depends on Phase 21: Bottom Rail Focused Surface Expansion.
* Enables later game presentation phases for battlefield sectors, camps, attention, War Tide, Dawn Report, hero growth, collections, social, and audio ceremony without adding another state authority.

***

## Source Scope

Source material folded into this PRD came from `docs/game-design/01-product-vision-brief.md`, `docs/game-design/02-core-gameplay-and-player-verbs.md`, `docs/game-design/03-game-loops-and-onboarding.md`, `docs/game-design/04-world-map-and-combat-systems.md`, `docs/game-design/05-progression-economy-and-collections.md`, `docs/game-design/06-social-and-scoring-systems.md`, `docs/game-design/07-content-audio-and-feature-inventory.md`, `docs/game-design/08-production-roadmap-and-phase-a-spec.md`, `docs/game-design/09-risk-boredom-and-integrity-audit.md`, `docs/game-design/10-technical-design-and-game-projection.md`, `docs/game-design/11-open-questions-and-change-log.md`, `docs/game-design/12-utility-admin-holdout-inventory.md`, and `docs/game-design/13-implementation-phase-outline.md`. Implementation details live in the Phase 22 session stubs in this directory.

***

## Product Rules

Everything serves one conversion: real agent work becomes legible, juicy, escalating game consequence. FactionOS is "Command Idle" / Real-Work Strategy: it borrows faction fantasy, hero units, bases, fog-of-war reveal, voice barks, numbers-go-up compulsion, offline progress, boss fights, roles, loot, guild seasons, unit attachment, and run structure; it rejects fake work, clicking for clicking, grinding, time gates, punishing neglect, and permadeath of valued work.

The five Fun Laws are non-negotiable:

1. The forge only burns real fire. No game resource is generated by in-game clicking; every point traces to a real agent event such as `tool_use`, `mission_complete`, or `plan_completed`.
2. Fun-per-glance. The cockpit is peripheral; every glance must deliver state, and every real event must be legible within 1 second.
3. The game asks for exactly the attention the work deserves. High-value real actions, including `awaiting_input`, plan approval, guarded actions, and quest acceptance, are the highest-value game actions.
4. Never reward waste. Tokens burned are not score; scoring is outcome-weighted and efficiency-tempered.
5. Losing is content. Errors, failed missions, and stalled tasks spawn enemies instead of punishment.

Every game phase must ship demoable value, keep all game state flowing through `gameProjection.ts`, ship reduced-motion equivalents with motion, audit Fun Laws 1 and 4, and update docs in the same implementation window when decisions diverge from this PRD.

***

## Player And Verb Context

The player fantasy is Warchief, not spreadsheet operator. Existing faction and hero systems already provide:

* First-run factions: Orcs, Humans, Elves, Undead.
* Hidden Lobster faction unlocked after all five fundamental tomes.
* Faction name, motto, sigil, accent color, voice style, hero archetypes, and building names.
* Faction-driven hero faction, portrait/standee, roster/battlefield styling, ranking bucket, visual accent, and optional audio barks.

Phase 22 must not copy those source-of-truth settings into the projection. The projection tracks event-derived aggregates only.

Shipped player verbs and later ceremony homes:

| Verb                                    | Fantasy framing                            |
| --------------------------------------- | ------------------------------------------ |
| Spawn hero                              | Summon a champion                          |
| Send prompt/orders                      | Rally order from the command tent          |
| Answer `awaiting_input`                 | Counsel; douse the flare                   |
| Approve/reject plans                    | Seal or refuse the battle plan             |
| Permission / guarded-action decision    | Open or hold the gate                      |
| Accept a quest                          | Muster against a Blight camp               |
| Post/resolve a notice                   | Tavern contract posted or fulfilled        |
| `collect_scroll`                        | Loot the tome, the one pure-game click     |
| Dismiss hero                            | Honorable discharge toward Hall of Banners |
| Join War Room                           | March to the shared front                  |
| `hero_state_override`                   | Warchief's decree                          |
| Codebase/analysis/project scan          | Dispatch Scouts                            |
| Approve/convert inbound channel command | Receive the Emissary                       |

Phase 22 adds no ceremony for these verbs, but its state shape must leave one future home for their consequences.

***

## Phase Scope

| Scope | Details                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| In    | Create `apps/web/src/lib/gameProjection.ts` and `apps/web/tests/gameProjection.test.ts`; touch `apps/web/src/store/useGameStore.ts` only at event-apply path, projection field, persistence lifecycle, selectors, reset wiring, and real camp-link hook if caller supplies `campId`, `heroId`, and prompt; add local persistence for derived aggregate only; add replay/reconnect/mock guards; define full v1 shape even when later-owned fields start empty, zeroed, or `null`. |
| Out   | New battlefield rendering; camp buttons; Quest Board filtering; effects layer; SFX; barks; animation; XP/level UI; Battle Report reskin; War Council picker UI; Dawn Report composer; hosted/cross-device persistence; new protocol events; any state authority outside `gameProjection.ts`.                                                                                                                                                                                     |

Phase A roadmap handoff: `gameProjection.ts` must stand up first, thin at first, grown per phase, never bypassed. Phase A later reads populated `legion`, `pendingLinks`, `campLinks`, `alertFocus`, and `lastEventAt`; territory, XP, and essence need not be visible in Phase 22.

***

## Experience Loops Preserved

* **Seconds, Glance Loop:** `alertFocus` answers "does anything need me?" with one pulse. Later `warTide` answers "are we winning?" from real deltas only. Dead-air ambience is state-driven, not event-minted progress.
* **Minutes, Command Loop:** `awaiting_input`, plan approvals, permissions, and guarded actions are valuable interruptions. `flareCeremony` and `swiftCommand` let fast unblocks become rewards without fake urgency.
* **Session, Campaign Loop:** a cockpit session is a war day. Later Battle Report work reads banners, objectives, War Tide, MVP, and efficiency from projection aggregates plus existing rollup helpers.
* **Hours/Overnight, Return Loop:** Dawn Report is synthesized by diffing persisted projection against fresh hydration, then sequenced through `dispatchReplayEntries`; the raw replay buffer is capped and only captures while the cockpit is open.
* **First Ten Minutes:** faction oath, first hero spawn, first mission, first completion, and at most one first Blight camp reveal should work without special state outside projection.
* **Week/Season, War Loop:** `season` exists in v1 so later prestige, saga chronicle, Hall of Banners, and re-fog can be added without changing the persistence root.

***

## Code-Verified Facts

* Protocol events used by the design exist in `packages/protocol/src/events.ts`: `test_failure`, `build_error`, `build_fix`, `ts_errors`, `lint_error`, `loop_update`, `task_stalled`, `task_near_stall`, `task_verification`, `plan_verification_complete`, `suggestion_update`, `hero_evidence_update`, `file_intent_declare`, `warroom_pending_catchup`, `scroll_collected`, `achievement_unlocked`, `awaiting_input`, `plan_budget_warning`, `plan_budget_exhausted`, and `subagent_lineage_update`.
* `useGameStore.ts` is about 3,640 lines. Projection logic belongs in a pure module under `apps/web/src/lib/`, folded once from the store event-apply path and read through selectors.
* `SuggestionAcceptRestResponse` is imported and type-guarded in `apps/web/src/lib/questBoardActions.ts`, so future camp-to-mission verbatim prompt linkage needs no protocol change.
* `replayingSinceMs` exists in `useGameStore.ts`; `dispatchReplayEntries` exists in `apps/web/src/lib/replayLink.ts`.
* The battlefield side-module pattern exists in `apps/web/src/components/battlefield/achievementCelebration.ts` with `CELEBRATION_MAX_CONCURRENT`; future effects layers should mirror it, but Phase 22 renders nothing.
* Mock substrate: `apps/server/src/managers/mockEventGenerator.ts` plus the `mockEnabled` development knob in `useSettingsStore.ts`. Mock events are wire-indistinguishable, so guards key off `mockEnabled`, not event shape.
* `apps/server/src/lib/names.ts` already generates faction-themed names.
* Heroes record working directory in `packages/protocol/src/heroes.ts`.
* Scanner stack: `codebaseScanOrchestrator.ts`, `codebaseIssueScanners.ts`, `projectScanEngine.ts`, and `onDemandAnalysisEngine.ts` feed `suggestionManager.ts`.
* `channelCommandManager.ts` supports later Emissary framing.
* `sfxAssets.ts` keys `SFX_FILES` by `FactionId x BarkKind` with synth fallback through `voiceSynth.ts`; later world cues extend that manifest.
* Complexity tiers `quick`, `routine`, `heavy`, `epic` and specialization confidence `low`, `medium`, `high`, `generalist` are verified in `missionComplexity.ts` and `heroSpecialization.ts`.
* `apps/web/tests/` has broad pure-lib and component-test conventions; Phase 22 primarily needs pure reducer tests plus narrow store/persistence tests.

***

## System Wiring

Every game mechanic derives from modules that already ship. Flow: `packages/protocol/src/events.ts` -> single WS stream / `wsEventBatcher` -> existing derivations, existing state/UI, and new `gameProjection.ts` -> game presentation reads projection only. `overlayLayers.ts` is only the cockpit-shell z-contract for surfaces above the board; battlefield game elements use board-internal stacking inside `Battlefield.tsx`.

Existing module consumers:

| Existing module                                                                 | Feeds game system                                              |
| ------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `events.ts` tool/mission/plan events                                            | Essence income, combat playback, XP triggers                   |
| `events.ts` dev-health events                                                   | Live Legion skirmishers, rams, gremlins                        |
| `scanCodebase.ts` + `questBoard*.ts`                                            | Blight camps, camp HP, dawn patrol, Blight Titan               |
| `missionTags.ts` -> `heroSpecialization.ts`                                     | Hero class identity, barks, icon, class-advantage muster bonus |
| `missionComplexity.ts` x `heroEfficiency.ts`                                    | XP formula and efficiency multiplier                           |
| `plans.ts` events                                                               | Battle-plan ritual, verification bonus, supply lines           |
| `lineage.ts` / `subagent_lineage_update`                                        | Peon mini-units tethered to parent hero                        |
| `missionHeatmap.ts`                                                             | Sector heat and fog pushback rates                             |
| `battlefield2d.ts` + board-internal stacking                                    | Sector grid placement and battlefield layers                   |
| `battlefieldHeroes.ts`                                                          | 12-hero cap and later garrison overflow                        |
| `achievements.ts` + attribution                                                 | Titles, relic triggers, Hall of Banners stats                  |
| `scrolls.ts` + `collect_scroll`                                                 | Loot layer, tome expansion, pure-game click                    |
| `factionStandings.ts` + `heroLeaderboard.ts`                                    | Seasonal league and Battle Report MVP                          |
| `warroom*.ts`                                                                   | Allied standees, shared front, Dawn Report catch-up            |
| `orchestrationCommandCenter.ts` + `taskQueue.ts`                                | World Boss HP, siege states, rally orders                      |
| `noticeBoard*.ts`                                                               | Tavern contracts and courier-style surfacing                   |
| `SessionRollup` + `replayLink.ts`                                               | Battle Report, battle replay, Dawn Report sequencer            |
| `costProjection.ts` / `factionCostProjection.ts`                                | War chest and efficiency grading input                         |
| `backgroundMusic.ts`, `sfxAssets.ts`, `factionVoiceLines.ts`, `audioRuntime.ts` | Dynamic layering, barks, verb ceremonies                       |
| `mediaCatalog.ts` + provenance gates                                            | Boss standee and relic/sigil art generation                    |
| `hostedPersistence.ts`                                                          | Later hosted/cross-device projection persistence               |
| ToastTray + `notifications.ts`                                                  | Town Crier toasts under work toasts                            |
| Public demo parity pattern                                                      | Synthetic front for pre-install visibility                     |

***

## Input Substrates To Fold

| Substrate                           | Code references                                                                                                                                                                                                     | Projection-relevant facts                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Missions                            | `packages/protocol/src/missions.ts`, `events.ts`, `missionManager.ts`, `eventIngest.ts`, `useGameStore.ts`, `MissionLog.tsx`, `MissionDetailDrawer.tsx`, `MissionTimeline.tsx`                                      | Missions are created from prompts and owned by heroes. States cover pending, active, awaiting approval, completed, failed, cancelled. Records include prompt, assistant text, tools, plan id, parent/child ids, lineage, token totals, summary. Start/update/completion/hydration events keep live and recent mission indexes current. Streaming text, tool starts, and tool results become linked combat playback.                                                                                                                                                                                   |
| Tools and activity                  | `events.ts`, `eventIngest.ts`, `useGameStore.ts`, `toolUsage.ts`, `ToolUsageOverlay.tsx`, `HeroDetailDrawer.tsx`                                                                                                    | Tool-use records include tool name, summary, path, input preview, start/end, result status. Taxonomy covers filesystem reads/writes, shell, search, web, planning, MCP, other tools. Success-rate/duration tracking excludes pending calls and aggregates by tool, mission, hero, faction. File access/read/conflict/intent and Bash events already stream. Summaries are sanitized; projection must not broaden prompt, secret, or path exposure.                                                                                                                                                    |
| Plans, permissions, guarded actions | `plans.ts`, `guardedActions.ts`, `permission.ts`, `guardedActions.ts`, `PlanWorkpad.tsx`, `PermissionModal.tsx`, `useGameStore.ts`                                                                                  | Generated plans, permission requests, and guarded-action proposals already surface as pending decisions. Client actions track approval/rejection/denial and duplicate in-flight guards. Plan states cover draft, awaiting approval, approved, in progress, completed, paused, failed, cancelled, with tasks, budget, remaining tokens, assigned hero, prompt preview, and summary. Permission modal exposes requester, action, boundary, rationale, tool detail, expiry, and expired disabled state. Projection presents battle-plan seals and gates while workpad/modal remain source of truth.      |
| Heroes                              | `heroes.ts`, `heroRoster.ts`, `useGameStore.ts`, `HeroRoster.tsx`, `HeroCard.tsx`, `HeroDetailDrawer.tsx`, `battlefieldHeroes.ts`, `heroCli.ts`                                                                     | Spawn records include name, archetype, faction, CLI, session id, model, cwd, TTY flag, tint, metrics, and battlefield position. Faction names/archetypes, model-tier detection, and session-id aliases make stable heroes. States cover spawning, idle, active, awaiting input, verifying, error, dismissed. Metrics cover missions, tools, tokens, cost, last activity, active mission, and free position. Roster excludes dismissed heroes, caps visible heroes at 12, sorts by state/activity, and forces selected hero into the visible window.                                                   |
| Quest Board and scanner             | `suggestions.ts`, `suggestionManager.ts`, `suggestions.ts` route, `idleSuggestionLifecycle.ts`, scanner engines, `questBoard.ts`, `questBoardModel.ts`, `questBoardActions.ts`, `QuestBoard.tsx`, `useGameStore.ts` | Canonical suggestion updates normalize into Quest Board cards with legacy idle compatibility. Sources include idle hero work, codebase issues, session follow-ups, analysis items, project scan items, and legacy idle suggestions. Severity, source ordering/counts, dismissed state, and totals are computed. Cards cap at 24 canonical and 12 legacy. Idle quests support accept, selected-idle-hero assignment, dismissal, optimistic updates, rollback, in-flight keys, and bounded bulk dismissal. Scans expose status, root selection, ignore patterns, and safe labels.                       |
| Derived metrics                     | `heroLeaderboard.ts`, `factionStandings.ts`, `factionRadar.ts`, `heroEfficiency.ts`, `factionEfficiency.ts`, `costProjection.ts`, `factionCostProjection.ts`, `SessionRollup.tsx`, `useGameStore.ts`                | Leaderboard ranks non-dismissed heroes by missions, tools, tokens, cost, achievements, last activity, or name. Faction standings cover missions, tools, tokens, cost, achievements, hero counts, active count, top performer, selected-player highlight, and tie-breaks. Efficiency profiles include closed/in-flight missions, tokens, cost, per-mission averages, duration, success rate, throughput, null no-closed score, S-A-B-C-D tiers, and sorting. Cost projection uses a 15-minute trailing window, token rates, hourly/daily formatting, faction buckets, unassigned totals, and rankings. |

***

## `GameProjection` Shape

One versioned, serializable aggregate object. It is never an event log, so persisted size remains bounded.

```ts
interface GameProjection {
  version: 1;
  sectors: Record<SectorId, { dirKey: string; cell: number; fogPct: number; lastTouchedAt: string }>;
  legion: {
    camps: Record<CampId, { hp: number; maxHp: number; issueIds: string[]; tier: "camp" | "fort" | "citadel"; entrenchedScans: number; scarredPct: number }>;
    skirmishers: Record<string, { path: string; sinceAt: string }>;
    ram: { detail: string; sinceAt: string } | null;
    gremlins: { tsErrors: number; tsFiles: number; lint: number };
    wraiths: Record<HeroId, { stack: number; sinceAt: string }>;
    siege: { level: number; stalledTaskIds: string[] } | null;
  };
  pendingLinks: Array<{ campId: CampId; heroId: string; prompt: string; expiresAt: string }>;
  campLinks: Record<MissionId, CampId>;
  heroes: Record<HeroId, { xp: number; level: number; titles: string[]; quirkSeed: string; swiftCommand: { count: number; meanMs: number }; banners: number; wraithKills: number }>;
  essence: {
    fire: number;
    lore: number;
    forge: number;
    treasury: number;
    burst: Record<"fire" | "lore" | "forge", { windowStartAt: string; count: number }>;
  };
  buildings: Record<BuildingLine, { level: number; xp: number }>;
  warTide: { windowStartAt: string; gains: number; losses: number };
  council: {
    warDay: string;
    rerolled: boolean;
    objectives: Array<{ id: string; kind: "raze" | "relight" | "swift" | "nemesis" | "siege"; refId: string; done: boolean }>;
  } | null;
  nemeses: Record<string, { name: string; source: "camp" | "test" | "wraith"; refId: string; escalations: number; slainAt: string | null }>;
  alertFocus: { kind: "flare" | "gate" | "siege" | "ram" | "skirmisher" | "newCamp"; refId: string } | null;
  season: { id: string; startedAt: string };
  flareCeremony: { recentFlares: number; windowStartAt: string };
  lastEventAt: string;
}
```

Initial state requirements: empty records for `sectors`, `legion.camps`, `legion.wraiths`, `heroes`, `campLinks`, `buildings`, and `nemeses`; empty `pendingLinks` and future `council.objectives`; `null` for `legion.ram`, `legion.siege`, `alertFocus`, and `council`; zeroes for essence, War Tide, flare counts, banners, XP, wraith kills, and swift-command counters; deterministic `season.id` from injected `now` such as `season:${YYYY-MM-DD}`; injected `now` for `lastEventAt`, `season.startedAt`, `warTide.windowStartAt`, and `flareCeremony.windowStartAt`; no copies of selected faction, scroll inventory, achievement inventory, War Room membership, settings, Notice Board messages, visible hero slots, active bottom-rail surface, replay entries, toasts, animation state, audio state, or raw prompts.

***

## Public API

Recommended exports from `apps/web/src/lib/gameProjection.ts`:

```ts
export const GAME_PROJECTION_VERSION = 1;
export const GAME_PROJECTION_STORAGE_KEY = "factionos-game-v1";
export const GAME_PROJECTION_PERSIST_DEBOUNCE_MS = 2_000;
export const GAME_PROJECTION_PERSIST_MAX_LATENCY_MS = 30_000;
export type SectorId = string;
export type CampId = string;
export type HeroId = string;
export type MissionId = string;
export type BuildingLine = "keep" | "forge" | "library" | "barracks" | "watchtower";
export interface GameProjectionReduceContext { now: string; isReplaying: boolean; mockEnabled: boolean; }
export interface GameProjectionPersistContext { isReplaying: boolean; mockEnabled: boolean; }
export function createInitialGameProjection(now?: string): GameProjection;
export function reduceGameProjection(state: GameProjection, event: ServerEvent, ctx: GameProjectionReduceContext): GameProjection;
export function recomputeAlertFocus(state: GameProjection): GameProjection["alertFocus"];
export function parsePersistedGameProjection(input: unknown, now?: string): GameProjection;
export function loadPersistedGameProjection(storage?: Storage | null, now?: string): GameProjection;
export function persistGameProjection(state: GameProjection, storage?: Storage | null): void;
export function shouldPersistGameProjection(ctx: GameProjectionPersistContext): boolean;
```

Internal helper exposure, if needed, should use one `__testing` object instead of many production-looking exports.

***

## Reducer Contract

`reduce(state, event, ctx) -> state`

`ctx` carries at least `now`, `isReplaying` from `replayingSinceMs`, and `mockEnabled` or equivalent guard. Use `ctx.now` as an ISO string in store calls; tests pass fixed ISO values.

Reducer purity requirements: no `localStorage`, timers, audio, input mutation, or persisted strike/juice/SFX/animation/toast queues; unknown events return the exact same state reference; events outside the mutation map are ignored.

Snapshot-style events reconcile current truth but never mint progress: `mission_state`, `roster_update`, and `suggestion_update`. Accumulation cases include XP, essence, kills, banners, War Tide gains, title progress, and swift-command counters. When `ctx.isReplaying` is true, accumulation is no-op; snapshot reconciliation may still run so UI state can recover.

Target event map:

| Event(s)                                                                                   | Projection effect                                                                                                                                                                                                                                                                                                   |
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `suggestion_update` snapshot                                                               | Reconcile `legion.camps`; coalesce issues per sector; recompute hp/tier; increment `entrenchedScans` on untouched camps. Camp removal on scan-clear is a kill only if a linked mission verified; otherwise silent reconcile. If Phase 22 cannot fold this safely, treat as explicit snapshot no-op and document it. |
| `tool_use`                                                                                 | Essence burst/income after decay; combat strike transient if `missionId` is in `campLinks`; `lastEventAt`.                                                                                                                                                                                                          |
| `mission_start`                                                                            | Resolve `pendingLinks` to `campLinks` by `heroId` plus verbatim prompt; fallback to first mission on that hero inside expiry window.                                                                                                                                                                                |
| `mission_complete` / failed `mission_update`                                               | Banner/XP accrual using complexity x verification x swift x class-advantage; camp `scarredPct`; nemesis escalation check on failure; War Tide gains on verified success; council objective checks; clear camp link. In Phase 22, do not mint XP/banners unless verification ledger is also added.                   |
| `task_verification`, `plan_verification_complete`                                          | XP verification multiplier; camp kill authorization.                                                                                                                                                                                                                                                                |
| `test_failure`, `build_error`, `build_fix`, `ts_errors`, `lint_error`                      | Maintain skirmishers, singleton ram, and gremlin counts. Skirmisher dies when later scan/pass clears its path; ram dies on `build_fix`.                                                                                                                                                                             |
| `tool_result{ok:false}`, hero error state                                                  | Stack `wraiths[heroId]`, capped by wraith stacking rule; skip stack increments while replaying.                                                                                                                                                                                                                     |
| `awaiting_input` / `input_received`                                                        | Recompute `alertFocus`; track `flareCeremony`; record swift-command timing only from a prior flare and not while replaying.                                                                                                                                                                                         |
| `permission_request/response`, `guarded_action_update`, `command_center_permission_update` | Gate handling in one alert tier. Legacy permissions and Command Center permissions must fold into one authority or be explicitly deferred in tests/comments.                                                                                                                                                        |
| `task_stalled`, `task_near_stall`, `task_escalation`                                       | Reconcile `legion.siege` level and stalled task ids.                                                                                                                                                                                                                                                                |
| `internal_hero_spawn`, `roster_update`, `hero_dismissed`                                   | Ensure hero entries exist, seed `quirkSeed` from `hero.sessionId ?? hero.id`, keep dismissed hero aggregates instead of deleting them.                                                                                                                                                                              |
| `scroll_collected`, `achievement_unlocked`                                                 | Later title/relic triggers; do not duplicate scroll or achievement inventory.                                                                                                                                                                                                                                       |
| First event of a new war day                                                               | Deterministic council objective draw; War Tide window rollover.                                                                                                                                                                                                                                                     |
| Skirmisher respawn / wraith cap hit                                                        | Nemesis promotion check by renaming/dressing existing state, not adding enemy logic.                                                                                                                                                                                                                                |

Phase 22 reducer slice to implement now: `connected` no-op; hero spawn/roster/dismissal creates or preserves neutral hero entries without growth changes; attention folds `awaiting_input`, `input_received`, permissions, guarded actions, and Command Center gates; Live Legion folds siege events, `build_error`/`build_fix`, `test_failure`, `ts_errors`, `lint_error`, and `tool_result` failures; camp links fold `mission_start`, `mission_complete`, failed `mission_update`, and dormant pending-link recording only from real `campId` data; `suggestion_update` is safe camp reconciliation or explicit snapshot no-op.

Deferred beyond Phase 22: XP thresholds, class advantage, titles, relics, loot, essence income, building XP, War Tide gains/losses, council draws, nemesis promotion, fog, territory, Dawn Report diffs, random rewards, event logs, strike queues, SFX cues, animation pulses, and toast effects.

***

## Alert Focus

Failure mode: flare + gate + siege + skirmishers at once makes the board illegible. Fix: `gameProjection.ts` computes exactly one `alertFocus`.

Priority ladder: flare > gate > siege > ram > skirmisher > new camp > `null`.

Only one thing may pulse at any moment; everything else holds static badge state. Clearing the focus promotes the next focus. Components must not compute their own pulse authority.

Pending refs can be represented by serializable projection fields or derived from existing persisted fields. A simple approach: flares keyed by hero id, gates keyed by permission/guarded-action/Command Center id, and siege/ram/skirmisher/camps under `legion`. Every reducer case that changes an attention source should return state with `alertFocus: recomputeAlertFocus(nextState)`.

***

## Persistence And Honesty

Known exploit: shared replay links and reconnect hydration re-dispatch events through `applyEvent`. A naive projection would re-mint essence, XP, kills, and other progress, violating Fun Law 1.

Required fix: honor `replayingSinceMs` through `ctx.isReplaying`; treat snapshots as reconciliation only; test shared replay and reconnect so persisted progress numbers do not change; guard mock/drill persistence by `mockEnabled`, because mock events cannot be inferred from wire shape.

Replay substrate facts:

* Event log captures server events in an in-memory buffer capped at 500 and a persisted local buffer capped at 100.
* Replay persistence is debounced, excludes replay-generated events while replay runs, and restores by reapplying captured events.
* Replay windows cover last 30 seconds, 2 minutes, and 5 minutes at 1x, 2x, and 4x with gap capping.
* Visible replay list caps at 50 rows and preserves priority mission, tool, permission, guarded-action, and hero state events when noisy.
* Share links encode up to 200 redacted events in local URL fragments, with clipboard fallback and disabled replay/share/clear controls while replay is in flight.
* Replay is a battle recorder and Dawn Report sequencer input, but projection honesty guard still applies.

Persistence requirements:

* Key: `localStorage["factionos-game-v1"]`.
* Pattern: mirror replay-buffer `schedulePersist` debounce and versioned key posture.
* Cadence: about `2_000ms` debounce with `30_000ms` max-latency flush.
* Flush on `visibilitychange` when hidden, `pagehide`, and `beforeunload`.
* Persist only aggregates, never an event log.
* Swallow unavailable storage, quota, and JSON errors.
* Parse malformed or wrong-version snapshots defensively into a fresh initial projection.
* Boot order: hydrate persisted projection, apply server hydration snapshots as reconciliation, then resume live folding.
* Cross-device/hosted persistence waits for later hostedPersistence posture.
* `localStorage` is acceptable because projection is derived; worst-case loss re-fogs cosmetic progress, never work state.

Mock / War Games guard:

* Synthetic demo front is later framed as War Games, clearly bannered drill mode using the mock generator and seed-data toggles.
* Drill events must never accumulate into persisted projection.
* Folding for transient spectacle may be allowed later.
* Persisting mock/drill progress is forbidden.
* Because `useSettingsStore.DEFAULT_SETTINGS.mockEnabled` is currently `true`, persistence tests must explicitly set `mockEnabled: false`; drill/no-persist tests should leave or set it true.

***

## Store Integration

Add one store field:

```ts
gameProjection: GameProjection;
```

Initialize beside the replay ring:

```ts
gameProjection: loadPersistedGameProjection(undefined, new Date().toISOString())
```

Fold once from `applyEvent(e)`, after replay-ring capture and before the existing switch, so the projection sees the original event stream and does not depend on later UI store mutations. The helper should:

* derive `{ now, isReplaying: Boolean(get().replayingSinceMs), mockEnabled: useSettingsStore.getState().mockEnabled }`;
* call `reduceGameProjection(state.gameProjection, event, ctx)`;
* preserve state reference when reducer returns the same projection;
* schedule persistence only when changed and `shouldPersistGameProjection(ctx)` is true.

Expose selectors instead of ad hoc reads:

```ts
export const selectGameProjection = (state: Pick<GameStore, "gameProjection">) => state.gameProjection;
export const selectGameAlertFocus = (state: Pick<GameStore, "gameProjection">) => state.gameProjection.alertFocus;
export const selectGameLegion = (state: Pick<GameStore, "gameProjection">) => state.gameProjection.legion;
```

Prefer `Pick`-based selector input types so the full `GameStore` interface does not become a public dependency.

`resetToSeed()` must reset projection to a fresh initial state and clear or schedule removal of `factionos-game-v1`. Browser local erasure must clear the same key. Since erasure boundary ids are protocol-owned, include the key in the existing browser-storage inventory and tests; do not invent a web-only erasure boundary id.

***

## Camp-Link Boundaries

There is no simulated combat. The live tool-event stream of a linked mission is the battle.

The earlier idea of storing a camp id via `missionTags` is wrong: `missionTags.ts` is client-side regex inference and `Mission` has no tags field. The correct zero-protocol-change mechanism is:

1. Future camp accept returns exact prompt dispatched to a chosen `heroId`.
2. `Mission.prompt` is the verbatim prompt that starts the mission.
3. `gameProjection.ts` records `{ campId, heroId, prompt, expiresAt }` on accept success.
4. On `mission_start`, reducer binds `missionId -> campId` by `heroId` plus verbatim `prompt`; fallback is first mission on that hero inside the window.

Current limitation: `codebaseIssueToCard()` exposes `dismissIssue`, not accept/assign; `/suggestions/:suggestionId/accept` accepts only idle suggestions through `SuggestionManager.acceptSuggestion()`. Therefore Phase 22 must not pretend codebase issue cards can already start camp missions.

Rules:

* Add `recordProjectionPendingLink(input)` only if the caller supplies real `campId`, `heroId`, and verbatim `prompt`.
* Do not record ordinary idle-suggestion accepts as camp links unless future Phase 2 UI associates them with a real camp.
* While linked, future rendering may show edit/write as hammer blow, Bash as fireball, read/grep as scout ring, tool error as counterattack, and Task subagent as flank.
* Camp HP only falls on plausibly-fixing events.
* Camp dies only when quest completes and verifies via `task_verification`; burn-down is the kill shot.
* Failed mission leaves camp alive with `scarredPct`.
* Concurrent missions on the same camp can later render as joint assault and credit every linked hero on verified kill.

***

## Future Field Meaning

These details explain why the v1 shape includes fields Phase 22 may leave neutral.

| Field area                      | Design meaning and constraints                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Territory and living map        | The 16:9 board becomes a territory map of actual repos. Sectors are top-level dirs/workspaces from paths carried by `file_access`/`file_read`; `MissionHeatmap` already aggregates this. Sectors clear as missions touch them, re-fog after weeks, and corrupt when scanners find new issues. Buildings anchor home sector and level from real output: edit/write volume, plans completed, subagent usage, scroll collection. Multi-project means multi-front tabs by cwd/project. Implementation stays 2D DOM/CSS with sectors over board art.                                     |
| Map legibility and camps        | Legibility budget handles tiny repos and monorepos. Sector count caps around 24. Small repos split one level deeper; huge repos group by workspace globs and aggregate long tail into "Borderlands." Camps coalesce to one marker per sector. HP weights are error 3, warning 2, info 1. Tiers are camp -> fort -> citadel. Progress is frontline delta, not remaining total.                                                                                                                                                                                                       |
| Live Legion enemies             | `test_failure` spawns skirmishers keyed by path. `build_error` spawns ram; `build_fix` kills it. `ts_errors` and `lint_error` update gremlins. `task_stalled` / escalation advance siege. Tool-result errors and hero error stack wraiths per hero. Wraiths stack, never multiply: one per hero, stack-count badge grows, board-wide cap is 3, stack resets when mission verifies.                                                                                                                                                                                                  |
| Dead air and ambience           | Board motion is state-driven, not event-minted. Ambience keys off projection state: camp fires, banners, keep smoke. Later scrying marks active missions with no tool event for more than 20 seconds. Empty roster shows keep, last war state, and one summon call-to-action, never a blank board.                                                                                                                                                                                                                                                                                  |
| Flare ceremony                  | Chatty `awaiting_input` events keep full priority but reduce ceremony. First flare in a while gets full fanfare. Swift Command bonus scales down with flare frequency. Work signal is never suppressed.                                                                                                                                                                                                                                                                                                                                                                             |
| War Tide                        | Later one topline meter answers "are we winning?" over a trailing war-day window from real deltas only. Gains: verified banners, camp HP removed, skirmisher/ram kills. Losses: new camp mass, re-fog ticks, siege advance. States: advancing, holding, losing ground. It is self-relative, not cross-player. Reduced motion renders as signed delta chip.                                                                                                                                                                                                                          |
| War Council                     | Later session stakes are selected, not generated, from the real needed-work census in projection and Quest Board. Objective kinds: raze high-severity camp, relight oldest sector, keep mean response under trailing `swiftCommand.meanMs`, slay nemesis, break siege / kill ram. Objectives are buffs, never gates. Selection is deterministic from projection plus scan state, with no RNG or server work. One reroll. Battle Report opens with declared-vs-achieved ledger.                                                                                                      |
| Hero growth and class advantage | XP weights mission completions by complexity x verification bonus x Swift Command bonus x class-advantage bonus. Levels are cosmetic/title-granting only. Specialization roles become Feature Architect, Bug Hunter, Refactor Specialist, Test Engineer, Scribe, Operator, Quartermaster, Wandering Generalist. Class advantage is pure accept-time comparison between hero class and quest nature; it never gates or penalizes, only adds capped flourish and XP. Dismissed heroes with enough banners later enter Hall of Banners. Hero quirks are deterministic from session id. |
| Essence and buildings           | Fire, Lore, and Forge mint only from real tool/work events. Primary sink is automatic tithe into matching building XP. Secondary capped treasury is ceremony only: war paint, banner styling, building dressings, naming reroll, saga pages, rally horn. Burst-window diminishing returns discourage tool spam. Sinks are cosmetic, so gaming essence buys no power.                                                                                                                                                                                                                |
| Nemeses                         | Nemeses rename and dress existing enemy state; they add no HP, spawn, or kill logic. Promotion sources: camp survives at least two failed linked missions; test skirmisher respawns at least three times in a week; wraith stack hits cap. Names are deterministic from stable key and table. Verified kill drops guaranteed relic/title without changing verification requirements.                                                                                                                                                                                                |

***

## Board And Presentation Boundaries

* Phase 22 renders nothing, but leaves projection readable by Phase A without returning to design docs.
* Shipped battlefield remains 2D DOM/CSS; no engine change for sectors, camps, strikes, bosses, or fog.
* Board-internal stacking owns game elements: board art, camp layer, shipped hero token layer, pointer-events-none effects layer, single `alertFocus` ring.
* Camp markers later become native buttons with accessible names and open Quest Board filtered to `issueIds`.
* `BATTLEFIELD_HERO_LIMIT` is 12. Overflow later garrisons the keep; urgent flare/gate heroes must be able to auto-swap into visible set. Phase 22 must not encode visible hero as game truth.
* Champion Mode is the floor for one- and two-hero rosters; future rendering scales visible camp count down and hero detail up while full census remains in projection and Quest Board.
* Clean repos get Golden Age treatment and neutral ambience, not fabricated issues. Empty Legion is a bragging state.
* Banish/dismiss issue handling is dry and loot-less; it is not kill, XP, loot, territory, urgency, or score.
* Long missions need later combo-strike and SFX-rate-limit posture. Phase 22 persists aggregates only; strikes, sounds, and animations are transients.
* Every motion effect later needs a reduced-motion static twin: chips, badges, dotted routes, labels, and signed deltas are first-class.

***

## Existing Ownership Boundaries

| System       | Boundary                                                                                                                                                                                            |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Scrolls      | Scroll system remains source of truth. Five fundamental tomes unlock Lobsters. Later tome expansion/relic display can react to `scroll_collected`, but Phase 22 does not duplicate inventory.       |
| Achievements | Achievements engine remains source of truth. Later titles, relics, Hall of Banners stats, and saga entries derive from `achievement_unlocked` and attribution snapshots.                            |
| Loot         | Loot rolls are celebration choreography around real rarity. Phase 22 adds no random reward state or click-farmable resource.                                                                        |
| War Room     | Later Alliance layer can add allied standees, shared bosses, faction leagues, and tavern contracts. Phase 22 does not share projection state or answer redaction.                                   |
| Notice Board | Messages later become tavern contracts, but create/hydrate/update/resolve/dedupe/targeting/visibility remain owned by Notice Board model.                                                           |
| Audio        | Audio stays opt-in and rate-limited. `gameProjection.ts` never emits or schedules audio. Barks, faction lines, world SFX, synth fallback, muted/blocked/unavailable states remain runtime concerns. |
| Toasts       | Town Crier framing is presentation only; work-critical toasts stay visually senior to game toasts.                                                                                                  |

***

## Anti-Goodhart Rules

1. Banner does not equal mission count; score is weighted by complexity and verification.
2. Efficiency is a multiplier on session score, graded against the player's own trailing baseline.
3. Error-spawned enemies drop no loot when re-killed by retry spam; wraith kills count only when the underlying mission eventually verifies.
4. Essence diminishes per tool call within a burst window.
5. Cost framing stays quiet but present; spending is never celebrated per se.

***

## Utility And Admin Boundaries

These cockpit-accessible systems are not game progression and must not create score, XP, loot, urgency, territory, enemy spawns, or rewards merely by running:

* Shell availability: loading, empty, offline, error, deferred, unavailable.
* Local server connection diagnostics.
* Command palette fuzzy ranking, result caps, keyboard navigation, roster/theme/severity/filter commands, shortcuts, theme controls, focus containment, drawer behavior.
* Session export, browser download, local erasure, exact confirmation phrases, server availability badges, error redaction.
* Browser-notification permission states, support gates, test actions, readiness states, Web Push subscription details.
* Settings reset, audio-runtime reset, notification-permission reset, scan-state reset.
* Codebase scan error state, disabled scan gating, scan error summaries, Quest Board ignore-pattern caps, scan-root safety labels.
* Mock generator and seed-data toggles, except as later progress-inert War Games synthetic front.
* Manual scan triggering, which may later be framed as Dispatch Scouts but is not itself score, XP, loot, territory, urgency, or progression.
* Command Center validation limits, file/git controls, terminal controls, isolation diagnostics, provider diagnostics, raw payload blocking, and redaction rules. The game may summarize high-level outcomes only.

***

## Planning Assumptions And Open Questions

Resolved assumptions:

* Source "Phase 1" means first game-projection phase; `.spec_system` phase is 22 because Phase 21 is complete.
* Master PRD/state lacked Phase 22, so the projection source material is the newly approved next phase and this PRD reconciles tracking.
* Work is primarily `apps/web`; no protocol events or server routes unless a session proves necessity.
* Foundation-only scope excludes rendering, camp buttons, Quest Board filtering, XP UI, reports, council UI, Dawn Report, hosted persistence, audio/SFX, and extra state authorities.
* `mockEnabled` is the authoritative drill persistence guard.
* Ordinary idle-suggestion accepts are not camp links without real camp id, hero id, and verbatim prompt.
* `localStorage["factionos-game-v1"]` is acceptable because projection is derived and non-authoritative.

Implementation choices still open:

* Whether Phase 22 implements lightweight `suggestion_update` camp reconciliation or treats it as explicit snapshot no-op until Phase 2 camp coalescing.
* Whether the current shape needs additional persisted pending attention refs to promote alerts after clearing flares and gates.

Deferred product/design tuning:

* Multi-repo identity and how fronts federate when heroes roam cwd/project.
* Guardrail-approved headless messaging verbs beyond Rally Order and Emissary.
* What score/projection state can be shared in War Room under redaction.
* Lobster faction content debt, especially premium-feeling click lines.
* Essence rates, burst decay curve, treasury cap, and tithe cadence.
* Combat focus affordance when many linked strikes are active.
* Minimal Dawn Report recap-event vocabulary.
* Class Advantage affinity pairings, bonus size, and pre-muster surfacing.
* War Council objective count, reroll ceremony, and picker weighting.
* War Tide window length, gain/loss weights, and reduced-motion chip format.
* Nemesis name table size, styling, dedupe guarantees, and saga policy.

Relevant carry-forward considerations:

* \[P03] Stable docs are the current contract.
* \[P18-apps/server+apps/web] `suggestion_update` is canonical; preserve hydration/store reconciliation semantics and avoid parallel event authority.
* \[P18-apps/server] Quest Board state is durable local evidence; camp-link readiness respects suggestion store and local cleanup boundary.
* \[P20] Privacy gates are release-critical: projection snapshots, events, tests, and docs avoid raw prompts, broad paths, terminal output, file contents, provider payloads, tokens, and secrets.
* \[P08] Full trusted erasure remains no-claim; browser-local projection cleanup does not imply erasure across archives, logs, backups, hosted surfaces, or workspace files.
* \[P21] Archived phase snapshots can lag live state; live specs/state tracking are authoritative for completed Phase 21 status.

***

## Implementation Surface

New files:

* `apps/web/src/lib/gameProjection.ts`
* `apps/web/tests/gameProjection.test.ts`

Likely touched files:

* `apps/web/src/store/useGameStore.ts` for projection field, fold helper, `applyEvent` call, selectors, reset wiring, persistence hooks, and optional real pending camp-link hook.
* `apps/web/src/lib/localErasure.ts` and `apps/web/tests/localErasure.test.ts` for `factionos-game-v1` cleanup.
* `apps/web/tests/replayPersist.test.ts` or a small `apps/web/tests/gameProjectionStore.test.ts` for store/persistence coverage.

Do not touch rendering files in Phase 22: `Battlefield.tsx`, Quest Board UI, audio files, and ceremony components stay unchanged.

***

## Verification And Completion

Fixture style should match `apps/web/tests/replayPersist.test.ts`, `replayBuffer.test.ts`, `questBoardStore.test.ts`, `factionStandings.test.ts`, and `heroLeaderboard.test.ts`: fixed timestamps, small typed event builders, and no component render unless a store lifecycle hook requires React `act()`.

| Area                  | Required proof                                                                                                                                                                                                                                                                                                                                                                                            |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Reducer contract      | `createInitialGameProjection()` returns the full serializable v1 shape; unknown events return the same object reference; reducer is pure/deterministic; JSON round-trip keeps only serializable aggregate fields.                                                                                                                                                                                         |
| Snapshot/honesty      | Snapshot events reconcile or no-op without progress; accumulation no-ops while `ctx.isReplaying`; replaying the same `mission_complete` / `tool_result` sequence twice does not double counters.                                                                                                                                                                                                          |
| Attention             | `alertFocus` priority is exactly flare > gate > siege > ram > skirmisher > new camp > `null`, and clearing a higher-priority ref promotes the next.                                                                                                                                                                                                                                                       |
| Store/persistence     | `applyEvent` folds once; selectors expose projection, alert focus, and legion; `__flushGameProjectionPersist()` drains pending writes like `__flushReplayPersist()`; boot order is hydrate -> reconcile snapshots -> fold live events.                                                                                                                                                                    |
| Replay/reconnect/mock | `applyEvent` does not persist while `replayingSinceMs` is set; `dispatchReplayEntries()` with `setReplayingFlag` does not change persisted progress; reconnect snapshots (`roster_update`, `mission_state`, `suggestion_update`) do not change XP, essence, banners, kills, title progress, or War Tide; `mockEnabled: true` may fold in memory but leaves `localStorage["factionos-game-v1"]` unchanged. |
| Persistence lifecycle | Writes only through guarded projection path; debounce, max-latency, lifecycle flush, and synchronous test flush work; malformed/wrong-version storage parses to fresh initial projection.                                                                                                                                                                                                                 |
| Erasure/reset         | `resetToSeed()` and browser local erasure clear `factionos-game-v1`.                                                                                                                                                                                                                                                                                                                                      |
| Scope guard           | No new rendering surface, protocol event, hosted persistence path, audio path, transient presentation persistence, or parallel game state authority.                                                                                                                                                                                                                                                      |

Minimum validation:

```bash
npm test -- apps/web/tests/gameProjection.test.ts
npm test -- apps/web/tests/replayPersist.test.ts apps/web/tests/localErasure.test.ts
npm --workspace @factionos/web run typecheck
```

If shared event handling is touched broadly, also run:

```bash
npm test -- apps/web/tests/replayBuffer.test.ts apps/web/tests/questBoardStore.test.ts
npm test -- apps/web/tests/orchestrationStore.test.ts apps/web/tests/commandCenterStore.test.ts
```

Phase complete when all 8 sessions pass their validation, the matrix above is satisfied, and any validation failure is captured with concrete test names and blockers. Phase A is then unblocked for single-authority glance classification, replay/reconnect honesty, verified camp kills, and Fun Law 1 traceability from projection element to real backing source.

## Implementation Checklist

| Area              | Required work                                                                                                                                                                                                                                                                                                                                                                                               |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Projection module | Add `apps/web/src/lib/gameProjection.ts`; define `GameProjection`, alert focus, reducer/persistence contexts, serializable subtypes, storage/version/cadence constants, parse/load/persist helpers, complete v1 initial state, pure `reduceGameProjection`, alert-focus derivation, snapshot-vs-accumulation semantics, `ctx.isReplaying` no-accumulation, and settings-based mock/drill persistence guard. |
| Store lifecycle   | Add projection load/hydrate from `localStorage["factionos-game-v1"]`, replay-buffer-style debounce/max-latency schedule/flush, hidden/pagehide/beforeunload hooks, one `applyEvent` fold call, read selectors, `resetToSeed` cleanup, and dormant pending camp-link recording only when caller has real `campId`.                                                                                           |
| Erasure and tests | Add `factionos-game-v1` to browser local-erasure cleanup; add `apps/web/tests/gameProjection.test.ts`; add replay/reconnect farming, serialization-boundary, persistence cadence/hydration, and local-erasure coverage, extending existing persistence tests where local patterns fit.                                                                                                                      |
| Documentation     | Update owning design docs and change log in the same implementation window only if implementation decisions diverge from this PRD.                                                                                                                                                                                                                                                                          |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://faction-os.gitbook.io/faction-os-docs/.spec_system/archive/phases/phase_22/prd_phase_22.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
