# Bestiary Sanitization — Progress Report _Last updated: 2026-07-02 (session in progress)_ ## Why this exists The extracted bestiary was badly corrupted: distinct creatures shared mis-aligned statblocks (e.g. Civilian / Servo-Skull / Imperial Agent all got the Hive Tyrant's line — T78 / W120), and several NPCs stored **Strength/Toughness bonuses** (single digits) where the characteristic should be. This report tracks the repair. ## Effective data source (important) - Runtime reads **`public/deathwatch-bestiary-extracted.json`** (34 entries). `loadBestiaryData()` tries a MariaDB `bestiary` table first, but **that table does not exist**, so it falls back to this JSON. Fixing the JSON fixes the app. - `database/deathwatch-bestiary-extracted.json` is a **stale 24-entry subset**, not used at runtime. (Left mostly alone; safe to regenerate from public later.) - Sources of truth for stats: the rulebook PDFs in `database/rules/` (`CR.pdf` = Core Rulebook, `MoX.pdf` = Mark of the Xenos), read with `pdftotext` (raw mode linearizes statblocks; `-layout -f P -l P` per page pairs name→profile). Plus `database/remote-extract/all_deathwatch_statblocks.json` (5 NPCs). ## DONE ✅ ### Roller code fixes (src/components/DeathwatchRoller.jsx, SkillRoller.jsx) - GM "Roll as" any player/enemy; players added to Target/defender list. - Fate now loads for the acting-as player (`fateSubject`). - Player self-load reads lower-case chars + `{total,current}` wounds (was broken). - Enemy 0/again wounds coerced robustly (strings, 0, missing → usable tracker). - `/api/bestiary/enemies` now returns the **full** bestiary (filter relaxed) and `transformBestiaryEntry` coerces wounds + honours `stats.toughnessBonus`. - Fire-mode +10/+20, jam 96/94/100, Advanced-untrained skill gate, Dodge/Parry burst negation = DoS (earlier in session). Full CRA build compiles. ### Bestiary data - `scripts/sanitize-bestiary-canonical.js` written and **run** against `public/…json`. Backups written (`*.pre-sanitize..json`). - Result on the 34 public entries: - **PDF-verified (9):** Chaos Space Marine, Chaos Heretic, Gun Drone, Hive Tyrant, Hormagaunt, Tau Commander, Tau Fire Warrior, Tau Stealth Suit, Tyranid Warrior. - **Canonical-fill, review (13):** Termagant, Renegade Militia, Imperial Guardsman, Daemon Prince, Ork Nob, Battle/Monotask/Industrial Servitor, Servo-Skull, Civilian, Chapter Serf, Imperial Agent, Imperial Guard Field/Senior Officer. - **OCR-restored (5):** caele Rylus Darkscourge, Father Marius, Infected Auran warrior, Feynir Surbryte, Arcus Dyre. - **Structural-only, review (8):** the TEP named NPCs (Agamorr, Skarjaw, Vex, Genestealer Patriarch, Zephon, Chaos Cultist Champion, Prince-Prefect Allewis, Alexei Drahj — S/T bonus→char fixed). - Each touched entry records `stats.statSource` ('pdf'|'canon'|'ocr'|'structural') and `stats.needsReview`, and an effective `stats.toughnessBonus` (includes Unnatural Toughness, e.g. Tyranid Warrior 10, Hive Tyrant 15, Genestealer 8). ## PDF-verified statblocks (authoritative, WS BS S T Ag Int Per WP Fel | W | armour) ``` Chaos Space Marine 50 50 65 45 40 35 40 43 17 | 29 | Power Armour H8/A8/B10/L8 CR363 Tyranid Warrior 55 30 60 50 44 20 35 50 – | 48 | Reinforced Chitin All8 CR370 Hive Tyrant 78 33 60 53 45 45 49 70 – | 120 | Bonded Exoskeleton All10 CR371 Hormagaunt 45 20 35 30 55 10 40 30 – | 9 | Chitinous Carapace All3 CR372 Tau Fire Warrior 25 35 30 30 25 30 20 30 30 | 12 | Fire Warrior Armour All6 CR369 Tau Stealth Suit 30 42 42 47 38 30 40 40 40 | 30 | XV15 H8/B8/A7/L7 CR368 Gun Drone 20 25 20 40 40 15 25 20 10 | 15 | (machine) CR368 Chaos Heretic 35 35 35 35 30 20 30 35 20 | 10 | Flak All4 CR361 Tau Commander 41 57 50 57 48 45 45 50 50 | 90 | XV-8 Crisis Suit All9 MoX Genestealer 55 0 45 40 50 20 40 40 10 | 16 | Reinforced Chitin All4 MoX44 Apostle Bellephrades75 60 66 45 48 48 48 75 30 | 80 | Armour of Chaos All12 CR363 ``` ## DONE ✅ (continued) - **Sanitizer now also ADDS missing canonical creatures** and mirrors public→DB copy. Public + database JSON are both **38 entries** and identical. New entries added: **Genestealer, Ork Nob, Apostle Bellephrades, Industrial Servitor**. - **Bestiary UI redesign** (`src/components/BestiaryTab.jsx`): colour-coded stat chips (WS/BS/S/T/Ag/Int/Per/WP/Fel each with its own colour), tier bar + badge (Troops/Elite/Master by wounds), Wounds + TB pills, `verified`/`review` badges from `statSource`/`needsReview`, and smart armour rendering (`{all:8}`→"All 8", location objects → "Head 8 · Body 10 · …"). Build compiles clean. - **Long-entry cleanup**: `scripts/sanitize-bestiary-canonical.js` now strips raw `pageText`/`snippet` extraction text, removes copied root-level spillover from canonical entries, and trims OCR/structural `skills`/`talents`/`traits`/`gear` at section boundaries. Public + database JSON remain identical, 38 entries, with no rendered text field over 300 chars. - **40k RPG Tools index staging**: `scripts/import-40krpgtools-bestiary-index.js` downloads the public master bestiary index from `https://www.40krpgtools.com/bestiary/` and writes Deathwatch-only missing references to `database/remote-extract/40krpgtools-bestiary-index.json`. This is an index only (name/type/affiliation/book/page/source URL), not statblocks, so it does not write directly into the live bestiary. - **DB-backed live bestiary**: `database/mariadb.js` now creates the `bestiary` table and exposes replace/upsert helpers. `scripts/import-bestiary-json-to-db.js` imports the sanitized active bestiary into MariaDB. Current DB import: **84 rows**, **57 PDF-verified**, all 84 are roller-ready because `/api/bestiary/enemies` reads the same `bestiary` table before falling back to JSON. - **40k RPG Tools hydration**: `scripts/hydrate-bestiary-from-40krpgtools.js` uses the staged index as a page map and local PDFs as the stat source. First Mark of the Xenos pass added **46** compact statblocks to the active bestiary. The remaining index-only rows stay out of the live combat table until a real profile and wounds can be extracted. - **First Founding pass**: hydrated **4** additional compact statblocks from the local `FF.pdf`: The Headsman of Cellebos, Blight-Master Ussax, Atum Sum, and Warsmith Koros. Re-imported MariaDB afterward; current live DB count is **88** bestiary rows. Verified these four through `/api/bestiary/enemies` with valid Wounds/TB/armour output for the combat roller. ## TODO / NEXT (in priority order) 1. **More NEW creatures**: the PDF scan found ~40 more clean profiles whose names need pairing via per-page `-layout` extraction (Ork Boy, Broadside XV-88, Kroot Carnivore, Bloodletters/greater daemons, more Tyranids). Extend the `CANON` table in `scripts/sanitize-bestiary-canonical.js` (auto-adds if missing). The 40k RPG Tools staging file currently has **344 Deathwatch index rows not already in the active bestiary**; use it to prioritize local PDF page extraction. Do not import index-only rows into the live `bestiary` table because live bestiary rows automatically appear in the combat roller. 2. Re-verify canonical-fill entries against PDFs (Termagant, Ork Nob, Daemon Prince, servitors, officers) to promote them from `needsReview` to `pdf` (verified). 3. **ComfyUI (192.168.1.96)** image generation for players/beasts — user asked; feasible. Plan: backend `POST /api/imagegen` proxy → ComfyUI `/prompt` API with a stored workflow JSON, poll `/history/{id}`, save PNG under `/avatars` or a new `/beast-images`; UI "Generate portrait" buttons on PlayerTab + BestiaryTab. NOT STARTED (needs the ComfyUI workflow JSON + confirmation the box is reachable). ## How to continue - Re-run sanitizer: `node scripts/sanitize-bestiary-canonical.js` (idempotent; writes fresh backups each run). - Extract a creature page: `pdftotext -layout -f -l database/rules/CR.pdf -` (or MoX.pdf). Profile = the 9-number row; Wounds/Armour/Weapons lines follow. - The DoS/roller conventions and the jsdom-segfault test workarounds are in project memory (`~/.claude/projects/-nas-git-dwroller/memory/`). ``` ```