Commit Graph

370 Commits

Author SHA1 Message Date
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
cc6fb7dd39 Adv 2.0 L4f-prep: flip DisplayName readers to loadDisplayName
Swap all char.DisplayName / c.DisplayName reader sites in
internal/plugin/ to loadDisplayName(userID). Touches 12 files
across combat (combat_bridge, dnd_zone_combat, dnd_zone_cmd,
dnd_expedition_combat), arena, hospital, events, render,
masterwork, robbie, scheduler, and adventure.go.

Only intentional char.DisplayName references remaining are in
adventure_character.go (scan + save + dual-write) and the
player_meta.go doc comment. go vet + go test ./internal/plugin/...
clean.

Unblocks deferred L2 step 9 (arena AdvCharacter import drop)
once L4 hospital/pets/render also lands.

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
d300008812 Adv 2.0 Phase L3: delete co-op dungeons; refund in-flight runs on startup
Drop the coop_dungeon system per migration plan §5. 11 coop_*.go files
removed; cleanup_l3.go added with an idempotent per-run status-guarded
UPDATE that cancels any open/active runs and refunds member contributions
plus unsettled bets via EuroPlugin.Credit. Tables retained for now;
schema purge deferred to a future GOGOBEE_COOP_PURGE pass.

adventure.go drops !coop dispatch, coopTicker, the startup combat-lock
hook, and the !coop help line. adventure_scheduler.go loses the
activeCoopMemberSet skip branch and the post-reset combat-lock call.
adventure_render.go drops the teaser plus the in-coop status block;
replaced with a one-week morning-DM closure announcement
(TODO: remove after 2026-05-16). TestPickCoopTeaserCandidate_SkipsLeader
removed.

go vet ./... and go test ./... clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
3b4dfa44d3 Adv 2.0 L2: rip legacy arena combat path; boss flow is the only path
Cancels the planned ARENA_BOSS_FLOW soak. The boss flow is shipped
unconditionally with no legacy fallback — boss-flow errors now surface
to the player and abort the round rather than silently falling back to
the old CombatPower path.

Deleted:
- runArenaCombat (combat_bridge.go) — legacy CombatPower-vs-Lethality
  arena combat driver.
- RenderCombatLogArena, renderArenaOutcome (combat_narrative.go) — legacy
  arena renderers. RenderCombatLogArena was a thin alias for
  RenderCombatLog; renderArenaOutcome was already dead.
- renderArenaCombatFinalMessage (combat_bridge.go) — legacy "rewards +
  closer" trailing text.
- arenaWinCloser, arenaLoseCloser (adventure_arena_combat.go) — flavor
  helpers that only fed the deleted renderers. File now just holds
  arenaParticipationXP.
- arenaBossFlowEnabled + the ARENA_BOSS_FLOW env var (adventure_arena.go,
  .env.example). The env gate served only the now-removed fallback.
- sendArenaCombatMessages (adventure_arena.go) — wrapper that switched
  pacing between boss flow and legacy. Replaced with direct
  sendZoneCombatMessages calls at the three call sites.
- TestArenaBossFlowEnabled (bossflow test) and
  TestRenderCombatLogArena_ProducesPhaseMessages (narrative test).

Simplified resolveArenaRound / resolveArenaSurvival / resolveArenaDeath:
the bossNarr-vs-nil branches collapsed since narration is now always
produced by resolveArenaBoss. Equipment degradation and the death-save
reprieve hook are unchanged — both already worked off the boss-flow
CombatResult.

Migration doc (§4) updated to record the cancellation and the
remaining tuning approach (playtest-driven, no flag soak window).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
eeb96c4b07 Adv 2.0 Phase L2 step 8: arena test cleanup + bossflow win assertion
Two cleanup edits to adventure_arena_test.go:
- TestRenderArenaStreakEntry_ShowsMultipliers: Level 50 → 20 (D&D cap).
- TestRenderArenaStreakEntry_HighLevel_AllEligible: stale "Level 70"
  comment retargeted to Level 20.
- TestArenaStreakSimulation_FullRun: rename combatLevel → skillBonus
  (the value flows into arenaRoundReward as the battleSkill arg, not
  a character level — naming was a CombatLevel-era leftover).

New test in adventure_arena_bossflow_test.go:
TestArenaBossOutcome_WinSurfacesStagedLogAndBossDeath drives
renderBossOutcome directly with a forced-win CombatResult so the
assertion is RNG-free, then checks the win path emits a TwinBee
BossDeath line before the victory headline plus the trailing d20 roll
summary. RenderCombatLog is exercised on the same result to confirm
the staged-log phases caller in resolveArenaBoss returns content.

go vet ./... + go test ./... clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
aea307e418 Adv 2.0 Phase L2 step 7: arena tier gate → DnDCharacter.Level
- Rebracket arenaTiers MinLevel to D&D scale (1/4/8/13/18) so arena
  tiers gate on DnDCharacter.Level instead of legacy CombatLevel.
- Auto-advance level gate + reward skill bonus now read Level via a
  new arenaDnDLevelOrZero(userID) helper.
- Delete dead arenaDeathChance (combat engine owns death now) and its
  six tests; drop the math import from the test file.
- Update streak-entry / level-gate tests to use D&D-scale levels.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
8afd9494fe Adv 2.0 Phase L2 step 6: arena render reads → DnDCharacter
renderArenaStreakEntry now takes *DnDCharacter and reads c.Level
instead of AdventureCharacter.CombatLevel. renderArenaStatus drops
its unused *AdventureCharacter arg. Callers in handleArenaMenu /
handleArenaStatus updated; menu path uses ensureCharForDnDCmd to
fetch (or auto-migrate) the sheet. Tests switched accordingly.

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
prosolis
693cd9c221 Adv 2.0 Phase L2 step 4b: clarify Misty repair comment
runZoneCombat already calls npcRepairMostDamaged internally when the
Misty buff is active and the 20% chance hits — boss flow inherits that
behavior automatically. The comment in resolveArenaRound previously
implied the boss flow skipped Misty repairs, which was wrong.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
6862a94c8f Adv 2.0 Phase L2 step 4b: wire ARENA_BOSS_FLOW into round resolver
resolveArenaRound now branches on arenaBossFlowEnabled(): when set, the
round runs through resolveArenaBoss (zone-boss combat + staged narration)
and the resulting intro/phases/outcome are threaded into resolveArenaSurvival
and resolveArenaDeath via a new arenaBossNarration carrier. Both handlers
swap their RenderCombatLogArena + dnd opening/closing/roll-summary stack
for the pre-rendered boss-flow narration; arena economic glue (rewards,
tier-clear, helmet drops, hospital ad) is preserved untouched.

A new sendArenaCombatMessages helper picks 2–3s zone pacing under boss
flow and 5–8s arena pacing under the legacy path. bossFlowPhaseMessages
prepends the intro line ahead of phases, mirroring streamOrSend's
intro+phases pattern from dnd_zone_cmd.go.

Tests: TestArenaBossFlowEnabled covers the env-gate parsing;
TestBossFlowPhaseMessages asserts the staged-narration assembly. Full
suite green with the flag both off (default, legacy path) and on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
315909e6fe Adv 2.0 Phase L2 step 4a: resolveArenaBoss helper
Adds resolveArenaBoss(userID, ArenaBossEncounter) — a single-round
combat path that routes through runZoneCombat + renderBossOutcome so
arena fights surface the same staged narration zone bosses use:
intro line, RenderCombatLog phases, Nat20/Nat1 mood lines, phase-two
barb on T3+ (ZoneArena routing), BossDeath/PlayerDeath flavor, dice
summary, arena-styled headline.

Behind ARENA_BOSS_FLOW env var (arenaBossFlowEnabled). Side-effect
free — no ArenaRun mutation, no payout, no history rows. The dispatch
into resolveArenaRound lands in step 4b once the surrounding economic
glue (rewards, achievements, helmet drops, death flag) is plumbed for
the new combat result shape.

Smoke tests cover (a) end-to-end arena round produces non-empty
intro/phases/outcome with the right headline, (b) bad tier/round
returns an error, (c) phase-two thresholds match the design doc,
(d) every tier×round arenaBosses entry has positive stats.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
3fe2d2f0ca Adv 2.0 Phase L2 step 3: arena boss-shaped bestiary
Reshapes the legacy ArenaMonster data into a boss-shaped arenaBosses
map of DnDMonsterTemplate entries — the carrier the upcoming
resolveArenaBoss (step 4) will hand to runZoneCombat + renderBossOutcome.

IDs namespaced "arena_t<tier>_r<round>" so they don't collide with
dndBestiary. HP/AC/Attack scale with tier via arenaTierBaseStats;
within a tier, BaseLethality biases each monster by a ±30% band so
round-1 anchors the bottom of the tier and round-4 the top. Stats are
first-pass — final tuning happens during the ARENA_BOSS_FLOW flag soak.

arenaBossPhaseTwoAt(tier) gates phase-two narration: T1–T2 = none,
T3+ = 50% HP. The T4–T5 flavor barb piggybacks on the existing
bossPhaseTwoPool path through ZoneArena.

No callers yet — wiring lands in step 4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
b0738e8e0a Adv 2.0 Phase L2 step 2: ZoneArena synthetic ID
Adds ZoneArena to the ZoneID enum so the upcoming arena boss path can
route renderBossOutcome's twinBeeLine calls without inventing a parallel
flavor system. Synthetic by design — never registered via registerZone,
so !zone enter arena is unreachable and allZones() / registry tests are
unaffected.

Mood pools (Nat20/Nat1/BossDeath/PlayerDeath) are already zone-agnostic
in pickPool, so ZoneArena falls through to the generic defaults at zero
cost. Phase-two and boss-entry pools default to nil/generic too —
arena-specific overrides land alongside the bestiary work in step 3
only if existing flavor doesn't read right.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
62eed7a064 Adv 2.0 Phase L2 step 1: extract renderBossOutcome
Factors the staged-narration body of resolveBossRoom into a shared
helper renderBossOutcome(BossOutcomeInputs) so the upcoming arena
boss flow (resolveArenaBoss) can reuse the same Nat20/Nat1 mood lines,
phase-two transition barb, BossDeath/PlayerDeath flavor, and trailing
roll-summary line.

Inputs include caller-supplied DefeatHeadline / VictoryHeadline so
arena and zone read in their own voice ("Run ended." vs the future
arena equivalent). Helper is pure: side effects (abandonZoneRun, loot
drops, threat ticks, kill records) stay at the call site.

No behavior change for zone bosses — same strings, same ordering.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
dfa7beeb96 Adv 2.0 D&D polish: GM→DM rename, streamed zone combat, end-to-end scenario test
GM→DM rename across docs and code (GMNarrationType→DMNarrationType,
GMState→DMState, narration constants, comments) so the system reads as
"Dungeon Master" everywhere. Player-visible "GM mood" wording stays
where it appears in flavor.

Streamed zone/expedition combat: zone advance now stages patrol →
patrol play-by-play → patrol resolution → room intro → room play-by-play
→ final outcome through sendZoneCombatMessages with 2–3s pacing
(arena keeps its 5–8s window). Combat narrative lines pick up a compact
d20-vs-AC roll annotation for hit/crit/miss/block events.

Combat outcome polish: dndHPSnapshot lets narration show sheet HP
rather than legacy combat-engine HP, and markAdventureDead clears the
zombie state where hp_current was 0 but the legacy alive flag stayed
true after a D&D-layer KO.

Adv 2.0 announcement (ADVENTURE_2.0_ANNOUNCEMENT.md), README rewrite
covering the new layer, and adv2_scenario_test.go — a full
zone-run + expedition + harvest playthrough against a copy of the prod
DB asserting persisted state end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
83a71173b1 Adv 2.0 D&D Phase R6 polish: standalone-zone harvest persistence
Adds a dnd_zone_run.harvest_nodes_json column and a small persistence
layer (loadStandaloneHarvestNodes / saveStandaloneHarvestNodes) so
!zone enter runs that aren't tied to an expedition can carry per-room
HarvestNode state. Expedition runs continue to use
expedition.region_state for the same data — this is the casual flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
1953eec3b5 Adv 2.0 Phase L1: Babysit pivot + legacy resolver retire
Babysit pivots from harvest service to pet-care + safe-rest perk:
- runBabysitDaily/runAutoBabysitDay/runBabysitAction deleted; replaced
  by runBabysitDailyTrickle (3 pet XP/day while subscribed).
- BabysitSafeRest predicate promotes Standard camps to Fortified-tier
  rest (HP+1d6, threat -5, resources refresh) and reduces wandering
  monster check campMod from 0 to -4. Hooks live in
  dnd_expedition_camp.go and dnd_expedition_night.go.
- !adventure babysit auto/focus subcommands removed; status/purchase
  DMs reflect the new perks.

Scheduler drops the auto-babysit fallback block (no more silent
debits + harvest-day for missed logins). Babysit subscribers still
skip the morning DM; trickle runs in the background.

TwinBee daily simulator self-contained: simulateTwinBeeOutcome +
simulateTwinBeeLoot replace the resolveAdvAction call against a
fake CombatLevel=35 character. Outcome distribution and reward
shape preserved; gold-share to active players unchanged.

Legacy activity resolver retired: resolveAdvAction,
advEligibleLocations, AdvEligibleLocation, resolveAdvEmptyOutcome
deleted from adventure_activities.go (zero production callers
post-babysit/twinbee). AdvActionResult kept — combat_bridge still
produces it for arena/dungeon flows; dies in L2/L3.

Migration plan doc gogobee_legacy_migration.md added: five-phase
L1-L5 plan covering arena (Adv 2.0 boss flow rebuild), co-op
deletion, perk-gate sweep, and final teardown of AdventureCharacter
+ CombatLevel + combat_engine.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
45863bd5f3 Adv 2.0 D&D Phase R7: post-completion audit hardening
High-priority fixes from the multi-agent audit of Adventure 2.0:

- Phase 11 nil-deref cluster: zoneOrFallback() helper replaces 8 unsafe
  getZone(_) sites in dnd_zone_cmd.go. Corrupted zone IDs render a
  placeholder instead of panicking.
- Briefing/recap idempotency: deliverBriefing/deliverRecap now claim
  the rollover via a conditional UPDATE. Double-fires from clock skew
  or restart become no-ops; supply burn and day++ no longer reapply.
- Graceful ticker shutdown: AdventurePlugin gains stopCh + Stop(); all
  11 background tickers now select on stopCh in addition to ticker.C.
- Mood decay (§3.2): math.Round(elapsed*2) replaces int() truncation
  so sub-hour gaps decay correctly.
- 24h auto-abandon (§4.3): getActiveZoneRun returns clean slate and
  abandons stale runs whose LastActionAt is over 24h old.
- Respec / auto-migrate orphan cleanup: !respec and the auto-migrated
  draft wipe now abandon active zone runs and expeditions before
  deleting the dnd_character row.
- Phase R combat-link: applyBossDefeatThreat now wired from
  resolveBossRoom (-20 threat); applyRoomCombatThreatForUser adds
  +5/+8 from non-boss/elite kills (§8.1).
- Starvation → forced extraction: briefing-time check forces extract
  with §10.2 coin tax when supplies hit zero.
- GMNat20/Nat1 narration wired into resolveCombatRoom and
  resolveBossRoom (nat-20 takes precedence over nat-1).
- Treasure-undo race: LoadAndDelete on both timer-fire and `undo`
  paths so only one side wins.
- Battle Master: Disarming, Menacing, Parry maneuvers added (3 → 6
  of 10). Remaining 4 (Pushing, Goading, Riposte, Commander's Strike)
  documented inline as needing ally/reaction mechanics the engine
  doesn't model.
- Threat-70 warning: tracked in RegionState["siege_warning_fired"]
  so a drop-and-recross doesn't re-fire the beat.
- region_state JSON decode error now logged via slog.Warn instead
  of silently discarded.

Failing TestProdDB_DnDLayer fixed via option (a): track migrated
characters and only run round-trip / idempotency assertions on those,
skipping pre-existing prod-DB rows accumulated from live bot use.

New tests in dnd_audit_phase_R7_test.go cover: 24h auto-abandon,
briefing double-fire idempotency, threat-70 warning idempotency,
multi-region extract→resume state preservation, and starvation
forced-extraction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
8a1d9a16ce Adv 2.0 D&D Phase R R1: Legacy activity-loop deprecation
Retires the standalone !adventure dungeon/mine/forage/fish daily loop in
favor of !expedition. Gate intercepts legacy 1/2/3/4 + word-form input
and DMs a deprecation notice; town services (shop/blacksmith/rest/thom)
stay on !adventure.

- Delete dead resolveActivity (269 lines) + parseActivityLocation; add
  isLegacyActivityInput + renderLegacyActivityDeprecation.
- Morning-DM menu rewritten to point at !expedition and the !forage etc.
  harvest commands; writeAdvLocationLines removed (sole caller).
- New dnd_r1_migration.go runs archiveOrphanZoneRuns at Init: archives
  any active dnd_zone_run not linked to an active expedition (via
  exp.run_id or region_state.region_runs). Idempotent.
- Pre-existing flake in TestPickEveningRecap_BossOverridesAll fixed
  (E6c pool added entries without literal "boss"; widen substring set).
- Internal helpers (resolveAdvAction, advEligibleLocations, AdvLocation
  tier data, consumable drops) preserved — babysit/scheduler still use
  them passively.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
c3083637ac Adv 2.0 D&D Phase R R6: Zone-condition harvest polish
Wires the §3 zone-note interactions that were called out across the
resource doc but left for the polish pass.

Harvest gates (dnd_expedition_harvest.go):
- zoneConditionHarvestBlock — Underforge Heat 10 blocks !mine, Dragon's
  Lair infernax_awake blocks all harvesting, Abyss Instability 81+
  blocks all harvesting.
- zoneConditionHarvestDCMod — Underforge Heat 7-9 → +3 !mine DC,
  Dragon's Lair Awareness Pulse 3+ (day 9+) → +4 all DCs, Abyss 61-80
  → +5 all DCs, Feywild Double-Day → -3 !forage DC. Delta + reason
  shown inline on the dice line.
- restoreHarvestNodesInRoom — Time Loop hook (in-memory only; cycle
  flow persists downstream).
- Manor !scavenge: 10% Cursed Trinket secondary drop with one-time
  TwinBee warning gated by RegionState["manor_cursed_warned"].

Time Loop wiring (dnd_expedition_temporal.go):
- feywildTemporalPostRollover Loop branch now restores nodes in the
  current room and appends a re-emergence narration line.

Drow Poison Blade recipe (dnd_economy.go):
- ThomCraftRecipe gains BladeWeaponCount; pickBladeWeapons matches
  Type=="weapon" + blade keyword (sword/dagger/blade/scimitar/etc.).
- !craft list shows the blade slot with current inventory count.
- Recipe consumes 1× Drow Poison + 1× any blade weapon → Drow Poison
  Blade (sleep-on-crit for 3 combats, effect TODO).

Tests (dnd_zone_conditions_test.go, all DB-free): block branches per
zone, DC delta tables, manor cursed-trinket gate at 10% boundary,
restoreHarvestNodesInRoom in-memory roundtrip, isBladeWeapon /
pickBladeWeapons coverage + shortfall + exclude-already-consumed,
drow_poison_blade recipe shape.

Flavor reuse only — no new flavor file. Reasons-strings on the dice
line and the cursed-trinket warning are inline (Thom/TwinBee voices
in existing pools didn't fit harvest-blocking or warding contexts).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
c170adaf05 Adv 2.0 D&D Phase R R3-R5: Combat-link, zone loot, fishing, economy
R3 — Combat Event Integration:
- dnd_expedition_combat.go: Combat Interrupt rolls (§4.2) with
  threat-clock and Ranger-wilderness modifiers; Patrol Encounters
  scaled by threat level; recordZoneKill writer with monsterKillTags.
- Interrupt gate at head of handleHarvestCmd; patrol gate before
  resolveRoom in zoneCmdAdvance; kill writer wired into combat-win
  paths.

R4a — Zone Loot Tables:
- dnd_zone_loot.go: §5 loot drop tables for all 10 zones × 5 tiers
  with §8.1 sell-value bands. dropTierFromCR brackets + boss floor.
- Hooks on combat-win in resolveCombatRoom, resolveBossRoom,
  runHarvestInterrupt, tryPatrolEncounter.

R4b — Fishing Integration:
- dnd_zone_fish.go: fishingZones allow-list, fishingSkillBonus,
  rangerRareCatchUpgrade (§6.2), feywildFishDistortion narration.
- §6.1 fish entries added to resource registry for Forest, Sunken
  Temple, Underdark, Feywild zones.
- !fish wired through handleHarvestCmd; zoneItemFlavor matrix
  populated for all 10 zones × all loot items.

R5 — Economy Integration:
- dnd_economy.go: !sell (post-expedition gate, single CHA Persuasion
  DC 17 → +15% bump), !craft (§8.2 4 exemplar recipes), !lore
  (INT/Arcana DC 15 recipe discovery).
- dnd_known_recipe table for persistent recipe discovery.

Flavor reuse: HarvestInterrupt, PatrolEncounter, CombatVictory,
PlayerDeath, LootDrop*, FeywildTimeDistortion* — all existing pools.
No new flavor file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
83cdf07374 Adv 2.0 D&D Phase R R2: Harvest nodes & per-room registry
Per-zone resource registry (§3, all 10 zones) and full per-room harvest
layer wired into expeditions: !forage / !mine / !scavenge / !essence /
!commune / !resources. Auto-spawns a DungeonRun per region on
!expedition start, swaps it on !region travel, retires on
abandon/extract. Long rest at camp replenishes nodes across every
room and region. Reuses existing flavor.Harvest* / RichYield /
NodeDepleted / per-zone Harvest<Zone> pools.

Also clears two pre-existing test failures introduced by the E6c
flavor expansion (briefing substring list + combat-lift trial count).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
cb51c56f00 Adv 2.0 D&D Phase 12 E6c: Expedition flavor topup
Expand under-populated TwinBee expedition pools (singletons → 3, pairs →
4) for: morning briefings (Day1/3/14/21), evening recaps (boss/close-call/
quiet), all four threat clock bands, every zone temporal event narration,
supply depletion, and every milestone narration. Add MilestoneCartographer
and MilestoneSurvivalist pools (spec §13). Wire MilestoneSurvivalist into
AwardCompletionMilestones (was inline string).

All additions in TwinBee voice, no existing entries altered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
cbb2670bb8 Adv 2.0 D&D Phase 12 E6b: Expedition milestones (§13)
Daily milestones fire from the morning briefing once the day count
crosses the threshold (CurrentDay reflects the new day post-rollover):
- First Night (day 2, +50 XP)
- Week One (day 8, +200 XP, Thom Krooke discount flag — hookup deferred)
- Two Weeks (day 15, +500 XP, +1 primary stat — char-system hookup deferred)

Completion milestones via AwardCompletionMilestones, exported for the
combat-link boss-kill path to call once status flips to 'complete':
- Patient Zero (max threat ≤ 50, +10% of XPEarned bonus)
- Survivalist (tier ≥ 3, no forced extraction ever, title flag)
- Long Game (tier 5, legendary item — loot-grant hookup deferred)

Reuses MilestoneFirstNight/WeekOne/TwoWeeks/PatientZero/TheLongGame
flavor pools from twinbee_expedition_flavor.go. Cartographer and
Survivalist have no flavor pool (Survivalist uses an inline string,
Cartographer is fully deferred since it needs combat-link search hooks).

Awarded keys persist as RegionState["milestones"]. Max threat sampled
daily into RegionState["max_threat_seen"] before milestone checks
read it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
157455a3fe Adv 2.0 D&D Phase 12 E6a: Multi-region expedition map (!map)
Adds renderRegionChain — for tier 4-5 multi-region zones, renders the
4-region progression (e.g. ST──DO──IW──TDT for Underdark) with ✓
cleared / ▶ here / · pending markers, and  overlay where a base
camp has been pitched. Single-region zones skip the chain and fall
through to the existing renderZoneMap room layout.

!map (top-level) and !expedition map both invoke handleExpeditionMapCmd
which composes: region chain → current region label → per-run room
map → legend. Region abbreviations are decoded in a "Regions:" footer
so the glyph row stays compact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
d179ca27a7 Adv 2.0 D&D Phase 12 E5: Extraction & Resume
Voluntary !extract burns 1 day, flips status to 'extracting', keeps
all rewards, resumable for 7 real days. Forced extraction (Abyss
collapse, future HP-0/supply-0 hooks) flips to 'abandoned' and applies
the §10.2 20% coin tax via the cycle layer's euro handle. !resume
within the 7-day window repurchases supplies and restores threat &
temporal stacks as-is; expired rows demote to failed.

getActiveExpedition / briefing / recap loaders narrow to status='active'
so 'extracting' rows don't get phantom day rollovers. Reuses
flavor.ExtractionVoluntary, ExtractionForced, ExpeditionResume from
twinbee_expedition_flavor.go.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
a199efd773 Adv 2.0 D&D Phase 12 E4d: Base camp as persistent waypoint
Replaces the deferred-branch rejection in handleCampCmd: !camp base
now pitches when (a) the zone is multi-region, (b) the player's
current region's BaseCampSite is true, and (c) the region boss is
cleared. Otherwise the surface returns specific reasons (wrong zone,
wrong site, boss not down).

First pitch records the region in RegionState["base_camps"] via
addRegionListEntry — that list is what !region renders the  marker
from, and the waypoint is persistent for the rest of the expedition
even after the camp is broken. Reuses the existing
flavor.BaseCampEstablished pool with [N] day interpolation.

Tests cover non-base-site rejection, uncleared-region rejection, and
the happy path through pitch + waypoint persistence (3 SU cost,
HasBaseCampAt true after).
2026-05-09 14:25:22 -07:00
prosolis
e392cf7d8f Adv 2.0 D&D Phase 12 E4c: !region command + inter-region travel
Spec §11.3. New !region command surface:
  !region          — list regions w/ status (▶ current, ✓ cleared,
                     · visited, ★ zone boss,  base camp)
  !region travel   — move to next region in order

Travel burns one day of supplies via applyDailyBurn (so harsh / siege
multipliers stack normally), advances current_day +1, fires one
transit wandering check (resolveTransitWanderingCheck — same buckets
as the night check but campMod = 0 since you're not bedded down),
then writes region-transition narration on both ends.

Two new flavor pools (RegionTransitDeparture / RegionTransitArrival)
with [REGION_NEXT] interpolation. Travel rejected when camped, and
when already in the final (zone-boss) region.

Tests cover the campMod=0 invariant, the day/supply/region delta on
travel, and the marker rendering in renderRegionList.
2026-05-09 14:25:22 -07:00
prosolis
c2bed4282d Adv 2.0 D&D Phase 12 E4b: region progression hooks + status display
Multi-region zones now default CurrentRegion to the entry region at
startExpedition; that region is also written to regions_visited so
the visited/cleared distinction is meaningful from Day 1.

MarkRegionVisited / MarkRegionBossDefeated expose the combat-link
hook surface. MarkRegionBossDefeated also flips Expedition.BossDefeated
when the cleared region's IsZoneBoss is set, since the zone-boss kill
is the same event from §7's POV (suppresses temporal accumulation,
kills further awareness pulses, etc.). setCurrentRegion is the
companion writer used by E4c's travel command.

!expedition status now shows the region line ("🗺 Region: Drow
Outpost (2/4)") with a ✓ marker when the region boss is down.
2026-05-09 14:25:22 -07:00
prosolis
9484dae146 Adv 2.0 D&D Phase 12 E4a: Region data model + zone registry
Spec: gogobee_expedition_system.md §11. Tier 4–5 zones (Underdark,
Dragon's Lair, Abyss Portal) split into 4 regions each. ExpeditionRegion
struct + per-zone registry; CurrentRegion(e) defaults to Order==1 when
unset; RegionState helpers persist regions_cleared / regions_visited /
base_camps as []string lists. Single-region zones return nil.

Tests cover registry shape (4 per zone, last is IsZoneBoss), lookups,
default current region, and round-trip persistence through DB.
2026-05-09 14:25:22 -07:00
prosolis
10d398fd9a Adv 2.0 D&D Phase 12 E3f: Abyss Portal destabilization
§7.6 instability accumulator: +5/day (cap 100), persisted via
TemporalStack. Bands:
  • 0–20   normal
  • 21–40  mild (-1 WIS)
  • 41–60  reality warps (rooms shift order)
  • 61–80  demon surges (12h wandering)
  • 81–99  unraveling — forces 2× supply burn override + -2 all rolls
  • 100    collapse — forced extraction (status = failed)

Pre-burn override fires at 81–99 so the supply doubling lands the same
day the band activates. Collapse at 100 calls completeExpedition
with ExpeditionStatusFailed and emits the AbyssPortalCollapse line.

Adds ReduceAbyssInstability(e, n) for the §7.6 -10 per major story
objective; combat-link wires the call when objectives complete.
Combat-side knobs (WIS/all-roll penalties, room-shift, surge cadence)
are exposed via AbyssInstabilityBandFor — combat-link reads on tick.

Reuses flavor.AbyssPortalDestabilizationMid / Critical / Collapse per
feedback_reuse_existing_flavor.

Closes Phase 12 E3 (Zone Temporal Events). Next session = E4
(Multi-Region Zones).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
ff7d55f33b Adv 2.0 D&D Phase 12 E3e: Dragon's Lair awareness pulses
§7.5 awareness pulses on Day 3, 6, 9, 12, ... apply +10 threat at
briefing rollover and log a temporal entry (DragonsLairAwarenessPulse
flavor). Day-14 awakening sets RegionState["infernax_awake"]=true
once, with the §7.5 awakening narration; the pulse line is suppressed
that day so the awakening narration carries the moment alone.

Combat-side knobs (kobold patrols +1 enemy per active room, 6h
wandering cadence, dragon-encounter weighting in the wandering table,
forced extraction pressure once awake) are exposed via the
infernax_awake region flag — combat-link phase reads it to switch
behavior. Boss-defeated suppresses entirely.

Reuses flavor.DragonsLairAwarenessPulse / DragonsLairAwakenWarning per
feedback_reuse_existing_flavor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
3a000ada8c Adv 2.0 D&D Phase 12 E3d: Feywild Crossing time distortion
§7.4 daily 1d6 distortion roll fires at briefing time:
  • 1–2 normal day (no override)
  • 3–4 half-day → 0.5× burn multiplier override
  • 5    double-day → 2.0× burn + extra wandering check at recap
  • 6    time loop → narration only; combat-link reads on next room

Refactors applyZoneTemporalPreBurn to return TemporalBurnOverride
(Multiplier-based) instead of a force-double bool. Sunken Temple's
Day-6 tidal piggybacks on the same override pipeline (2.0×) instead
of the siege-flag hack.

Persists the day's distortion bucket to RegionState["feywild_today"]
so the recap path can fire the §7.4 extra wandering check on
double-day.

Reuses flavor.FeywildTimeDistortionHalf / Double / Loop per
feedback_reuse_existing_flavor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
044baddcce Adv 2.0 D&D Phase 12 E3c: Underforge heat accumulation
§7.3 heat stacks: +1/day in the Underforge (cap 10), persisted via
new updateTemporalStack helper. Band thresholds:
  • 1–3 ambient (no narration)
  • 4–6 warning band (combat fire damage applies, narration logged)
  • 7–9 supply band (forces harsh-conditions burn = +50%, narration)
  • 10 critical (exhaustion narration, log entry)

Fortified/base camp long rest in the Underforge drops heat by 2
(updates briefing's "Fortified rest" summary line), per §7.3.

Replaces the placeholder `TemporalStack > 0 → harsh` predicate in
deliverBriefing with zoneTemporalHarsh(), which is now per-zone aware
— Heat 1–3 in the Underforge is correctly flavor-only and no longer
prematurely doubles supply burn from Day 1.

Combat-side knobs (+1d4 fire round-start damage at heat 4–6, dis CON
saves at 7–9, -2 to all rolls at 10) are exposed via heat band but
not yet applied — combat-link phase reads heat band off the active
expedition.

Reuses flavor.UnderforgHeapWarning / UnderforgHeapCritical per
feedback_reuse_existing_flavor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
9d4085f4c3 Adv 2.0 D&D Phase 12 E3b: Haunted Manor nightly reset
§7.2 cycle: every 3rd day's morning briefing in Blackspire fires the
"manor reset" temporal log — non-boss rooms respawn one enemy each.
Boss-defeated suppresses. Wrong-zone is a no-op.

Exposes ManorReset(e, day) bool for the combat-link phase to read at
!advance time when applying the actual respawn (per the deferral
pattern from E2b's wandering-encounter combat).

Reuses flavor.HauntedManorResetMorning per feedback_reuse_existing_flavor.

Deferred: Night-2 quiet warning narration on the recap path — needs
a new hook in deliverRecap; not load-bearing for the reset itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
41c60617db Adv 2.0 D&D Phase 12 E3a: Sunken Temple tidal event
Adds the §7.1 tidal event hook: warnings on Days 4 and 5, peak event
on Day 6 with forced 2× supply burn (overrides tier-2 HarshMod 1.5×),
silenced if the boss has been defeated.

Wires zone-temporal events into deliverBriefing as two new hook points
— pre-burn (mutates burn multiplier) and post-rollover (appends
flavor-bearing log entries). Future E3b–E3f layer onto the same hooks.

Reuses the prewritten flavor.SunkenTempleTidalWarning / TidalEvent
pools per feedback_reuse_existing_flavor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
9cb66d46ac Adv 2.0 D&D Phase 12 E2e: !threat command
§14 surfaces the threat clock as a first-class command. !threat shows
level/100, current band, the per-band combat & supply effects (built
from ThreatBandInfo so display tracks any future band re-tuning), and
the last 5 ThreatEvent log entries with deltas + reasons. Adds a
"siege is active" / "past 70" footer when those gates have triggered.

Active-enemy camp guard and close-call evening recap stay deferred —
both need combat state inside an expedition, which doesn't exist yet
(expeditions still don't host their own combat loop). They land in the
combat-link phase along with the §8.1 combat-driven threat modifiers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
f844068660 Adv 2.0 D&D Phase 12 E2d: Fortified Camp branch + overnight rest effects
§5.1 fortified camp unlocked in !camp, gated on exp.BossDefeated for
now (cache-site waypoints land in E3+). +2 SU cost was already in the
E1e cost table; this commit just stops rejecting the type.

§3 / §5.1 / §8.1 overnight rest applied at briefing rollover via
processOvernightCamp:
  - rough: HP floor of HPMax/2 (no spell refresh)
  - standard: full HP, exhaustion -1, refreshAllResources +
    refreshSpellSlots
  - fortified / base: standard rest + 1d6 HP bonus on wake +
    threat-clock -5 (§8.1 modifier)

The camp auto-breaks once the rest applies — staying camped multi-day
isn't a thing in the spec; the player can re-pitch on the next night
if they want to. Briefing body now includes a 💤 line summarizing the
HP/threat delta from the night's rest.

Combat-driven side of fortified ('+1d6 HP on wake' as a rare flavor
event) currently surfaces as the rest summary; a peaceful-night TwinBee
narration variant could come later if we add a dedicated pool — for
now reusing the existing CampEstablished ambience suffices.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
5b8ef740f8 Adv 2.0 D&D Phase 12 E2c: Siege Mode effects + threat-70 warning
§8.3 siege economics:
- applyDailyBurn now takes an explicit siege flag and enforces a 2× floor
  on supply burn even when HarshMod is below 2 (tier-1 zones still get
  starved out per spec).
- currentBurn mirrors the same precedence so status/briefing readouts
  stay consistent.
- Briefing rollover passes e.SiegeMode through, decoupling siege from
  the harsh-conditions composite.

§8.3 threat-70 warning: applyDailyThreatDrift emits a one-time
appendApproachingSiegeLog when the level crosses 70 (prevLevel<70 and
new level≥70). Pulls from a new flavor.ThreatClockApproachingSiege pool
seeded with the spec's verbatim warning beat plus two voice-matched
alternates.

Other siege effects (boss +20 HP / Legendary Resistance, cleared-room
respawn, no-short-rest enforcement) stay deferred to the combat-link
phase — ThreatBandInfo already exposes the flags that engine will read.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
9b48dda79e Adv 2.0 D&D Phase 12 E2b: Wandering monster table + night phase
§6 night-phase resolution. Fires once per recap when the player has an
active camp. resolveWanderingCheck rolls 1d20 + threat mod (+1 per full
10 above 30) + camp mod (rough +3, fortified -4, base -6) + class mod
(ranger -2 in wilderness zones), then buckets per §6.1 into Peaceful /
Passage / Minor / Standard / Elite / Ambush.

Encounter side picks a roster entry from the zone (weighted by
SpawnWeight, biased to elite for Elite/Ambush) and persists the result
to camp.NightEvents + a "night"-typed log entry. Signs-of-passage adds
+2 threat per spec. Combat resolution itself is deferred — expeditions
don't host their own combat loop yet — so encounter outcomes log as
pending and surface in the recap with an "encounter pending" hint;
the combat-link phase will read these on next !advance.

Flavor reuse: encounter narration pulls from the existing ThreatClock
{Stirring,Alert,Hostile,Siege} pools, matched to the current band so
voice tracks the dungeon's awareness level.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
b520b0ce2d Adv 2.0 D&D Phase 12 E2a: Threat Clock state machine
Threshold model on top of the E1a persistence: ThreatBand bracketing
(Quiet/Stirring/Alert/Hostile/Siege) with per-band combat/supply/rest
knobs (ThreatBandInfo). Daily threat drift (+3/day, GMMood-modded:
effusive→-3 elated, hostile→+5 wrathful) wired into the 06:00 briefing
rollover; no-op once the boss is down.

Threshold crossings emit a flavor-bearing log entry pulling from the
prewritten flavor.ThreatClock{Stirring,Alert,Hostile,Siege} pools.
applyBossDefeatThreat is the -20 hook for the eventual combat-completion
wiring (combat→expedition link is a later phase).

Combat-driven modifiers (loud-ability, escape, combat-in-new-room) are
deferred to the same combat-link phase since expeditions don't currently
host their own combat path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
0a3417a341 Adv 2.0 D&D Phase 12 E1e: !camp rough/standard + placement guards
Wires the basic camp surface per gogobee_expedition_system.md §5. Two
camp types ship: rough (any location, +0.5 SU, partial rest) and
standard (cleared room, +1 SU, full long rest). Fortified and base
camps remain explicitly rejected at the surface — those wire up in E2
and E4 respectively, so the command tree is forward-compatible.

Placement validation per §5.2: boss rooms and trap rooms reject; non-
cleared rooms downgrade an intended standard camp to rough. Active-
enemy detection is deferred to E2 (it needs the combat-state hooks
that don't exist on the expedition path yet). Until !advance is wired
into the expedition layer, the player is treated as standing at the
entry room (always cleared), so today's validation effectively only
fires once room context catches up.

Camp-pitch consumes SU immediately and uses flavor.CampEstablished for
the narrative line. !camp break struck and recorded. !camp with no
arg shows help + current camp state.

Tests cover deduction amounts, double-pitch rejection, break, locked-
type rejection (fortified/base), insufficient-supply rejection, and
boss-room placement guard via a synthetic zone run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
7528d5c239 Adv 2.0 D&D Phase 12 E1d: real-time day cycle (06:00 briefing / 21:00 recap)
Two new tickers in adventure.go startup. Each minute, when UTC clock hits
the trigger hour, walks every active expedition whose last_briefing_at /
last_recap_at is stale relative to today's threshold and fires it. Per-
expedition gating (rather than global db.JobCompleted) means a player who
started mid-day still gets their first briefing the next morning.

Briefing: applies one day's supply burn (harsh mult kicks in when threat
> 60 / siege / temporal stack > 0), advances current_day by one, picks
flavor.MorningBriefing<DayBand> (Day1/Day3/Day7/Day14/Day21 → otherwise
Generic), DMs the §12.1 block, appends a 'briefing' log entry, stamps
last_briefing_at.

Recap: scans today's log entries — boss kill picks
flavor.EveningRecapBossKilled, no combat + no narrative entries picks
EveningRecapNothingHappened, otherwise EveningRecapGeneric. DMs the
§12.2 block (HP-aware close-call detection deferred to E2 since it
needs combat hooks). Stamps last_recap_at.

Tests cover day-band selection, day advance + supply burn (incl. harsh
mult), threshold filtering, recap log entry creation, and boss-kill
override.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
1042e2ab71 Adv 2.0 D&D Phase 12 E1c: !expedition start/status/log/abandon
Command surface for the multi-day expedition layer. Wired in
adventure.go alongside !zone. Subcommands: list (delegates to zone
list), start <zone> [Ns] [Md], status, log, abandon, help. Default
outfit is one standard pack; pack tokens parse as e.g. "2s 1d".

start debits coins via p.euro.Debit, refunds on persistence failure,
and blocks if a !zone single-session run is also active. Status
renders the §12.1 daily briefing block with HP, supplies (current
burn factors in harsh conditions / siege), threat threshold label,
and depletion warning if rationing or worse. log shows the last 5
ExpeditionEntry rows. abandon flips status to 'abandoned'.

Start uses the prewritten flavor.ExpeditionStart pool for the
narrative coda and writes it to the log so !expedition log shows it
back. Tests cover parser combinations (1s default, 3s 1d, error
paths), threshold band edges, the start→status→log→abandon roundtrip,
and the underfunded-start no-debit guarantee.

Day cycle (E1d) and !camp (E1e) wire on top in subsequent commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
2413f892e5 Adv 2.0 D&D Phase 12 E1b: supply system (procurement, burn, depletion)
Pure logic per gogobee_expedition_system.md §4. Tier→base SU/day and
harsh-conditions multiplier tables (§4.1). Standard pack 10 SU/50c
(max 3) and deluxe pack 20 SU/90c (max 1) per §4.2. SupplyPurchase
struct with Validate / Total / Cost. Depletion brackets (>25 / 10–25
/ 1–9 / 0) → roll modifier and long-rest gate per §4.3. applyDailyBurn
deducts and clamps; resets the per-day forage flag.

Procurement is exposed as a pure helper here; the !expedition start
flow wires it interactively in E1c.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
9608ce554a Adv 2.0 D&D Phase 12 E1a: Expedition data model + persistence
Lays the multi-day expedition substrate per gogobee_expedition_system.md
§4.4/§5.3/§8.4/§9. Schema dnd_expedition + dnd_expedition_log added in
db.go. Expedition struct, ExpeditionSupplies, CampState, ThreatEvent
shipped in dnd_expedition.go with start/get/scan/abandon/complete,
updateSupplies, updateCamp, applyThreatDelta (clamped + sticky siege),
advanceExpeditionDay, append/recent log helpers. Round-trip + concurrent
rejection + threat clamp/siege + log ordering covered.

E1b (supply procurement) and E1c (commands) wire on top in subsequent
commits; day cycle (E1d) and camp commands (E1e) follow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:22 -07:00
prosolis
c5defc9b34 Adv 2.0 D&D Phase 11 D11: GMNarrationType coverage test
Lock in the D6 audit conclusion that every GMNarrationType routes to a
non-empty pool for every registered zone. New zones or types added
without wiring their pool now fail TestNarrationCoverage_AllZonesAllTypes
instead of silently rendering "".

Audit confirmed all 12 types × 10 zones already covered (per-zone pools
where they exist, generic fallback otherwise) — no flavor written.

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