Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,58 @@ Always read DESIGN.md before making any visual or UI decisions.
All font choices, colors, spacing, and aesthetic direction are defined there
(the "Ley Terminal" system). Do not deviate without explicit user approval.
In QA mode, flag any code that doesn't match DESIGN.md.

## Workflow

- Branch → PR → Cubic AI review → the human maintainer merges. `main` is
protected; never commit to it directly, and never merge PRs yourself.
- **No AI attribution anywhere on this repo**: no generated-with footers in
PRs or issues, no Co-Authored-By/attribution trailers in commits.
- Treat every Cubic finding honestly: reproduce/validate it first. If real,
fix the ROOT CAUSE (the whole bug class, not the flagged instance) and
reply describing what you found; if invalid, explain why with evidence.
Cubic re-reviews on every push — iterate until clean.
- Checkpoint-commit long work (e.g. content transcription) every few units
so partial progress survives.

## Definition of done

- `vp check` and `vp test` clean from the root (root `vp test` runs every
package's Vitest project).
- Rules changes assert against printed book values, with the page cited in
content JSON (`page` = printed page number) and exercised in tests.
- User-visible changes are verified LIVE in the browser (roll vitals, deal
damage, cast, navigate between characters) — tests alone don't count.
Watch for the navigation-state bug class: param routes don't remount, so
per-character state must reset on id change, and async guards need
ownership tokens, not booleans.

## Rules-content fidelity (the heart of the project)

- Every mechanic comes from the rulebook, transcribed from rendered pages —
NEVER from memory of the Rifts rules, which is unreliable on specifics
(printed costs, dice, page numbers). If memory and the page disagree, the
page wins.
- The rulebook PDF lives at `docs/rules/` (gitignored, local-only). It is a
scanned book with NO text layer — text extraction returns garbage. Use
the vision pipeline in docs/rules/TRANSCRIPTION.md, with sections indexed
in docs/rules/PAGE_MAP.md.
- Engine functions return raw book amounts and stay pure and deterministic
(dice rolls and elapsed time are INPUTS). Clamping and validation live at
the write: the backend revalidates every mutation through `deriveSheet`,
so illegal states are unstorable rather than defensively handled.
- When a printed mechanic doesn't fit the schema, extend the schema (with
refinements that reject contradictions at content-load time) instead of
flattening the rule into prose.

## Dev loop

- Local Convex: `pnpm exec convex dev` in packages/backend (anonymous LOCAL
deployment; always `pnpm exec`, never npx). Stop it before pulls — it
rewrites `convex/_generated`. Killing the CLI can orphan
`convex-local-backend` on port 3210 with STALE functions still serving;
kill that process before restarting.
- Web: `vp dev` in apps/web, then verify at localhost:5173.
- Seed test data: `pnpm exec convex run characters:create '{...}'` in
packages/backend (the CLI may crash with a libuv assertion AFTER the
mutation succeeds — a returned id means it worked).
54 changes: 54 additions & 0 deletions docs/rules/TRANSCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Transcribing the rulebook — the vision-extraction pipeline

The source (`docs/rules/Rifts-Ultimate-Edition-Main-Book.pdf`, gitignored and
local-only) is 382 **scanned pages with no text layer**. `pdftotext` yields ~1
garbage character per page — do not attempt text extraction. The proven
pipeline is render-and-read:

## The pipeline

1. **Locate the section** in [PAGE_MAP.md](./PAGE_MAP.md).
Offset (verified): `PDF page index (0-based) = printed page number + 2`.
2. **Render the page range** to PNG at 2.2x — crisp enough for dense stat
blocks and numeric tables:

```bash
python -c "
import fitz
d = fitz.open('docs/rules/Rifts-Ultimate-Edition-Main-Book.pdf')
for i in range(START_IDX, END_IDX):
d[i].get_pixmap(matrix=fitz.Matrix(2.2, 2.2)).save(f'OUT_DIR/p{i}.png')
"
```

(PyMuPDF is installed; `pdftoppm`/poppler is not.)

3. **Read each PNG visually** and transcribe into content JSON:
- keep printed wording for ranges, durations, and notes;
- stamp `page` with the **printed** page number (not the PDF index);
- structured fields (dice, costs) get the structured value, and variable
or conditional printed rules keep the full sentence in a `*Note` field
(`ppeNote`, `savingThrowNote`) beside the number.
4. **Batch per book section** (e.g. one spell level at a time). Merge into
the content file sorted consistently; schemas validate at import via
`.parse`, so a bad transcription fails the whole test suite immediately.
5. **Pin totals in tests** (e.g. per-level spell counts in
`packages/rules/tests/spells.test.ts`) so silent drift or accidental
deletion is caught forever after.
6. **Checkpoint-commit** every few sections; transcription sessions are long
and partial progress should survive anything.

## Rules of the pipeline

- Transcribe only what the render shows. Model memory of Palladium rules is
unreliable on specifics (costs, dice, levels, page numbers) — e.g. Heal
Wounds is printed at level 5, not the commonly-remembered 6. **The page
always wins.**
- Completeness is checked against the book, not the existing catalog: the
spell catalog claimed "levels 1-4" for weeks while holding 5 of 13 level-2
spells. When touching a section, verify the whole section's roster from
the pages.
- When a printed mechanic doesn't fit the current schema, extend the schema
with refinements that reject contradictory shapes at content-load time
(see `spellHealingSchema`'s exclusive/othersOnly/full) rather than
flattening the rule into description prose.
Loading