Commit Graph

5 Commits

Author SHA1 Message Date
prosolis
a063e0ccd0 N3/P6d: a party where every member can see the dungeon
Every ownership lookup in the adventure module keys on a user id, and a
party member owns neither the expedition row nor the zone run. So each
player-facing read quietly told them they were not playing.

Rewire them. Reads a member should see resolve through activeExpeditionFor
/ activeZoneRunFor. Leader-only actions answer with copy that names the
leader instead of denying the expedition. Three busy-guards had to start
refusing a member outright: !zone enter, !expedition start and !sell all
keyed on the sender's own row, so a seated member could open a private
dungeon, outfit a rival expedition, or run a shop from the boss room.

Four things the rewire itself exposed:

!resources looks like a read but seed-persists harvest nodes, and
saveHarvestNodes rewrites the entire region_state blob — kills, event
gates, temporal stack — last-write-wins. Reaching it as a member would
revert the leader's walk from a stale snapshot. Persist only for the owner;
seedRoomNodes is pure, so a member re-derives the same nodes.

!zone taunt moves the party's shared mood, which is intended and safe:
applyMoodEvent lands an atomic delta. Its neighbour applyMoodDecayIfStale
writes an absolute gm_mood from the caller's snapshot, and every command
takes the *sender's* lock — a member running it against the leader's run
holds the wrong mutex. The owner check now lives on that function.

A seat outlives status='active'. releaseParty deliberately skips the
seven-day 'extracting' limbo, so the roster persists while
activeExpeditionFor goes blind — long enough for a member to open a run
that wins every lookup once the leader !resumes. seatedExpeditionFor spans
both statuses; it is what the busy-guards ask.

!expedition run was still member-blind. It is the same walk as !zone
advance, reached by its other name.

isPartyMember replaces `run != nil && !isLeader`: activeZoneRunFor reports
isLeader=false for a player with no run anywhere, so the bare test sends a
solo player to go ask their leader.

Golden byte-identical; solo T1 expedition clears end-to-end.
2026-07-09 23:56:16 -07:00
prosolis
b333d05443 N3/P6c: a fight the whole party sits down for
`!fight` seats the expedition's roster instead of the one player who typed
it. Seat 0 is the leader, always: the session row is theirs, the lock is
theirs, and `!flee`, the fork, and `!extract` stay their call.

A monster that wins initiative now swings before anyone speaks. The session
layer used to park every new fight on a player_turn, which is true of the
hardcoded solo order and a lie about a party's -- the enemy would forfeit
round 1 and nobody would notice. `startPartyCombatSession` rolls the order
and sets the phase from it; `handleFightCmd` settles the round before it
announces, so the opening block narrates the hit rather than quietly
showing its damage.

Members were invisible to two commands that had no business ignoring them:
`!cast` queued a spell for "next combat" while its caster was standing in
one, and `!rest` healed a seated member to full mid boss fight. Both now
resolve through the party.

Nobody leaves without an answer. A downed member's `!fight` opens the
party's fight and tells them why they are not in it. The leader's `!extract`
reaches everyone it drags out of the dungeon, and everyone rolls for what
moved into their house while they were gone.

Supplies burn at 50% x N x 4/5 -- a party eats more than one and less than
N. The ratio is exact: 0.8 as a float truncates a party of three to 119%,
a permanent tax nobody would have found.

Solo is untouched, byte for byte. One seat means one build, one INSERT, no
participant rows, the same RNG draws in the same order -- the combat
characterization golden does not move, and neither does the balance corpus.
2026-07-09 23:23:17 -07:00
prosolis
100a4f1054 Auto-walk: cross region boundaries in multi-region zones
Extract regionCmdTravel's transit core into advanceToNextRegion (shared
by manual !region travel), then have runAutopilotWalk auto-advance into
the next region on a mid-zone stopComplete instead of dead-stopping the
walk. Transit cost (day + supplies + wandering check) is identical on
both paths.

(cherry picked from commit 8ac09cf3b059a086c431859c8aaaf046aa992342)
2026-05-21 23:56:11 -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
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