Commit Graph

23 Commits

Author SHA1 Message Date
prosolis
38a3693832 N7/B2: Renown — prestige past the L20 cap
Overflow XP that grantDnDXP used to drop at L20 now accumulates as Renown
on player_meta.renown_xp (cumulative, atomic +=; renown_level derived as
renown_xp/25000). The reward is prestige-only: a derived rank ladder
(Renowned→…→Eternal), a cosmetic ✦N marker on the sheet and leaderboard,
a games-room shout on rank promotion, and !level progress once capped.

Renown perks are the two combat-neutral economy levers only — +loot / +XP,
capped at a streak-30 grant's economic half (+15% / +20%). combat_stats.go
reads DeathModifier/SuccessBonus/ExceptionalBonus (which map to Defense/
Attack/CritRate) but never LootQuality/XPMultiplier, so renown pays out even
through loadCombatBonuses without moving the golden or the balance corpus.
The plan's "-death penalty" perk is deliberately dropped (it would inflate
Defense).

The overflow→renown conversion and the character save commit in one
transaction (saveDnDCharacterExec now takes an executor), so a crash can
neither drop the overflow nor double-credit it on the next grant.

Schema: renown_xp column, DEFAULT 0 correct for every existing row, no
bootstrap (journal_pages/epilogue_cleared pattern).

Claude-Session: https://claude.ai/code/session_017mEwUmmS7aQTP2NQXj6rUa
2026-07-10 19:48:49 -07:00
prosolis
9b6c1ff9a4 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
2026-07-10 15:37:53 -07:00
prosolis
fd7803b13c N5/D1a: journal pages — the Hollow King campaign collectible
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
2026-07-10 15:20:09 -07:00
prosolis
57a0ea90f2 N4/E1: second pet slot for the Tier-4 Estate
An Established (Tier-4) home can draw a second companion. Both pets show up on
the sheet and the town showcase, level via babysitting, and wear their own
barding (!thom pet2buy <tier>).

Combat: both pets contribute their attack/deflect/whiff procs at half weight —
DerivePlayerStats now averages over the active pets, so a pair reads as roughly
one full pet (flavor-forward, not a stat spike; difficulty-neutral). The average
reduces to identity over a single pet (x/1==x, 0.0+x==x, 3+(2L+1)/2==3+L), and
the engines still roll one proc per channel per round, so the single-pet path
and TestCombatCharacterization golden stay byte-identical. Pinned by
TestDerivePlayerStats_OnePetIsByteIdentical + the golden.

Scope kept deliberately flavor-forward: pet 2 carries identity/level/barding and
the three combat procs only. The morning-defense buff, death/ditch-recovery
save, and the level-10 supply-shop unlock stay pet-1 mechanics — no second
defensive multiplier stacks, and the one-pet path is untouched by construction.

Storage: parallel pet2_* columns on player_meta + Pet2* mirror on
AdventureCharacter (absent == no second pet, DEFAULT '' correct for every
pre-existing row, no backfill — the N2-temper / P4-party precedent). Pet-1 code
paths are untouched. Arrival reuses the chase/feed/type/name DM flow, slot-aware,
gated HouseTier>=4 with an established first pet.

Review fixes folded in (high-effort /code-review, 3 angles): pet-2 barding block
no longer hidden when pet-1 is chased away; showcase tie-break restored
(level desc, then name); petGrantXP collapsed onto the shared level-up helper;
dead-param armor-buy wrappers inlined; pet-2 barding tagged with its own euro
ledger reason.

Claude-Session: https://claude.ai/code/session_017mEwUmmS7aQTP2NQXj6rUa
2026-07-10 15:03:57 -07:00
prosolis
3369d7d8fe gofmt: bring internal/ and cmd/ back to gofmt -l clean
Mechanical `gofmt -w ./internal ./cmd`. Mostly struct-field realignment that
had drifted, plus a few trailing-newline fixes. No behaviour change — gofmt is
semantics-preserving, and build/vet/test are green either side.

Split out from the code-review fixes that follow so those stay reviewable
instead of hiding inside a wall of realignment.
2026-07-10 07:18:07 -07:00
prosolis
f6a457ae84 Pets: fire 25% morning event from expedition briefing + fix defense-buff leak
Recovers the genuinely-missing half of ade0335 (orphaned on the unmerged
phase-H-harvest-charges branch). 3ed2e1d redid pet *arrival* via the
emergence seam and explicitly deferred the morning event; this closes
that gap.

- deliverBriefing now rolls the 25% morning pet event (petMorningEvent,
  already present but only wired into the overworld DM that underground
  players never see) and prepends it to the briefing body, granting the
  one-day PetMorningDefense buff.
- Add resetAllPetMorningDefense + wire it into midnightReset. The buff was
  leaking permanently even on HEAD's existing overworld path — it was set
  on the 25% roll but never cleared. Resets the pet_flags_json key in place.

Deliberately omits ade0335's ArrivalPending machinery: arrival is now the
emergence seam's job (3ed2e1d + 978dc5e + 513cf32), so queuing arrival
from the briefing would double-roll it.
2026-05-21 23:45:57 -07:00
prosolis
5d98e5684a Adv 2.0 post-L5 audit cleanup: drop redundant dual-writes + refresh stale comments
Audit confirmed saveAdvCharacter's fan-out (upsertAllPlayerMetaFromAdvChar)
covers all 13 migrated subsystems, so the per-call-site upsertPlayerMetaXxx
calls retained "as defense" through the L4-L5 migration are redundant
post-L5h. Removed 16 such call sites across arena (2), scheduler (4),
hospital (1), consumables (2), rival (1), housing (11), and reordered
masterwork to drop a now-pointless explicit upsert before the load+save.

Refreshed nine stale doc comments referencing the legacy
adventure_characters table or "soak window / fallback" language that no
longer applies after the L5 close-out (commit 596b2b7). No behavior
change; go vet + go test ./... pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:23 -07:00
prosolis
c0f03a41b5 Adv 2.0 L5 close-out: loader rewire + adventure_characters purge gate
Rewires loadAdvCharacter / createAdvCharacter to source from player_meta,
drops every legacy-table fallback in the loadXxxState helpers, and adds a
GOGOBEE_LEGACY_PURGE=1 gate to drop the now-cold adventure_characters
table. Schema CREATE removed and column migrations tolerate the missing
table so the purge stays sticky across reboots.

§7.4 of the migration plan is reconciled to document that
player_meta.combat_level stays — combat_stats.go consumes it via the
overlay and is shared infrastructure, not legacy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:23 -07:00
prosolis
1c7939c3c8 Adv 2.0 L5h: cold AdvCharacter table — saveAdvCharacter fan-outs to player_meta
saveAdvCharacter no longer writes the legacy adventure_characters row.
It's now a thin fan-out: bumps LastActiveAt and routes through
upsertAllPlayerMetaFromAdvChar, which calls the per-subsystem upserts
for display_name, hospital, arena, masterwork, rival, skills, babysit,
NPC, lifecycle, death, misc, pet, and house.

loadAdvCharacter calls applyPlayerMetaOverlay at the tail to re-source
every migrated subsystem from player_meta. The adventure_characters
SELECT still happens (for the user_id / equipment-table linkage and as
a fallback substrate) but its values are overwritten by the overlay.
This achieves the L4e in-place housing reader flip automatically —
adventure_housing.go's char.HouseFoo reads now see player_meta values
without per-site changes.

npcMidnightReset extended to write player_meta alongside the frozen
adventure_characters.

Per-call-site upsertPlayerMetaXxx duals are now redundant with the
saveAdvCharacter fan-out but kept as defense; cleanup deferred.
player_meta.combat_level column drop deferred — still read by babysit
cost / combat_stats / activities via the overlay; sequenced with the
final combat_engine teardown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:23 -07:00
prosolis
df886ab86f Adv 2.0 L5f: misc fields (Title, TreasuresLocked, CraftsSucceeded) migration
Three player_meta columns (title TEXT, treasures_locked INT,
crafts_succeeded INT). MiscState struct + load/upsert/backfill/
projection helpers. Dual-write rides saveAdvCharacter alongside the L5e
death-state hook — Title is dormant, the other two already mutate at
sites that save.

Tests: TestPlayerMetaMiscStateBackfill_Idempotent,
TestLoadMiscState_FallsBackToAdvCharacter,
TestUpsertPlayerMetaMiscState_RoundTrip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:23 -07:00
prosolis
45586eb697 Adv 2.0 L5e: death state migration off AdvCharacter
Eight player_meta columns: alive (default 1), dead_until,
death_reprieve_last, last_pardon_used (DATETIME), last_death_date,
grudge_location, death_source, death_location (TEXT).

DeathState struct + load/upsert/backfill/projection helpers. Dual-write
strategy switches: instead of per-site upserts, the dual-write rides
saveAdvCharacter itself (after the existing display_name dual-write).
Death-state mutation surface spans ~50 save sites; the saveAdvCharacter-
internal hook catches every one without scatter.

Backfill idempotent (only fills rows where every death field is still
default). Tests cover backfill, fallback, round-trip, and the
saveAdvCharacter dual-write.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
8fc8941cc9 Adv 2.0 L5d: streak/action/lifecycle migration off AdvCharacter
Ten player_meta columns: current_streak, best_streak, last_action_date,
streak_decayed, action_taken_today, holiday_action_taken,
combat_actions_used, harvest_actions_used, created_at, last_active_at.

LifecycleState struct with HasLifecycle() marker + load/upsert/backfill/
projection helpers. New resetAllPlayerMetaDailyActions parallels the
existing resetAllAdvDailyActions for the bulk midnight reset.

createAdvCharacter now seeds created_at/last_active_at (CURRENT_TIMESTAMP)
so player_meta rows are fully formed at creation.

Mutation surface turned out to be small: only `rest` writes
ActionTakenToday (combat/harvest counters are dormant in current code);
plus streak halve + streak update in scheduler. Dual-writes wired at all
four sites + the bulk reset.

Tests: TestPlayerMetaLifecycleStateBackfill_Idempotent,
TestLoadLifecycleState_FallsBackToAdvCharacter,
TestUpsertPlayerMetaLifecycleState_RoundTrip,
TestResetAllPlayerMetaDailyActions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
c83b73b655 Adv 2.0 L5c: NPC counters/debuffs migration off AdvCharacter
Thirteen player_meta columns: misty/arina_last_seen,
misty_buff_expires, misty_debuff_expires, arina_buff_expires (all
nullable DATETIME), npc_msg_count + npc_msg_count_date,
misty/arina_roll_target, misty_encounter_count, misty_donated_count,
thom_animal_line_fired, robbie_visit_count.

NPCState struct with HasNPCActivity() marker + load/upsert/backfill/
projection helpers. Dual-writes wired at every NPC mutation site:
processNPCEncounters msg-count save, npcFireEncounter last-seen,
resolveMisty (insufficient balance, debit failure, buff/donated,
declined-debuff), resolveArina buff, adventure_robbie visit count,
adventure_housing Thom animal line.

Hidden discovery mechanic — buffs/debuffs and counters never surface
in player-facing output.

Tests: TestPlayerMetaNPCStateBackfill_Idempotent,
TestLoadNPCState_FallsBackToAdvCharacter,
TestUpsertPlayerMetaNPCState_RoundTrip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
d5b4834d44 Adv 2.0 L5b: babysit state migration off AdvCharacter to player_meta
Five player_meta columns (babysit_active, babysit_expires_at,
babysit_skill_focus, auto_babysit, auto_babysit_focus). BabysitState
struct with IsActive() marker mirrors SkillState/HouseState shape.
loadBabysitState / upsertPlayerMetaBabysitState /
backfillPlayerMetaBabysitState / babysitStateFromAdvChar helpers.

Dual-writes wired at handleBabysitStart, handleBabysitCancel,
checkBabysitExpiry. Backfill is idempotent — only fills inactive rows
whose legacy counterpart is active.

AutoBabysit / AutoBabysitFocus / BabysitSkillFocus are dormant in
current code but preserved for the schema migration.

Tests: TestPlayerMetaBabysitStateBackfill_Idempotent,
TestLoadBabysitState_FallsBackToAdvCharacter,
TestUpsertPlayerMetaBabysitState_RoundTrip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
79e5d19d23 Adv 2.0 L5a: skills migration off AdvCharacter to player_meta
Eight player_meta columns (combat_level, combat_xp, mining_skill,
mining_xp, foraging_skill, foraging_xp, fishing_skill, fishing_xp).
SkillState struct with HasSkills() marker mirrors PetState/HouseState
shape. loadSkillState / upsertPlayerMetaSkillState /
backfillPlayerMetaSkillState / skillStateFromAdvChar helpers.

Dual-writes wired at every mutation site: consumables craft XP (both
success and failure branches), events.go XP grant across all skills,
arena death + session-complete combat XP. Backfill is idempotent (only
fills rows where every skill column is still zero AND the legacy row
has any non-zero value).

CombatLevel/CombatXP are transitional — dropped at L5g after the DnD
mass-backfill retires the legacy CombatLevel-derived fallback.

Tests: TestPlayerMetaSkillStateBackfill_Idempotent,
TestLoadSkillState_FallsBackToAdvCharacter,
TestUpsertPlayerMetaSkillState_RoundTrip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
81cce0169f Adv 2.0 L4d reader flip: pet helpers off AdvCharacter to PetState
All ten helpers in adventure_pets.go now take PetState (or *PetState
for petGrantXP) instead of *AdventureCharacter. mistyHousingHint takes
raw NPC counters since Misty fields stay on AdvCharacter for a later
phase. mistyReactivatePet returns bool; caller flips both stores.

DeathTransitionParams gained Pet PetState so combat_bridge no longer
touches the DB; arena caller loads PetState. PetState.HasPet() mirrors
AdvCharacter's. petMidnightCheck loads PetState per char.

L4 grep-empty exit criterion now holds for adventure_pets.go.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
d638f62fd4 Adv 2.0 L4e: housing dual-write house_* off AdvCharacter to player_meta
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
690817f2ed Adv 2.0 L4d: pets dual-write pet_* off AdvCharacter to player_meta
Eight pet_* columns added to player_meta (flags packed into pet_flags_json).
PetState + load/upsert/backfill helpers; dual-writes wired at every pet
mutation site (arrival, naming, supply-shop unlock, babysit trickle,
morning defense, misty reactivation). Backfill idempotent, runs on Init.

L4 grep-empty exit criterion intentionally NOT met for adventure_pets.go:
helpers still take *AdventureCharacter because external callers (babysit,
scheduler, npcs, combat_bridge, combat_stats, dnd_sheet, arena, character)
read pet fields directly. Cross-file signature flip belongs after the
dual-write soak.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
283f7adf5f Adv 2.0 L4c: masterwork migrates MasterworkDropsReceived off AdvCharacter
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
a4b4a74ab4 Adv 2.0 L4b: rival migrates RivalPool + gate/stake off AdvCharacter
Gate switches from CombatLevel >= 5 to D&D Level >= 3; stake formula
becomes (level / 3) * 1000 (Level 3 → €1000 mirrors legacy CL5 → €1000,
tops €6000 at L20). RivalPool / RivalUnlockedNotified dual-write to
player_meta; readers in selectRivalPair, handleRivalsCmd, and morning
DM render flip to loadRivalState. Backfill wired into Init, idempotent.

Migration doc note about porting rival combat to simulateCombat was
wrong — rival uses RPS, no combat_engine port needed. Doc corrected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
730f16580a Adv 2.0 L4a: hospital migrates HospitalVisits + cost formula off AdvCharacter
player_meta.hospital_visits column added with idempotent backfill.
Hospital cost now DnDCharacter.Level x 50k (5x before insurance), falling
back to dndLevelFromCombatLevel when no D&D row exists. Revive paths also
restore DnDCharacter HP to full; char.Alive still dual-writes during soak.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
5000c3d696 Adv 2.0 L4f-prep: DisplayName migration scaffold (schema + dual-write + helper)
Adds player_meta.display_name with idempotent backfill, dual-writes from
createAdvCharacter (in-tx) and saveAdvCharacter (post-commit), and a
loadDisplayName helper with AdvCharacter fallback. Readers are not flipped
yet — soak begins now; per-call-site swap follows in a later session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
341b1fc8e5 Adv 2.0 Phase L2 step 5: arena counters → player_meta
Creates the player_meta table (gogobee_legacy_migration.md §2.1) with the
three columns L2 needs: arena_wins, arena_losses, invasion_score. Future
phases ALTER in their own columns. Naming follows the §2.0 callout — no
dnd_ prefix on new tables.

Helpers in internal/plugin/player_meta.go: loadPlayerMeta,
upsertPlayerMetaArena, backfillPlayerMetaArena (INSERT OR IGNORE,
idempotent, logs row count per Phase R1 precedent). Plugin Init runs
the backfill once on startup.

Dual-write per §11: every char.ArenaWins++/ArenaLosses++ in
adventure_arena.go also calls upsertPlayerMetaArena. AdvCharacter
columns stay in place — reads will switch back to AdvCharacter trivially
if a rollback is needed before L5 teardown.

Read swap: handleArenaStats and renderDnDSheet now load player_meta and
prefer its values (falling back to AdvCharacter if the row is missing,
which only matters during the deploy window before backfill runs).
renderArenaPersonalStats's signature changed from (char, stats) to
(displayName, wins, losses, stats) so the render is DB-free.

Tests: TestPlayerMetaArenaBackfill_Idempotent verifies double-run
backfill is a no-op and doesn't clobber post-backfill dual-writes;
TestUpsertPlayerMetaArena_RoundTrip covers insert/update/missing-user
paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00