adventure: work the five review findings the last pass left open

The extract pre-check is gone. It read a snapshot up to two minutes behind and
still got the last word, so somebody who set out over Matrix during a lagging
roster push was told they weren't on an expedition for a run gogobee would
happily have ended. Same call abandon and leave already made: let it through and
let rejected_not_running be the answer.

The siege_join check stays, because whether a boss is camped outside town is
town-wide and runs on a day-or-longer clock, but it now reads one column through
SiegeIsCamped instead of loading every defender row and the whole history to
look at one flag.

The war-room history insert is OR REPLACE. boss_id is the primary key and it was
never settled whether gogobee means the siege instance or the boss type by it, so
a duplicate pair used to fail the transaction carrying the live boss and the
muster too and freeze the war room on the last good snapshot. A dropped history
row is the smaller failure; the open question is noted in the schema.

offersToUndo's guard didn't cover the case its comment claimed. A gogobee too old
to push seats sends a valid blob with no party key, which decodes to the same
empty slice as a solo run, and a party member got shown the button that throws
away everyone's day. That needs a new field, so whoDetail gains party_known and
the flag gates the empty-list branch alone; the branch that reads the viewer's
own seat is self-evidencing and keeps working against any sender. gogobee's half
is written up in adventure_party_known_flag.md.

And an empty offer list no longer claims "you're already out there", which Pete
can't actually know from a game box too old to push offers at all.
This commit is contained in:
prosolis
2026-07-24 22:45:26 -07:00
parent c40ac1e673
commit aac6c3e127
9 changed files with 232 additions and 232 deletions
+16 -11
View File
@@ -108,15 +108,18 @@ func TestOnlyOneOutstandingOrderPerVerb(t *testing.T) {
}
}
// TestActionPreChecksAreCourtesyOnly pins both halves of a deliberate asymmetry.
// Pete refuses what its own snapshot says is impossible — but the snapshot is up
// to two minutes old, so the refusal must be cheap and local (a 409 the button
// shows immediately), never a queued order gogobee has to answer.
func TestActionPreChecksAreCourtesyOnly(t *testing.T) {
// Idle mark: extract refused up front.
// TestOnlyTownWideFactsArePreChecked. Pete's copy of the board is up to two
// minutes behind the game box, so what it may refuse locally turns on whether
// being two minutes late could make the answer wrong. A personal status can:
// somebody who set out over Matrix still reads as idle here, and refusing their
// extract would deny a run gogobee would have ended. A boss camped outside town
// cannot: that is town-wide and runs on a day-or-longer clock.
func TestOnlyTownWideFactsArePreChecked(t *testing.T) {
// Idle mark: extract goes through anyway, and rejected_not_running is the
// answer if the mark really was standing in town.
s := seedActions(t, "holymachina", "idle")
if w := placeAction(t, s, "holymachina", "extract"); w.Code != 409 {
t.Fatalf("extract while idle = %d, want 409", w.Code)
if w := placeAction(t, s, "holymachina", "extract"); w.Code != 200 {
t.Fatalf("extract while idle = %d, want it queued (%s)", w.Code, w.Body.String())
}
// No Siege pushed at all: unknown, not "inactive". Pete has never heard from
@@ -328,10 +331,12 @@ func TestExpeditionParamsAreResolvedAgainstTheOwnersOffers(t *testing.T) {
}
}
// An empty zone list means "already out there", not "nowhere to go" — gogobee
// omits the offers entirely while the adventurer is on an expedition. Refusing
// An empty zone list is a refusal, and the message says only what Pete saw. It
// usually means the adventurer is already out — gogobee omits the offers
// entirely while they are down there — but a game box too old to push offers
// sends the same empty list, so the copy claims nothing about which. Refusing
// cheaply here beats a verdict thirty seconds later saying the same thing.
func TestNoZoneOffersMeansAlreadyOut(t *testing.T) {
func TestNoZoneOffersMeansNothingOnOffer(t *testing.T) {
s := seedOffers(t, "holymachina", "expedition", storage.PlayerDetail{})
w := placeParams(t, s, "holymachina", advOrderReq{
Action: storage.AdvActionExpedition, Zone: "goblin_warrens", Loadout: "lean",