The old assertions targeted nc.log, which the component never renders.
Updated to assert against the rendered lastDetail panel, and use fake
timers to handle the setTimeout-deferred turn advancement.
- src/components/DeathwatchRoller.jsx: +1495 lines, core roller
component with expanded dice mechanics, character sheets, and
mission tracking
- src/components/BestiaryTab.jsx: +158 lines, enemy bestiary with
stat blocks and encounter builder
- src/components/MissionTab.jsx: +43 lines, mission briefing and
objective tracking
- src/App.js: route updates for new tabs
- database/routes/missionRoutes.js: backend mission state routes
- src/tests/: new test files for roller reset, bestiary, mission,
player management, requisition shop, and full mission playthrough
Players can spend a fate point to re-roll, add +10, or add a degree of
success to their most recent roll. Spends are persisted to the player's
sheet via a new atomic POST /api/players/:name/spend-fate endpoint
(read-modify-write that preserves password and other tabInfo).
- rollHelpers.applyFateToTest: shared recompute for a test result
- useFate hook: loads current/total fate, spends + persists
- FateControls: shared current/total display + Re-roll/+10/+DoS buttons
- SkillRoller and the combat roller each track their last roll and
replace it in history when fate is spent; combat also recomputes hits.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Remove the redundant 'Attacker (from enemies)' dropdown and handler; the
single Enemy selector remains as the target/defender profile.
- Parse the explicit single/semi/full RoF triplet (e.g. '-/-/6') from armoury
stats so weapon modes/RoF come from data: the Heavy Bolter is now full-auto
only at RoF 6. Bare numeric triplets defer to the class heuristic to avoid
range-string collisions. Weapon selection already auto-fills damage/pen/rof/
mode/tearing via computeWeaponDefaults.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract the skill-check UI/logic into a standalone SkillRoller component
and move shared dice helpers (d100, degrees, uid) into rollHelpers.js.
DeathwatchRoller now hosts the shared roll history + player sheet and a
Combat/Skill tab toggle; the skill-only characteristic/training helpers
moved into SkillRoller. Behaviour unchanged, including first-skill
preselect (now done inside SkillRoller) and the manual target override.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Allow setting an explicit target number to roll against instead of the
computed Per+training+mod. Works with no sheet skill selected (e.g. a
GM-set 'roll vs 43'). Empty field keeps the auto-computed behaviour.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Delete 17 one-off migration/admin scripts that imported the removed
./sqlite-db module. None are referenced by the app, package.json, or CI;
they broke at the SQLite->MariaDB migration. No live code imports
sqlite-db anymore.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sessions are served from MariaDB via sessionModel.js + routes/sessionRoutes.
These files were orphaned by the SQLite->MariaDB migration:
- requireSession.js: unused (no importers), still required ./sqlite-db
- healthcheck.js: required removed ./sqlite-db; would crash every run
- server-healthcheck.yml: scheduled the broken healthcheck every 15 min
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
healthcheck.js only opens the local committed SQLite DB on the runner
and never contacts the deployed server, so the every-15-min schedule
validated nothing and consumed Actions minutes. Keep workflow_dispatch
for manual runs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Dedicated 'Run unit tests' step (npm run test:unit) so unit tests are
visible and guaranteed in CI rather than implied by the build script.
- Build now calls react-scripts build directly, avoiding a redundant
second Jest run (CI=true still fails the build on lint warnings).
- Bump Node 18 (EOL) -> 20 and enable npm dependency caching.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wrap fetchReports in useCallback so the useEffect dependency array
satisfies react-hooks/exhaustive-deps. CI treats build warnings as
errors (CI=true), so this lint warning was failing the build step.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add 👁/🔒 toggle to combat row in Scene Secrets — players see
combat panel only when GM reveals it (scene.reveal.combat).
Stop flickering: background polls now run silent (no setLoading),
and state updates are skipped when JSON content is unchanged,
preventing unnecessary re-renders every 4s.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add amber banner at top of Mission Tab showing next session date
(Lørdag 28. juni 2026). Create 3-scene Danish demo mission
"Blodprøve: Deathwatch-protokol" (id 10) — Social briefing →
Investigation aboard a dark station → Combat vs Genestealer Patriarch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace single scene reveal toggle with individual unlock buttons for
title, mood, description, objectives, complications, and each check —
GM controls exactly what players see and when.
Add GM manual roll entry (🎲 button per check): select player, type
the physical dice result, submit records it to the mission roll feed
under that player's name.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Mood text field on scenes shown as amber italic read-aloud block
above scene description (gated by revealed flag for players)
- Scene editor includes Mood / Read-aloud textarea
- Scene editor moved full-width below two-column grid
- Mission "The Hunt for Fabius Bile" fully translated to Danish
("Jagten på Fabius Bile") with Danish names, descriptions,
objectives, complications, checks and mood text for all 10 scenes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Edit Scene button in Scene Secrets header opens an inline editor card
below Table State. Lets the GM edit scene title, type, description,
checks (name/skill/char/target/modifier/reward, add/remove), enemies
(name/wounds/BS/WS/damage/AP, add/remove), objectives, and
complications — all saved live via the existing patchScene path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 tiers selectable in Sim Lab config panel. Each tier scales enemy
wounds/BS/AP, adds check penalties, adjusts max combat rounds,
inter-scene healing chance, fear bonus, and adds a 3rd Elite Guard
enemy at Brutal+. Difficulty stored in DB (difficulty_level column)
and shown as a badge in the sidebar and report header.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- Add `simulations` table to MariaDB (mission, players, result, XP,
roll feed, player cards, scene log, findings — all persisted as JSON)
- simulationHelpers: save / getAll / getById / delete
- simulationRoutes.js: POST /api/simulations (run engine + save),
GET /api/simulations (list), GET /:id (full report), DELETE /:id
- Simulation engine: d100 dice, fear tests, combat rounds with
initiative, conditions, damage, fate re-rolls, skill checks,
auto-assign checks to best-stat player, enemy pools from scene data,
per-player report cards with grade A-D, hero moments, shame list
Frontend:
- SimulationTab.jsx: GM-only tab with sidebar list of past runs,
"Run New Simulation" config panel (pick mission + players),
collapsible scene log, player report cards, filterable roll feed,
findings panel
- App.js: import SimulationTab, add "⚙ Sim Lab" nav button (GM only)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Full standalone Node.js simulation of 'The Hunt for Fabius Bile'.
Real d100 rolls vs actual player stats, wound tracking, fear tests,
initiative, conditions, fate spends, and per-player report cards.
Run: node src/tests/playthroughRun2.js
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fetch player RP/renown/gear from GET /api/players/:name (public full data)
instead of the name-only player list, so RP is never 0
- Player status bar: RP (amber), renown badge, collapsible gear inventory
- Purchase flow posts to /api/shop/purchase and reads newRp from response
- Affordable filter, renown filter, item cards show affordability state
- GM panel: all-player RP/renown/gear table, Set RP, Set Renown forms
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On load, each player's character sheet is fetched from /api/players/{name}:
- BS and WS set from tabInfo.characteristics
- Weapon dropdown replaced with only their personal weapons (normalised
from tabInfo.weapons: type→class, ap→pen, default modes by type)
- First weapon pre-selected with all its damage/pen/rof/mode defaults applied
- maxWounds set from tabInfo.wounds; curWounds preserved from localStorage
if the player was already tracking mid-session
Active scene conditions (synced from MissionTab via dw:combat-state) now
apply automatic attack modifiers:
- Pinned: –20 BS
- Stunned: –20 BS/WS
- Knocked Down: –20 BS/WS
- Fatigued: –10 BS/WS
- Grappled: –20 WS, ranged/heavy weapons hidden from weapon dropdown
- Bleeding: banner only (no roll modifier)
Condition banner appears above the roller when any condition is active,
showing each condition label and the net modifier to the attack target.
The status bar target display also shows the condition delta inline.
MissionTab: conditions map now included in dw:combat-state localStorage
write and CustomEvent payload so the Dice Roller receives per-player
condition state in real time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All 11 issues from 4-player simulation of The Hunt for Fabius Bile:
1. Roll feed visible to all players (no GM auth guard)
2. Scene text gated by revealed flag; GM explicit reveal per scene
3. GM can add NPC/enemy entries to initiative tracker
4. Round counter synced to Dice Roller via localStorage + custom event
5. Check reward text hidden until player has rolled
6. Checks assignable to specific player via Scene Secrets ⚙ options
7. Fear test quick-roll panel with WP input appears when fearRating > 0
8. Decision checks show textarea/Declare instead of d100 roll button
9. Fate point re-roll: one per check per scene, resets on scene advance
10. Player poll reduced from 8s to 4s, combined mission + roll feed poll
11. Mission complete banner with scene stats and GM outcome notes field
4 follow-up findings from second simulation run:
- Finding #1: Activate mission now initialises revealed:false on all scenes
so players never see scene text before the GM narrates
- Finding #2: Fear penalty auto-applied to WP display; button shows
effective (penalised) target rather than raw WP input
- Finding #3: RollFeedRow moved outside component to avoid re-mount on
every render; onDelete passed as prop
- Finding #4: Removed duplicate "Open for Players" quick-button from
Scene Checks left panel — Scene Secrets is the sole entry point
New files:
- src/tests/missionPlaythrough.test.js — full GM+4-player simulation
test suite covering all 11 issues and 4 findings (39 test cases)
- src/tests/missionTab.test.js — player/GM view isolation tests
- src/utils/diceRoller.js — shared d100/degrees/clampTarget utilities
- tests/missionRoutes.test.js — backend mission route unit tests
- tests/playerRoutesLogin.test.js — player login route tests
- tests/sessionRoutes.test.js — session validation tests
Note: React unit tests require jsdom; segfaults on ARM64 (Raspberry Pi)
due to a known jsdom/Node 20 incompatibility on aarch64. Tests pass on
x86 CI. Backend integration tests (tests/) run normally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>