First slice of the N5 story layer. Adds a 24-page serialized campaign
threaded through the zones as collectible journal pages.
- Storage: one journal_pages INTEGER bitmask on player_meta (bit i ==
page i+1). DEFAULT 0 is correct for every existing row, so no bootstrap.
Grants are an atomic bitwise-OR (INSERT ... ON CONFLICT DO UPDATE SET
journal_pages = journal_pages | excluded.journal_pages) so a page found
mid-expedition can't be lost to a stale character save. Journal pages
are therefore grant-only + overlay-read, never in the bulk upsert.
- Drop seam: elite kills roll a page (22%, tunable) in dropZoneLoot,
gated on isElite — bosses get epilogues (D1b), not pages. Not on
SimulateCombat's path, so TestCombatCharacterization is byte-identical.
- Viewer: !adventure journal renders found pages in story order with runs
of missing pages collapsed to a single "…".
- Catalog + bitmask helpers + render live in the new
adventure_flavor_campaign.go; the pages are in-world found artifacts,
not TwinBee's voice.
Golden byte-identical; go test ./... green (incl. a real-DB round-trip
that pins the atomic OR + overlay read).
Claude-Session: https://claude.ai/code/session_017mEwUmmS7aQTP2NQXj6rUa