N5/D1c: the finale — !expedition start epilogue

Closes the Hollow King arc with a solo one-shot boss fight, reached via
`!expedition start epilogue` (intercepted before the zone/loadout
machinery). Unlock: all 24 journal pages found + both Tier-5 zones cleared.

- Encounter: runZoneCombat + renderBossOutcome, the arena's own pattern —
  no supplies, no walk, no party, no new mechanics. The stat block is
  Belaxath's (the abyss boss whose gate "lets one thing come home")
  re-dressed as the King returned in full, HP ×1.25 for a capstone; the
  ability/AC/CR carry over unchanged.
- Reward-once: first clear grants a unique title ("Kingsbane") + one
  Legendary + a games-room notice; later clears are a flavour-only
  rematch. The flag is a dedicated player_meta.epilogue_cleared column,
  latched by an atomic UPDATE (never the bulk save) so the monotonic
  false→true can't be clobbered — same discipline as D1a's journal
  bitmask. A loss costs a death, as any boss fight does.
- Title is written after a fresh character reload so runZoneCombat's
  post-fight HP persist isn't overwritten (the survivalist-milestone
  gotcha).

Golden byte-identical; go test ./... green.

Claude-Session: https://claude.ai/code/session_017mEwUmmS7aQTP2NQXj6rUa
This commit is contained in:
prosolis
2026-07-10 15:37:53 -07:00
parent aab7a7bad0
commit 9b6c1ff9a4
6 changed files with 290 additions and 1 deletions

View File

@@ -386,6 +386,13 @@ func runMigrations(d *sql.DB) error {
// character save. DEFAULT 0 == "no pages found", correct for every
// pre-existing row, so no bootstrap backfill.
`ALTER TABLE player_meta ADD COLUMN journal_pages INTEGER NOT NULL DEFAULT 0`,
// N5/D1c the Hollow King finale (gogobee_engagement_plan.md §D1). Set once,
// the first time a player closes the account — the reward (unique title +
// one Legendary) drops only on that first clear; later rematches are
// flavour-only. Written by a dedicated atomic UPDATE, never the bulk
// character save, so a monotonic false→true flag can't be clobbered.
// DEFAULT 0 correct for every pre-existing row, so no bootstrap.
`ALTER TABLE player_meta ADD COLUMN epilogue_cleared INTEGER NOT NULL DEFAULT 0`,
}
for _, stmt := range columnMigrations {
if _, err := d.Exec(stmt); err != nil {