> 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/sessions/phase22-session03-legion-enemy-reducer/spec.md).

# Session Specification

**Session ID**: `phase22-session03-legion-enemy-reducer` **Phase**: 22 - Projection Foundation **Status**: Not Started **Created**: 2026-07-05 **Base Commit**: 42d1d1230fe00c3e6ce66b3c631eebac53677768 **Package**: apps/web **Package Stack**: TypeScript

***

## 1. Session Overview

This session expands the `apps/web/src/lib/gameProjection.ts` pure reducer from hero attention into Phase 22 live enemy state. It folds build, test, type, lint, stalled-task, and failed-tool signals into the serializable `legion` aggregate while preserving the projection as bounded aggregate state rather than an event log, animation queue, reward ledger, or rendering source.

It is next because `phase22-session01-projection-contract` and `phase22-session02-hero-attention-reducer` are complete and validated, and the Phase 22 stub makes Legion enemy reduction the prerequisite for later camp-link, store-folding, persistence, honesty, and erasure sessions. The work remains in the existing `apps/web` pure module and Vitest file only; it does not touch store wiring, UI, audio, protocol contracts, persistence scheduling, hosted behavior, or camp mission links.

The output gives later game presentation phases a single projection-owned enemy and lower-priority attention source. Enemy state remains work-derived and privacy-conscious: it stores bounded refs and counts needed for focus and future presentation, not failure messages, tool previews, command output, raw prompts, terminal output, file contents, provider payloads, tokens, secrets, or broad diagnostic payloads.

***

## 2. Objectives

1. Implement immutable Legion reducer cases for dev-health, stalled-task, and failed-tool event families.
2. Keep enemy state bounded through caps, deterministic keys, stable ordering, and same-reference no-op behavior for duplicates or unchanged signals.
3. Preserve replay honesty by allowing reconciliation but preventing repeated replay traffic from farming wraith stacks or gremlin pressure.
4. Add focused reducer tests proving enemy upsert and clear behavior, wraith capping, replay no-accumulation, alert-focus promotion, and serialization boundaries.

***

## 3. Prerequisites

### Required Sessions

* [x] `phase22-session01-projection-contract` - Provides `gameProjection.ts`, the v1 aggregate shape, persistence guards, and initial tests.
* [x] `phase22-session02-hero-attention-reducer` - Provides the pure reducer seam, attention aggregate, alert-focus priority helper, hero helpers, and existing reducer test builders.

### Required Tools Or Knowledge

* Node 26.2.0+ and npm 11.16.0 workspace toolchain.
* TypeScript and Vitest patterns from `apps/web/tests/gameProjection.test.ts`.
* Protocol event shapes for `test_failure`, `build_error`, `build_fix`, `ts_errors`, `lint_error`, `task_stalled`, `task_near_stall`, `task_escalation`, and `tool_result` in `packages/protocol/src/events.ts`.
* Existing web event-folding behavior around mission and tool events in `apps/web/src/store/useGameStore.ts`.

### Environment Requirements

* Local workspace dependencies installed.
* No local server, browser runtime, hosted service, provider credential, Cloudflare credential, or database is required for this pure reducer session.

***

## 4. Scope

### In Scope (MVP)

* Web projection reducer needs live enemy state - fold `test_failure`, `build_error`, `build_fix`, `ts_errors`, and `lint_error` into `GameProjection["legion"]` with bounded refs/counts and explicit no-op handling for unchanged inputs.
* Task pressure needs one siege aggregate - fold `task_near_stall`, `task_stalled`, and `task_escalation` into `legion.siege` with stable stalled-task refs and severity levels.
* Failed tools need per-hero wraith pressure - fold failed `tool_result` events into capped per-hero `legion.wraiths` stacks, create neutral hero aggregates when needed, and skip stack accumulation while replaying.
* Lower-priority enemy attention needs deterministic promotion - recompute `alertFocus` after Legion changes so siege, ram, skirmisher, and new-camp priorities surface only when flare and gate attention are absent.
* Reducer tests need focused proof - extend `apps/web/tests/gameProjection.test.ts` for enemy upsert/clear behavior, same-reference duplicate behavior, wraith cap behavior, replay no-accumulation, focus promotion, and serialization exclusion of messages/previews.

### Out Of Scope (Deferred)

* Camp coalescing, pending camp links, mission-to-camp binding, camp kills, and verified camp progress - Reason: Session 04 owns camp-link boundaries and must not be skipped.
* Store folding, selectors, reset behavior, persistence scheduling, page lifecycle flushing, and localStorage erasure - Reason: Sessions 05, 06, and 08 own store lifecycle, persistence, and erasure.
* XP, levels, banners, title grants, relics, essence income, building XP, War Tide gains/losses, council objectives, nemesis promotion, fog, territory changes, Dawn Report diffs, combat strikes, loot, and SFX - Reason: these are later Phase 22 or future game-presentation mechanics, not live enemy foundation.
* Rendering, battlefield UI, Quest Board UI, audio, toasts, hosted persistence, protocol changes, server routes, and any second game state authority - Reason: Phase 22 foundation explicitly excludes presentation and parallel authority.

***

## 5. Technical Approach

### Architecture

Keep implementation inside `apps/web/src/lib/gameProjection.ts` as pure TypeScript. Add private reducer helpers for Legion updates, bounded string/ref normalization, task-severity mapping, wraith stack capping, and copy-on-write record changes. Continue to use `ctx.now` as the only clock input and `finalizeProjectionUpdate` as the single path that derives `alertFocus`.

Use `ServerEvent` discriminants from `@factionos/protocol` and treat missing or empty identifying fields as no-ops. Build/test/lint failure strings and tool-result previews are external inputs, so reducer state should store only bounded aggregate refs and counts. Preserve same-reference returns when the recognized event does not alter aggregate values so later store folding can avoid unnecessary persistence writes.

### Design Patterns

* Pure reducer module: Match the existing Session 02 pattern and avoid React, Zustand, timers, DOM, audio, storage, network, and protocol edits.
* Copy-on-write immutability: Clone only the changed nested Legion branch and return the original state when nothing changes.
* Snapshot-vs-accumulation split: Treat `ts_errors` as current truth, clear `ram` on `build_fix`, keep repeated identical enemy refs stable, and block replay-mode stack farming.
* Privacy-safe aggregate refs: Store stable ids, bounded path/detail labels, timestamps, levels, and counts; exclude messages, previews, raw output, prompt text, file contents, terminal output, provider payloads, tokens, and secrets.

***

## 6. Deliverables

### Files To Create

| File | Purpose                                                           | Est. Lines |
| ---- | ----------------------------------------------------------------- | ---------- |
| None | This session expands existing projection contract and tests only. | 0          |

### Files To Modify

| File                                    | Changes                                                                                                                                                                     | Est. Lines |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `apps/web/src/lib/gameProjection.ts`    | Add Legion reducer helpers, dev-health event folding, siege reduction, failed-tool wraith stacking, replay guards, focus recomputation, and bounded serialization behavior. | \~260      |
| `apps/web/tests/gameProjection.test.ts` | Add typed enemy event builders and reducer tests for upsert/clear behavior, wraith caps, replay guards, focus promotion, and privacy serialization exclusions.              | \~280      |

***

## 7. Success Criteria

### Functional Requirements

* [ ] `test_failure` creates or refreshes one skirmisher per stable path key without storing failure messages.
* [ ] `build_error` creates or refreshes a singleton ram with bounded detail; `build_fix` clears it and promotes the next eligible focus.
* [ ] `ts_errors` reconciles `gremlins.tsErrors` and `gremlins.tsFiles` from the event counts, including zero-count clearing.
* [ ] `lint_error` updates bounded gremlin pressure without storing raw lint messages and without unbounded growth.
* [ ] `task_near_stall`, `task_stalled`, and `task_escalation` reconcile one `legion.siege` aggregate with sorted, unique stalled task ids and deterministic severity.
* [ ] Failed `tool_result` events create capped per-hero wraith stacks and neutral hero projection records without storing tool previews or mission details.
* [ ] Replay mode cannot increase wraith stacks or other accumulation-like enemy counters, while clear/reconciliation events still work where defined.
* [ ] `recomputeAlertFocus` continues to honor `flare > gate > siege > ram > skirmisher > newCamp > null` after every enemy mutation.
* [ ] Reducer paths return the original state reference for ignored events, duplicate no-op events, and recognized events that leave the aggregate unchanged.

### Testing Requirements

* [ ] `apps/web/tests/gameProjection.test.ts` covers dev-health enemy upsert/clear behavior, siege reconciliation, wraith cap behavior, replay no-accumulation, focus promotion, and serialization exclusions.
* [ ] `npm test -- apps/web/tests/gameProjection.test.ts` passes.
* [ ] `npm --workspace @factionos/web run typecheck` passes.

### Non-Functional Requirements

* [ ] Projection JSON remains bounded and excludes raw prompts, failure messages, lint messages, tool previews, terminal output, command output, file contents, provider payloads, tokens, secrets, broad absolute paths, replay entries, toasts, animation state, audio state, combat strike queues, and reward queues.
* [ ] Reducer behavior is deterministic for a fixed state, event, and `ctx.now`.
* [ ] The module remains side-effect free except for the existing storage helpers from Session 01.

### Quality Gates

* [ ] All files ASCII-encoded.
* [ ] Unix LF line endings.
* [ ] Code follows project conventions.
* [ ] Primary user-facing surfaces are unchanged; this session adds no UI surface.

***

## 8. Implementation Notes

### Working Assumptions

* Package context is `apps/web`: The analyzer reports the Session 03 stub package as `apps/web`, Phase 22 names `apps/web/src/lib/gameProjection.ts`, and `apps/web/README_web.md` places pure helpers and Vitest tests in the targeted paths. Planning can proceed with a concrete package path.
* Session 03 is the next executable session: The analyzer reports `current_session: null`, Sessions 01 and 02 are completed, and the Session 03 stub depends only on Session 02. Later stubs depend on this enemy reducer foundation.
* `lint_error` is an event signal, not a full lint snapshot: The protocol event carries one path and message, while `GameProjectionLegion.gremlins` stores only numeric counters. This plan treats lint gremlin pressure as bounded aggregate state, skips replay accumulation, and excludes raw messages so planning can proceed without protocol changes.
* The Phase 22 wraith cap is 3: The Phase 22 PRD says wraiths stack per hero and the board-wide cap is 3. Session 03 will apply that cap to persisted per-hero stack counts and leave presentation-specific board multiplication out of scope.

### Conflict Resolutions

* Phase 22 wants path-derived enemy refs but security carry-forward warns against broad path exposure. The chosen interpretation is to keep the documented `skirmishers[path]` aggregate while bounding and normalizing stored labels and testing that messages, previews, and broad raw payloads do not enter projection JSON.
* Live enemy events mix snapshot and accumulation semantics. The chosen interpretation is snapshot reconciliation for events with current counts or explicit clears (`ts_errors`, `build_fix`) and bounded live pressure for single-signal events (`lint_error`, failed `tool_result`) with replay accumulation disabled.

### Key Considerations

* Enemy state must stay aggregate-only and bounded; it is not an event log or future animation/strike queue.
* `alertFocus` remains derived from `attention` and `legion`, not manually maintained as an independent source of truth.
* Camp state exists in the v1 shape but Session 03 should not invent camp coalescing, camp progress, camp kills, or Quest Board links.
* Replay and reconnect honesty matter even before store persistence is wired, because Session 07 will build on these reducer guarantees.

### Potential Challenges

* Stable keys: Test, lint, and task events need deterministic keys without preserving raw diagnostic payloads.
* Duplicate events: Repeated identical build or stall signals should avoid unnecessary object churn where the aggregate is unchanged.
* Severity mapping: `task_near_stall`, `task_stalled`, and `task_escalation` need a simple deterministic level scheme without adding future War Council or reward logic.
* Privacy tests: Builders should include sensitive messages and previews so tests prove those strings do not enter serialized projection state.

### Relevant Considerations

* \[P03] **Stable docs are the current contract**: Use `.spec_system/PRD/phase_22/PRD_phase_22.md`, package READMEs, and protocol types as current truth rather than archived source material.
* \[P18-apps/web] **Pure normalization before store mutation**: Normalize event payloads before projection mutation so malformed or terminal entries cannot blank the aggregate.
* \[P20] **Broad privacy gates are release-critical**: Keep raw prompts, command output, terminal output, file contents, provider payloads, broad paths, tokens, and secrets out of projection snapshots and tests.
* \[P08] **Full trusted erasure remains no-claim**: New browser-local projection enemy refs do not imply erasure across archives, logs, backups, hosted surfaces, or workspace files.

### Behavioral Quality Focus

Checklist active: Yes Top behavioral risks for this session:

* External event payloads could leak failure messages, tool previews, or broad diagnostic payloads into persisted projection JSON.
* Replay traffic could inflate wraith stacks or gremlin pressure if accumulation and reconciliation are not separated.
* Lower-priority enemy focus could override higher-priority flare or gate attention if focus recomputation drifts from the documented priority ladder.

***

## 9. Testing Strategy

### Unit Tests

* Test `test_failure`, `build_error`, `build_fix`, `ts_errors`, and `lint_error` reducer behavior for immutable upserts, clears, bounded values, and same-reference duplicate handling.
* Test siege reconciliation for near-stall, stalled, and escalation events with sorted unique stalled task refs and deterministic level promotion.
* Test failed `tool_result` wraith stacks for hero creation, cap behavior, replay no-accumulation, and preview exclusion.
* Test `recomputeAlertFocus` promotion when flare and gate attention are absent and demotion back to ram, skirmisher, newCamp, or null after clears.
* Test serialization exclusions for failure messages, lint messages, tool previews, command output, terminal output, broad paths, file contents, tokens, and secrets.

### Integration Tests

* None for this session. Store folding, selectors, reset behavior, persistence lifecycle, replay-store behavior, and local erasure are later Phase 22 sessions.

### Runtime Verification

* Run `npm test -- apps/web/tests/gameProjection.test.ts`.
* Run `npm --workspace @factionos/web run typecheck`.
* Run ASCII and LF checks against `apps/web/src/lib/gameProjection.ts`, `apps/web/tests/gameProjection.test.ts`, and the session artifacts.

### Edge Cases

* Empty path or task id events, duplicate `test_failure`, repeated `build_error` with same detail, `build_fix` without active ram, zero `ts_errors`, repeated `lint_error` during replay, failed `tool_result` without hero id, successful `tool_result`, wraith stack at cap, multiple stalled task ids, flare/gate focus outranking enemy focus, and old enemy state parsing through existing guards.

***

## 10. Dependencies

### Other Sessions

* Depends on: `phase22-session01-projection-contract`, `phase22-session02-hero-attention-reducer`.
* Depended by: `phase22-session04-camp-link-boundaries`, `phase22-session05-store-folding-and-selectors`, `phase22-session06-persistence-lifecycle`, `phase22-session07-honesty-regression-tests`, and `phase22-session08-erasure-docs-and-validation`.

***

## Next Steps

Run the `implement` workflow step to begin implementation.


---

# 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/sessions/phase22-session03-legion-enemy-reducer/spec.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.
