# 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`. - 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.