> 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/specs/phase24-session01-pure-strike-and-combo-model/spec.md).

# Session Specification

**Session ID**: `phase24-session01-pure-strike-and-combo-model` **Phase**: 24 - Legion II - Live Tier And Combat Playback **Status**: Not Started **Created**: 2026-07-08 **Package**: apps/web **Package Stack**: TypeScript

***

## 1. Session Overview

This session builds the pure, side-effect-free combat playback model that the rest of Phase 24 renders and wires. Game-design section 5A defines combat as deterministic playback of a linked mission's real tool-event stream, and section 14.4 requires that repetition escalates instead of repeating: strikes merge into combo stages, per-category SFX rate limits silence sound while visuals continue, and long sieges accumulate stage props so minute 40 looks different from minute 5.

The deliverable is a new `apps/web/src/lib/combatPlayback.ts` module with no DOM, store, or audio dependencies. It classifies tool events into strike kinds (Edit/Write hammer, Bash fire, Read/Grep scout ring, Task peon flank, generic fallback, and the tool-result-error counterattack), merges strikes into combo stages inside a tunable window, derives stage-prop milestones from mission elapsed time, and makes pure allow/deny SFX rate-limit decisions. Every tunable lands as a named exported constant so the Phase 12 tuning sweep can adjust values without touching logic.

Following the Phase 23 lesson that pure reconciliation first made every later session cheaper, this module is deliberately independent: Sessions 03, 04, and 06 consume it, but nothing here depends on linkage, rendering, or audio. Safe-label derivation for strike and skirmisher tooltips is included with privacy tests so later sessions cannot accidentally render raw paths.

***

## 2. Objectives

1. Classify `tool_use`/`tool_result`-shaped data into deterministic strike kinds with zero RNG and a generic fallback for unknown tools.
2. Merge same-category strikes within a tunable window into escalating combo stages that provably bound visual event frequency over a simulated 45-minute stream.
3. Derive stage-prop milestones from mission elapsed time as pure data.
4. Provide pure per-category SFX rate-limit decisions (N per minute) and safe-label derivation covered by privacy tests.

***

## 3. Prerequisites

### Required Sessions

* [x] `phase23-session08-validation-and-documentation` - Phase 23 complete; projection substrate for live enemies and links verified in place.

### Required Tools/Knowledge

* Vitest pure-lib test conventions in `apps/web/tests/`.
* `ToolUse` / `ToolUseEvent` / `ToolResultEvent` shapes from `packages/protocol/src/missions.ts` and `events.ts`.
* Existing tool taxonomy in `apps/web/src/lib/toolUsage.ts` (`toolCategoryFor`) and safe-label patterns in `apps/web/src/lib/legionCamps.ts`.

### Environment Requirements

* Node 26.2.0+, npm 11.16.0, workspace install present.

***

## 4. Scope

### In Scope (MVP)

* A viewer can rely on strike kinds mapping to real work: classification of tool names to `hammer` (Edit/Write/NotebookEdit), `fire` (Bash), `scout` (Read/Grep/Glob/WebSearch/WebFetch), `peon` (Task), and `strike` (generic fallback) - pure function over the tool name reusing `toolUsage.ts` categories where they fit.
* A viewer can distinguish a counterattack: a failed tool result (`ok: false`) classifies as `counterattack` targeting the hero.
* Long fights escalate instead of strobing: a pure combo accumulator that, given ordered strike timestamps per category, produces combo stages (stage 1..MAX) inside `COMBO_WINDOW_MS` and resets outside it.
* Long sieges visibly dig in: `stagePropsForElapsed(elapsedMs)` returning the cumulative prop tier from `STAGE_PROP_MILESTONES_MS`.
* Audio stays bounded: `shouldPlayCue(category, historyMs, nowMs)` allow/deny under `SFX_RATE_LIMIT_PER_MINUTE`, per category, independent categories.
* Tooltips stay private: safe strike label derivation (bounded length, relative-path style like the existing camp sector labels, no raw `inputPreview` passthrough).
* Focused pure test file `apps/web/tests/combatPlayback.test.ts`.

### Out of Scope (Deferred)

* DOM rendering, React components, effects layers - *Reason: Sessions 03-04.*
* Store wiring, reducer changes, pending-link recording - *Reason: Session 02.*
* Actual SFX cue definitions and audio runtime wiring - *Reason: Session 06.*
* Tuning validation of constant values - *Reason: design Phase 12 sweep.*

***

## 5. Technical Approach

### Architecture

One new pure module, `apps/web/src/lib/combatPlayback.ts`, exporting types, named tuning constants, and pure functions. No imports from the store, React, or audio runtime; protocol types may be imported type-only. State needed across calls (combo accumulators, rate-limit history) is expressed as immutable input/output values the caller threads, mirroring how `legionCamps.ts` stays side-effect free.

### Design Patterns

* Pure reducer-style helpers with immutable inputs/outputs: proven by the Phase 23 `legionCamps.ts` lesson to make later sessions cheap and testable.
* Named tuning constants exported at module top: matches the `CELEBRATION_MAX_CONCURRENT` and Phase 23 constant conventions so Phase 12 can sweep them.
* Equality-stable outputs: helpers return the same reference for no-op updates where practical, mirroring reducer equality guards.

### Technology Stack

* TypeScript (web workspace `tsc -b --noEmit`), Vitest, Biome.

***

## 6. Deliverables

### Files to Create

| File                                    | Purpose                                                                              | Est. Lines |
| --------------------------------------- | ------------------------------------------------------------------------------------ | ---------- |
| `apps/web/src/lib/combatPlayback.ts`    | Pure strike classification, combo merging, stage props, SFX rate limits, safe labels | \~260      |
| `apps/web/tests/combatPlayback.test.ts` | Focused pure tests including 45-minute simulation and privacy checks                 | \~320      |

### Files to Modify

| File   | Changes                                         | Est. Lines |
| ------ | ----------------------------------------------- | ---------- |
| (none) | Session is additive; no existing module changes | 0          |

***

## 7. Success Criteria

### Functional Requirements

* [ ] Every `ToolName` in the protocol union classifies to a strike kind; unknown/custom MCP tools classify to the generic `strike` fallback.
* [ ] `tool_result` with `ok: false` classifies as `counterattack`; `ok: true` produces no strike of its own.
* [ ] Combo accumulation reaches and caps at the max stage inside the window and resets after `COMBO_WINDOW_MS` of quiet.
* [ ] A simulated 45-minute stream of 1200+ events yields bounded distinct visual emissions (merged combos) and bounded allowed cues per category.
* [ ] Stage props are monotonic non-decreasing with elapsed time and hit every milestone exactly once.
* [ ] Rate-limit decisions are independent per category: silencing `hammer` cues does not silence `fire` cues.

### Testing Requirements

* [ ] Unit tests written and passing (`npx vitest run tests/combatPlayback.test.ts` from `apps/web`).
* [ ] Privacy tests prove derived labels never contain raw `inputPreview` content, absolute paths, or strings beyond the length bound.

### Non-Functional Requirements

* [ ] Module has no imports from store, React, DOM, or audio runtime (type-only protocol imports allowed); enforced by a test or review note.
* [ ] All tuning values are named exported constants.

### Quality Gates

* [ ] All files ASCII-encoded, Unix LF line endings.
* [ ] Biome format/lint clean; web typecheck passes.
* [ ] Code follows `CONVENTIONS.md` and existing lib/test naming.

***

## 8. Implementation Notes

### Key Considerations

* Reuse `toolCategoryFor` from `toolUsage.ts` where its categories align, but strike kinds are a presentation vocabulary - keep the mapping explicit and local so tool taxonomy changes do not silently change combat reads.
* The design fixes strike duration at <= 400ms; that constant belongs here even though rendering consumes it later.
* Combo state is keyed per (target, category) by the caller; the module should define the key helper so Sessions 03/04 agree on it.

### Potential Challenges

* Over-modeling: resist adding camp/HP/link logic here - that is projection and Session 04 territory. Mitigation: module boundary review against the scope list before closing.
* Time handling: all functions take explicit timestamps; no `Date.now()` inside the module, which also keeps tests deterministic.

### Relevant Considerations

* \[P23-apps/web] **Pure camp reconciliation first**: same pattern applied to combat playback; this session is the pure foundation the phase leans on.
* \[P23-apps/web] **Do not turn scanner camps into combat rewards**: this module emits presentation data only - no XP, kills, or progress fields.
* \[P07] **Redaction is boundary-specific**: safe-label derivation is the explicit minimization point for strike tooltips; privacy tests enforce it.
* \[P20] **Broad privacy gates are release-critical**: no raw commands or previews may survive into derived labels.

### Behavioral Quality Focus

Checklist active: Yes Top behavioral risks for this session:

* Unknown or custom MCP tool names crashing or misclassifying instead of falling back to the generic strike.
* Combo/rate-limit state growing unbounded over a long mission (memory leak shaped as an ever-growing history array).
* Label derivation leaking absolute paths or raw input previews into presentation strings.

***

## 9. Testing Strategy

### Unit Tests

* Classification table: every known `ToolName`, several custom MCP strings, and both `tool_result` outcomes.
* Combo stage progression, cap, reset, and per-key independence.
* Stage-prop milestones at boundaries (just before, at, after each).
* Rate-limit allow/deny across window edges and category independence.
* History pruning: bounded accumulator/history sizes after long streams.

### Integration Tests

* Simulated 45-minute mission stream (mixed categories, bursts, quiet gaps) asserting bounded visual emissions and cue allowances end to end.

### Manual Testing

* None required; module is pure. Spot-check exports compile into the web build via typecheck.

### Edge Cases

* Empty/whitespace tool names; events out of chronological order (clamp or document ordering contract); zero-length windows; elapsed time before mission start (negative) returning tier 0.

***

## 10. Dependencies

### External Libraries

* None new.

### Other Sessions

* **Depends on**: none (first session of Phase 24).
* **Depended by**: `phase24-session03-live-enemy-presentation`, `phase24-session04-combat-playback-effects-layer`, `phase24-session06-world-cues-and-bark-reuse`.

***

## Next Steps

Run `/implement` to begin AI-led 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/specs/phase24-session01-pure-strike-and-combo-model/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.
