# AGENTS.md - Codex Guide for Deathwatch Roller

This file gives Codex repo-specific context for working on Deathwatch Roller.
Prefer the live code and `package.json` over older docs when they disagree.

## Project Snapshot

Deathwatch Roller is a Create React App + Express application for running
Deathwatch tabletop RPG sessions. It includes player login and character data,
dice rolling, missions, simulations, a requisition shop, bestiary, rules search,
weapon references, GM tools, and user-submitted error reports.

The repository is currently a single-root JavaScript app:

- Frontend: React 18 JSX under `src/`
- Backend: Express server under `database/`
- Database: MariaDB via `mysql2/promise`, with some legacy SQLite artifacts
- Static data: JSON and generated assets under `public/`, `data/`, `database/`,
  and `database/rules/`
- Tests: Jest + React Testing Library for frontend unit tests; separate
  integration config for root-level integration tests

## High-Value Paths

- `src/App.js` - main React app, login/session state, tab wiring
- `src/components/` - feature UI components
- `src/components/DeathwatchRoller.jsx` - combat dice roller, weapon mapping,
  attack rolls, damage, hit locations, history, presets, and tracker state
- `src/components/SkillRoller.jsx` - sheet-driven skill checks, manual d100
  entry, target overrides, and fate-point roll modification
- `src/components/rollHelpers.js` - shared frontend d100, degrees of
  success/failure, ids, and fate application helpers used by roller components
- `src/utils/diceRoller.js` - mission-safe dice helpers and
  `MISSION_ROLL_CONTEXT_KEY`
- `src/components/MissionTab.jsx` - active mission play surface, scene reveal
  state, check rolling, combat state, initiative, mission roll feed, and GM
  progress controls
- `src/components/SimulationTab.jsx` - simulation report UI, run controls,
  difficulty/profile selection, roll feed display, and saved reports
- `src/components/MissionSimTab.jsx` - older/generated mission simulator UI;
  check current routing before extending it
- `src/utils/` - shared frontend utilities such as dice and XP logic
- `src/tests/` - Jest unit tests run by the default test config
- `database/server.js` - Express backend entrypoint and route mounting
- `database/routes/` - API routes for players, sessions, shop, rules, bestiary,
  weapons, missions, simulations, staging, and error reports
- `database/routes/missionRoutes.js` - mission CRUD, active mission selection,
  player-safe scene projection, active-scene patching, and shared roll feed APIs
- `database/routes/simulationRoutes.js` - backend simulation engine and
  simulation CRUD/report APIs
- `database/mariadb.js` - MariaDB pool, schema creation, schema patching
- `database/sessionModel.js` - session storage helpers
- `database/shop-helpers.js` - requisition/shop business logic
- `scripts/` - import, cleanup, validation, generation, and local CI helpers
- `docs/` - user/developer docs; some structure docs are stale and still refer
  to non-existent `backend/` and `frontend/` directories

## Commands

Run commands from the repository root unless noted.

```bash
npm install
npm start
npm run server:notest
npm run test:unit
npm test
npm run build
```

Useful variants:

```bash
npm run server        # runs tests first, then database/server.js
npm run test:integration
npm run build:fast    # skips tests, then reloads PM2
./scripts/local-ci.sh
```

Local ports:

- Frontend dev server: `http://localhost:3000`
- Backend API: `http://localhost:5000`
- CRA proxy points frontend API requests at `http://localhost:5000`

## Environment And Runtime Notes

- Backend loads environment variables with `dotenv` from the backend process
  working directory. `npm run server:notest` runs from `database/`, so
  `database/.env` is relevant.
- MariaDB connection defaults are hard-coded in `database/mariadb.js`:
  host `192.168.1.113`, user `deathwatch`, database `deathwatch`, port `3307`,
  password from `DB_PASSWORD` or `defaultpassword`.
- GM upload auth checks `GM_SECRET` and falls back to `defaultsecret`.
- `/api/narrate` calls Ollama using `OLLAMA_BASE` and `NARRATOR_MODEL`.
- The backend can serve `build/` statically when a production build exists.

## Testing Guidance

- Use `npm run test:unit` for normal frontend changes.
- Use targeted Jest commands when iterating, for example:

  ```bash
  npm test -- src/tests/login.test.js
  ```

- Use `npm run test:integration` only when backend/API behavior is touched and
  PM2/MariaDB are available.
- `npm run build` runs unit tests before `react-scripts build`.
- After any change that affects the production site or a PM2-served workflow,
  also verify the live site at `https://dwroller.alw.dk` in a browser after
  `npm run build:fast` or the equivalent deployment step. Treat local tests and
  builds as incomplete until the live flow has been checked.
- If tests fail because MariaDB, PM2, Ollama, or local services are unavailable,
  report that explicitly rather than masking the failure.

## Implementation Conventions

- The codebase is plain JavaScript/JSX, not TypeScript. Match the surrounding
  style unless a broader migration is explicitly requested.
- Prefer existing React hooks/component patterns in `src/components/`.
- Keep state persistence keys stable; the app uses `localStorage` keys such as
  `dw:shop:authedPlayer`, `dw:shop:sessionId`, and `dw:shop:playerData`.
- API calls from the frontend generally use relative `/api/...` URLs through
  the CRA proxy.
- Preserve backend response shapes used by existing components and tests.
- Keep database writes parameterized through `mysql2` APIs.
- Avoid broad refactors in data import scripts unless the task is specifically
  about those scripts.

## Dice Roller, Mission, And Simulation Notes

### Dice Roller

- The main dice roller lives in `src/components/DeathwatchRoller.jsx`.
- Shared roller primitives live in two places:
  - `src/components/rollHelpers.js` for UI roller and skill roller behavior.
  - `src/utils/diceRoller.js` for mission-safe helpers and the mission roll
    context storage key.
- Core roll semantics:
  - `d100()` returns 1-100.
  - `degrees(target, roll)` treats `roll <= target` as success and reports
    Degree of Success or Degree of Failure in 10-point bands.
  - Skill and combat roll targets should stay clamped to sensible d100 bounds.
- Combat helpers in `DeathwatchRoller.jsx` cover dice expressions, weapon
  normalization, RoF modes, hit counts from DoS, hit location from reversed
  d100, tearing/proven damage dice, and damage mitigation.
- Skill rolling is delegated to `SkillRoller.jsx`, which reads the logged-in
  player's sheet skills/characteristics, applies training modifiers, supports a
  manual d100 roll, and can override the target number.
- Fate handling is shared by `FateControls.jsx`, `useFate.js`, and
  `applyFateToTest()` in `rollHelpers.js`. Spending fate persists through
  `POST /api/players/:name/spend-fate`.
- Persistent browser state keys used by the roller include:
  - `dw:presets:v3`
  - `dw:history:v2`
  - `dw:weapons:v3`
  - `dw:tracker:v1`
  - `dw:mission:rollContext`

### Mission Play

- `MissionTab.jsx` is the current mission play implementation used from
  `App.js`.
- GM users are identified in the UI with `authedPlayer === 'gm'`.
- GM flow:
  - Load all missions with `GET /api/missions`.
  - Load the current active mission with `GET /api/missions/active/current`.
  - Create/update/delete missions through `POST/PUT/DELETE /api/missions`.
  - Set the active mission with `POST /api/missions/:id/active`.
  - Save progress and scene edits with `PUT /api/missions/:id/progress`.
- Player flow:
  - Load only the player-safe active scene with
    `GET /api/missions/active/player`.
  - Hidden scene fields are stripped in `playerScene()` in
    `database/routes/missionRoutes.js`.
  - Player mini rollers patch only the active scene through
    `PUT /api/missions/active/scene` so they do not overwrite the full mission.
- The shared mission roll feed is backed by the `mission_rolls` table:
  - Read with `GET /api/missions/active/rolls/feed?limit=50`.
  - Create with `POST /api/missions/rolls`.
  - Delete with `DELETE /api/missions/rolls/:id`.
- Mission scenes may contain checks, objectives, complications, extra
  challenges, combat state, reveal state, play prompts, enemies, rewards,
  `storyHook`, `secret`, GM notes, and ending text. Preserve unknown scene
  fields when patching scenes.
- Generated missions should give every scene a GM-only `storyHook` that acts as
  a thread toward the mission's main story. Skill check rewards are the visible
  triggers that reveal the hook; failures should use fail-forward consequences
  instead of blocking the clue.
- Combat state is scene-local and includes round, initiatives, conditions, and
  fear rating. Initiative rolls use `1d10 + Ag bonus`; fear/check rolls use the
  same d100 degree helpers as the roller.
- When changing mission roll behavior, verify both GM and player perspectives:
  the GM sees the full mission, while players only receive the active safe scene.

### Simulation

- `SimulationTab.jsx` is the current simulation report and control UI.
- `database/routes/simulationRoutes.js` contains the backend simulation engine.
  It loads mission/player data, builds temporary player combat state from
  `tabInfo`, runs scenes, and saves reports through `simulationHelpers`.
- Simulation endpoints:
  - `POST /api/simulations` runs and saves a new simulation.
  - `GET /api/simulations?limit=50` lists saved simulation summaries.
  - `GET /api/simulations/:id` returns a full saved report.
  - `DELETE /api/simulations/:id` removes a saved report.
- Simulation request inputs include `mission_id`, `player_names`, `difficulty`,
  per-player `combat_profiles`, and `enemy_profile`.
- Difficulty levels are 1-5 and affect wounds, enemy BS, check modifiers, fear,
  extra enemies, recovery, and max combat rounds.
- Combat profiles include `auto`, `balanced`, `ranged`, and `melee`; they affect
  weapon choice, grenade use, and enemy attack style.
- The simulation engine normalizes weapons from player `tabInfo.weapons`,
  infers ammo/clip defaults for common Deathwatch weapons, tracks ammo spend,
  resolves single/semi/full-auto hit counts, rolls initiative each combat round,
  applies conditions, performs fear tests, spends fate once when useful, and
  builds roll feeds/player report cards.
- Saved simulation reports include result, earned XP, total rounds, total rolls,
  overall success rate, combat success rate, puzzle/check success rate, scene
  results, player cards, roll feed, story hooks, findings, `difficulty_level`,
  and `enemy_profile`.
- Because simulations use `Math.random()` directly and are not seeded, tests and
  docs should avoid assuming deterministic exact roll output.

## App Tabs

Tabs are selected in `src/App.js` with the `tab` state. Login/session state is
owned by `App.js` and passed to tabs as `authedPlayer` and `sessionId` where
needed. Navigation actions are logged with `logUserAction()`.

### Mission

- Component: `src/components/MissionTab.jsx`
- Default tab: `mission`
- Availability: all logged-in states, but GM and player views differ.
- Backend: `/api/missions`, `/api/missions/active/current`,
  `/api/missions/active/player`, `/api/missions/active/scene`, and mission roll
  feed endpoints.
- Notes: GM sees full mission state; players receive only the active safe scene.
  Preserve unknown scene fields and avoid replacing the full scene array from
  player-facing actions.

### Dice Roller

- Component: `src/components/DeathwatchRoller.jsx`
- Tab key: `roller`
- Availability: visible to all users.
- Backend: player sheet/fate endpoints, bestiary enemy data where loaded, and
  mission roll context when launched from mission play.
- Notes: Depends on `SkillRoller.jsx`, `FateControls.jsx`, `useFate.js`,
  `rollHelpers.js`, and `src/utils/diceRoller.js`. Keep d100/DoS behavior
  aligned across mission, skill, and combat rolls.

### Requisition Shop

- Component: `src/components/RequisitionShop.jsx`
- Tab key: `shop`
- Availability: visible to all users; GM controls appear for `authedPlayer ===
  'gm'`.
- Backend: `GET /api/shop`, `POST /api/shop/purchase`,
  `GET /api/players/:name`, `GET /api/players`, `POST /api/players/gm/set-rp`,
  and `POST /api/players/gm/set-renown`.
- Data source: `public/deathwatch-armoury.json` through `shopRoutes.js`.
- Notes: Purchases deduct `tabInfo.rp` and append/update `tabInfo.gear`.
  Renown gating uses the local rank order in the component.

### Character Sheet

- Component: `src/components/PlayerTab.jsx`
- Tab key: fallback branch when no other tab matches; nav label is
  `Character Sheet`.
- Availability: visible to all users; GM edit tools are enabled only when the
  logged-in user is `gm`.
- Backend: `GET /api/players`, `GET /api/players/:name`,
  `PUT /api/players/:name`, avatar upload under
  `POST /api/players/:name/avatar` if present, and GM player endpoints.
- Local state: caches player data under `dw:shop:players:v1` and also consumes
  `dw:shop:playerData` as a fallback.
- Notes: Character data is stored primarily in `tabInfo` and includes
  characteristics, skills, weapons, armour, gear, wounds, fate, XP, XP spent,
  renown, movement, insanity, corruption, notes, and avatar/picture data.
  Preserve `tabInfo` shape because the roller and simulator read it directly.

### Rules

- Component: `src/components/RulesTab.jsx`
- Tab key: `rules`
- Availability: visible to all users.
- Backend: `GET /api/rules/categories`, `GET /api/rules/search`,
  `GET /api/rules/rule/:id`, and `GET /api/rules/random`.
- Local state: recent searches are stored as `dw:rules:recent`.
- Notes: Results include structured fields such as summary, examples,
  category, source, page, aliases, tags, and midgame priority. Keep highlighting
  and compact/full rendering behavior when changing rule result shapes.

### Weapons

- Component: `src/components/WeaponsTab.jsx`
- Tab key: `weapons`
- Availability: visible to all users.
- Backend: `GET /api/weapons`.
- Data source: MariaDB weapons when available, with fallback to
  `public/deathwatch-armoury.json`.
- Notes: The tab normalizes displayed categories into ranged, melee, grenade,
  armour, and other. Ranged stat strings may be parsed from semicolon-separated
  fields in `stats.damage`.

### Error Reports

- Component: `src/components/ErrorReports.jsx`
- Tab key: `errors`
- Availability: visible to all logged-in users; GM sees all reports and can
  resolve/delete.
- Backend: `GET /api/errors`, `POST /api/errors`,
  `PUT /api/errors/:id/resolve`, `PUT /api/errors/:id/status`, and
  `DELETE /api/errors/:id`.
- Auth: requires `x-session-id`; backend uses `requireSession`.
- Notes: Non-GM users should only see their own reports. Form submissions
  default `pageUrl` to the current browser path when not provided.

### Bestiary

- Component: `src/components/BestiaryTab.jsx`
- Tab key: `bestiary`
- Availability: GM-only in `App.js`; non-GM users see an access denied panel.
- Backend: `GET /api/bestiary/full`, `POST /api/bestiary/reload`, and
  `GET /api/bestiary/enemies` for dice roller enemy format.
- Local state: caches entries under `dw:enemies:v1`; DB-down warning dismissal
  uses `dw:warning-dismiss-until:v1`.
- Data source: MariaDB bestiary rows with fallback to
  `public/deathwatch-bestiary-extracted.json`.
- Notes: `normalizeEntry()` handles multiple imported statblock shapes. Keep
  fallback/cache behavior intact so the tab remains useful when the DB is down.

### Player Management

- Component: `src/components/PlayerManagement.jsx`
- Tab key: `players`
- Availability: GM-only nav item. The component also expects GM context.
- Backend: `GET /api/players`, `POST /api/players/gm/add-or-update`,
  `POST /api/players/gm/set-xp`, `POST /api/players/gm/set-xp-spent`,
  `POST /api/players/gm/set-rp`, `POST /api/players/gm/set-renown`,
  `POST /api/players/gm/reset-password`, and
  `DELETE /api/players/gm/delete/:name`.
- Auth: sends `x-session-id` and `x-gm-secret` where available; backend GM
  secret defaults are defined in `playerRoutes.js`.
- Notes: Bulk XP/RP operations update each player in sequence. The component
  expects player economic and progression values in `tabInfo`.

### GM Kit

- Component: `src/components/GMKit.jsx`
- Tab key: `gmkit`
- Availability: GM-only nav item and component-level access check.
- Backend: none for the current table UI; `App.js` separately uses
  `GET /api/gmkit/list` to choose the players-tab background.
- Notes: Current GM Kit is static reference tables inside the component:
  difficulty modifiers, hit locations, combat actions, weapons, armour,
  critical hits, weapon qualities, cover, renown, and related GM references.
  There are older `GMKit_old.jsx` and `GMKit_new.jsx` files; check routing
  before editing them.

### Simulation Lab

- Component: `src/components/SimulationTab.jsx`
- Tab key: `simulation`
- Availability: GM-only nav item in `App.js`.
- Backend: `/api/simulations` endpoints and mission/player data loaded by the
  backend simulation route.
- Notes: Reports include roll feeds, scene logs, player report cards, story
  hooks, findings, difficulty, and enemy profile. Simulation output is
  non-deterministic.

### External Utility Pages

- `public/cards.html` is linked from nav as `Kort` and opens in a new tab. It is
  a printable card-sheet view and fetches app/static data directly from the
  served public site.
- `public/print.html` is linked from nav as `Print` and opens in a new tab. It
  is a printable quick-reference sheet.
- These are not React tabs. They are static public pages served by CRA/dev
  server or by Express static serving in production.

## Deathwatch RPG Domain Notes

Use these notes when changing the app's roller, mission, simulation, shop,
rules, bestiary, and character-sheet flows. They are summarized from the local
rules database and app data, not copied from rulebook text. Do not paste long
rulebook passages into UI or docs; link/search the local rules DB and summarize
mechanics in original wording.

### Source Data In This Repo

- Rules index: `database/rules/rules-database.json`
  - Categories include `actions`, `combat`, `damage`, `conditions`,
    `requisition`, `renown`, `cohesion`, `squad_mode`, `psychic`, `hordes`,
    `gm_tables`, `mission`, `skills`, `talents`, and `traits`.
- Skill import/reference data: `database/deathwatch_skills_p94_107.csv`
- Armoury/shop data: `public/deathwatch-armoury.json`
  - Major groups include ranged weapons, melee weapons, grenades, other
    weapons, power armour, helmets, carapace/natural/primitive/xenos armour,
    shields, and other armour.
- Bestiary data: `public/deathwatch-bestiary-extracted.json` and
  `database/deathwatch-bestiary-extracted.json`
  - Entries generally include profile, movement, wounds, toughness, skills,
    talents, traits, armour, book, page, and snippets.
- Backend may load rules, weapons, and bestiary from MariaDB first, then fall
  back to the JSON files above.

### Core Resolution Model

- Deathwatch uses percentile tests: roll `d100` and succeed when the roll is
  equal to or under the effective target.
- Effective target is normally characteristic + skill/training + situational
  modifiers. The app generally clamps practical targets to d100 bounds.
- Degrees of Success/Failure are 10-point bands from the target/roll margin.
  The app's shared helper returns 1 base degree plus one per full 10 points.
- Basic skills can be attempted broadly; untrained advanced skills should be
  treated as unavailable or heavily constrained unless the app explicitly
  allows an override.
- Common characteristics:
  - `WS` Weapon Skill for melee attacks and parries.
  - `BS` Ballistic Skill for ranged attacks.
  - `S`, `T`, `Ag`, `Int`, `Per`, `Wp`, `Fel` for strength, toughness,
    agility, intelligence, perception, willpower, and fellowship checks.
- When making UI for tests, expose the final target, roll, success/failure,
  DoS/DoF, and the source of modifiers. This is more useful at the table than
  only showing pass/fail.

### Combat Flow

- Combat is round-based and action-based. Common action concepts include Aim,
  Charge, Standard Attack, Semi-Auto Burst, Full Auto Burst, Overwatch,
  Suppressing Fire, Dodge, Parry, Ready, Reload, Move, Run, and Disengage.
- Initiative is relevant to combat order. In the simulator, each combat round
  currently rolls player `Agility Bonus + d10` and enemy `agBonus + d10`, then
  resolves actors from highest to lowest.
- Attack flow for app purposes:
  - Choose WS or BS based on melee/ranged attack.
  - Apply attack-mode and situation modifiers.
  - Roll d100 against target.
  - On a hit, derive hit count from fire mode and DoS.
  - Determine hit location from the reversed d100 attack roll where needed.
  - Roll damage, apply weapon qualities such as Tearing/Proven where supported.
  - Reduce damage by armour and Toughness Bonus/Penetration handling as the
    app model supports.
  - Track wounds, critical damage threshold, defeat/downed status, and notable
    events.
- Semi-auto and full-auto are not just labels. They change the attack modifier
  and how additional hits scale with DoS. Keep roller and simulator hit-count
  formulas aligned.
- Dodge and Parry are reactions that negate hits when allowed. Dodge is usually
  the generic ranged/melee avoidance reaction; Parry is melee and WS-based.
- Suppressing fire and pinning matter in Deathwatch combat. Pinned targets are
  constrained and should be represented as a condition when simulation or
  mission play needs battlefield pressure.
- Righteous Fury is a special high-damage/exploding-damage style event on
  successful attacks. The simulator currently records "critical hit"/hero
  moments for high DoS, while the roller has damage dice mechanics; be explicit
  if changing this behavior to match a stricter table interpretation.

### Weapons, Damage, Armour, And Qualities

- Weapon data often appears as compact strings like range, RoF, damage,
  penetration, clip, reload, and qualities. Prefer structured parsing helpers
  over ad hoc display-only splitting when adding behavior.
- Damage expressions use dice notation such as `1d10+9`, `2d10+2`, or
  `3d10+4`, with a damage type marker in source data (`E`, `I`, `R`, `X`, etc.)
  for energy, impact, rending, explosive, and related categories.
- Important weapon qualities represented in app data include Tearing, Reliable,
  Accurate, Blast, Flame, Power Field, Razor Sharp, Unbalanced, Unwieldy,
  Volatile, Scatter, Storm, Smoke, Toxic, and others.
- Tearing usually means roll extra damage dice and keep the better result for
  supported dice. Proven sets a floor on supported damage dice. Only apply
  qualities that the code explicitly models.
- Armour can be whole-body or location-based. Bestiary and armoury entries may
  use different shapes for protection values, so normalize before using in
  combat calculations.
- Penetration should reduce armour effectiveness, not Toughness, unless a local
  helper intentionally abstracts damage.

### Missions, Requisition, Renown, And Economy

- Deathwatch missions are framed around a kill-team selecting mission gear,
  entering an operation, resolving scenes, earning XP/renown, and returning
  equipment.
- Requisition Points are mission equipment budget, not ordinary money. The shop
  stores purchases as gear and deducts `tabInfo.rp`.
- Renown gates access to more prestigious or restricted equipment. The app rank
  order is `None`, `Respected`, `Distinguished`, `Famed`, `Hero`; keep that
  order consistent in shop and player-management flows.
- Mission scenes in this app can include objectives, complications, checks,
  enemies, combat state, reveal state, extra challenges, rewards, and ending
  text.
- Good mission UI should separate GM-only information from player-safe scene
  information. The backend `playerScene()` projection strips hidden/GM fields.

### Skills And Scene Checks

- Useful scene-check skills from the local skill data include Awareness,
  Command, Tactics, Tech-Use, Medicae, Scrutiny, Search, Logic, Inquiry,
  Intimidate, Demolition, Tracking, Survival, Security, Psyniscience, and
  relevant Lore skills.
- Tactics is an Intelligence skill and should be used for battlefield doctrine,
  priority targets, ambush reads, deployments, or tactical approach. It should
  not automatically grant Squad Mode abilities or Cohesion benefits unless a
  mission rule explicitly says so.
- Awareness/Search/Tracking are good for threat and clue discovery.
- Tech-Use/Security/Demolition are good for machines, locks, traps, explosives,
  and sabotage.
- Medicae/Chem-Use are good for injuries, toxins, infection, biology, and field
  treatment.
- Command/Charm/Intimidate/Scrutiny/Deceive support social or morale scenes.
- Puzzle scenes should usually produce multiple checks with assigned best-fit
  players, visible targets, complications on high DoF, and rewards on success.

### Cohesion, Squad Mode, Solo Mode, And Fate

- Cohesion is the kill-team resource around coordination and Squad Mode.
  Existing app coverage is mostly reference/rules-search level; do not invent
  persistent Cohesion behavior without adding explicit state and tests.
- Squad Mode and Solo Mode are distinct from ordinary skill checks. A successful
  Tactics or Command roll should not silently toggle Squad Mode.
- Fate points can be spent to improve outcomes. In this app:
  - The roller exposes re-roll, +10, and +DoS style fate actions.
  - `useFate.js` reads and persists fate through player endpoints.
  - The simulator may spend fate once on severe failed checks.
- Keep fate changes persistent and visible because players track remaining fate
  across scenes/sessions.

### Psychic Powers And Warp Risk

- Psychic rules involve Focus Power tests, Psy Rating, and risk modes such as
  safer/fettered use versus riskier high-output use.
- Psychic Phenomena and Perils of the Warp are significant consequences. If
  adding psychic automation, model risk and output separately and expose the
  roll trail clearly.
- Avoid treating psychic powers as generic skills unless the feature explicitly
  asks for a simplified helper.

### Hordes And Large Enemy Groups

- Hordes are a special combat abstraction for many lesser enemies. They should
  not be treated as a single normal NPC unless the app is intentionally using a
  simplified profile.
- Horde-relevant UI should track magnitude/remaining threat, area effects,
  full-auto/multi-hit attacks, Blast, Flame, and morale/breaking behavior.
- Current simulation uses simplified enemy lists and wounds, not a full horde
  engine.

### Bestiary And Enemy Modeling

- Bestiary entries vary by source and import quality. Normalize names, profile
  stats, wounds, armour, movement, talents, traits, and attacks before using
  them in roller/simulation logic.
- Common enemy themes in app code include Tyranids, Chaos, Xenos/Tau, Orks, and
  servitor/Imperial profiles.
- For combat simulation, prefer clear approximations and visible findings over
  opaque precision. If a stat is missing, surface the fallback rather than
  pretending it came from the book.

### Simulation Design Guidance

- Simulation is a balance/design tool, not an authoritative rules engine.
- Keep simulated rolls categorized. Current categories include:
  - `combat` for attacks and combat-scene tactical checks.
  - `puzzle` for non-combat scene checks.
  - `fear` for fear tests.
- Reports should show overall success %, combat %, puzzle/check %, XP, rounds,
  roll count, scene results, player report cards, ammo/fate use, conditions,
  initiative events, and findings.
- Findings are scripted threshold notes, not AI. They do not use OpenAI/Ollama
  tokens unless a future feature explicitly calls an AI endpoint.
- Story hooks are scripted from mission scene data. Mission generation stores a
  GM-only `storyHook`/`secret` on each scene; simulation reports which checks or
  combat outcomes revealed, partially revealed, or escalated those hooks.
- Scripted findings should give the GM actionable mission edits grounded in the
  rules model: reduce or increase enemy pressure, add cover/Aim/Dodge/Parry
  prompts, add non-attack combat objectives, tune puzzle difficulty, allow
  assists/alternate skills, prevent single-roll clue bottlenecks, add recovery
  beats for conditions/fear, and match player roles to checks.
- Evaluate combat and puzzle/skill pressure separately. A good sim report
  should make it clear whether the mission is too lethal, too easy, too
  attack-only, too clue-blocked, or mismatched to the party's stats.
- Because simulation uses `Math.random()` and is unseeded, never make exact
  roll totals a test expectation. Test shape, categories, persistence, and
  visible report fields instead.

### Copyright And Wording

- The repo contains local summaries, OCR, PDFs, and extracted data for gameplay
  support. When writing UI/help/docs, use short summaries in original wording.
- Do not reproduce long rulebook sections, tables, or copyrighted passages in
  new docs or UI. Prefer links/search into local rule entries and cite source
  title/page metadata where helpful.
- If a requested feature needs exact rules text, build a lookup/display path
  from the existing rules database instead of embedding copied text in code.

## API Surface

Mounted API prefixes in `database/server.js` include:

- `/api/players`
- `/api/sessions`
- `/api/shop`
- `/api/rules`
- `/api/weapons`
- `/api/bestiary`
- `/api/rules/staging`
- `/api/missions`
- `/api/simulations`
- `/api/errors`
- `/api/gmkit/list`
- `/api/gmkit/upload`
- `/api/copy-bestiary`
- `/api/narrate`

Static serving includes `/gmkit`, `/weapon-images`, and `/avatars`.

## Files To Treat Carefully

- Do not commit or churn runtime logs such as `database/backend.log` or
  `database/server.log`.
- Do not overwrite database files, backups, generated JSON, PDFs, OCR output, or
  imported rulebook data unless the user specifically asks for data work.
- `public/` and `database/` contain generated/reference JSON used by the app;
  update paired files intentionally when scripts require it.
- There may be unrelated local changes. Inspect `git status --short` before
  editing and avoid reverting user work.

## Documentation Notes

- `README.md` and `DEVELOPMENT_GUIDE.md` are useful, but several docs under
  `docs/` and `CLAUDE.md` mention an older split `backend/` + `frontend/`
  structure. The current live structure is root CRA frontend plus
  `database/server.js` backend.
- When updating docs, prefer correcting stale structure references rather than
  propagating them.
