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:
+29
-16
@@ -38,6 +38,13 @@ type whoDetail struct {
|
||||
Map *whoMap `json:"map"`
|
||||
// Party is who else is down there, leader first. Absent on a solo run.
|
||||
Party []partySeat `json:"party"`
|
||||
// PartyKnown says the sheet was built by a gogobee that knows about party
|
||||
// seats, which an absent Party cannot: nil means "solo" and "this sender never
|
||||
// pushes seats" identically, and those two want opposite buttons. It is a
|
||||
// capability flag about the SENDER, set unconditionally by any gogobee new
|
||||
// enough — including on a solo run, including when the seat list is omitted —
|
||||
// so it must never be read as a fact about the character.
|
||||
PartyKnown bool `json:"party_known"`
|
||||
}
|
||||
|
||||
// partySeat is one body on a shared expedition as gogobee described it. Kind is
|
||||
@@ -120,28 +127,34 @@ const rosterStatusExpedition = "expedition"
|
||||
// its refusal is the real answer. What this buys is a page that does not put
|
||||
// "Leave the party" in front of somebody standing in town.
|
||||
//
|
||||
// Nothing new crosses the wire for it. Leadership is already legible in the party
|
||||
// seats gogobee pushes (W7), and the sitter's standing is already in the babysit
|
||||
// offer (W5b) — so the two facts the buttons need were both already here.
|
||||
// haveParty says whether the public detail blob decoded at all, and it is
|
||||
// load-bearing rather than defensive. An empty seat list means "solo" ONLY when
|
||||
// we have actually read the sheet; a blob that did not decode — a gogobee too old
|
||||
// to push seats, a truncated column, a shape change — produces the same empty
|
||||
// slice, and treating that as solo would offer a party MEMBER the button that
|
||||
// throws away everyone's day. Found by getting a fixture wrong: the page did
|
||||
// exactly that, silently and convincingly.
|
||||
func offersToUndo(token, status string, haveParty bool, party []partySeat, self storage.PlayerDetail) (abandon, leave, cancelSitter bool) {
|
||||
// Leadership is legible in the party seats gogobee pushes (W7) and the sitter's
|
||||
// standing is in the babysit offer (W5b), so the facts the buttons need are on
|
||||
// the wire already — except for one, which nil could not express. partyKnown is
|
||||
// the sender's "I know about party seats" flag, and it gates the empty-list
|
||||
// branch alone. An empty list means "solo, so this player is the leader" ONLY
|
||||
// from a sender that would have listed seats if there were any; from a blob that
|
||||
// did not decode, or from a gogobee too old to push seats at all, the same empty
|
||||
// slice would offer a party MEMBER the button that throws away everyone's day.
|
||||
// Found by getting a fixture wrong: the page did exactly that, silently and
|
||||
// convincingly.
|
||||
//
|
||||
// The asymmetry is deliberate: the len(party) > 0 branch reads the viewer's own
|
||||
// seat and is self-evidencing — a seat that says "member" cannot be mistaken for
|
||||
// leadership — so it needs no flag and keeps working against every sender.
|
||||
func offersToUndo(token, status string, partyKnown bool, party []partySeat, self storage.PlayerDetail) (abandon, leave, cancelSitter bool) {
|
||||
// The sitter first, because it is the only one of the three whose fact does
|
||||
// not go stale: an engagement is a property of the character, not of where
|
||||
// they are standing, so a two-minute-old snapshot is still right about it.
|
||||
cancelSitter = self.Babysit != nil && self.Babysit.Active
|
||||
|
||||
if status == rosterStatusExpedition && haveParty {
|
||||
if status == rosterStatusExpedition {
|
||||
if len(party) == 0 {
|
||||
// A SOLO run publishes no party at all — partySeatViews returns nil
|
||||
// below two seats — so an empty list here is not "we don't know", it is
|
||||
// "there is nobody else", which makes this player the leader.
|
||||
abandon = true
|
||||
// below two seats — so from a sender that knows about seats an empty list
|
||||
// is not "we don't know", it is "there is nobody else", which makes this
|
||||
// player the leader. Without the flag it is exactly "we don't know", and
|
||||
// the button stays off.
|
||||
abandon = partyKnown
|
||||
} else {
|
||||
// With a party, offer strictly on the viewer's own seat, and offer
|
||||
// nothing at all if we cannot find it. Guessing in that case would mean
|
||||
@@ -402,7 +415,7 @@ func (s *Server) handleAdventureWho(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
page.CanAbandon, page.CanLeave, page.CanCancelSitter =
|
||||
offersToUndo(token, entry.Status, page.HasDetail, page.Detail.Party, self)
|
||||
offersToUndo(token, entry.Status, page.HasDetail && page.Detail.PartyKnown, page.Detail.Party, self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user