adventure: let a player act from the web, not just read about it

The equip queue proved the reverse pipe works. This gives it verbs that
play the game: pull out of a run from the adventurer page, take today's
bout from the war room.

Its own table and its own poll, not more actions on equip_orders. Every
column of that table is equip vocabulary (item, slot, tier) and these
verbs act on the character rather than on something it is carrying.

Nothing in a request names an adventurer. The session maps to one
localpart and a localpart to one adventurer, so Pete resolves the
character itself and there is no id on the wire to forge.

The panel's copy is kept honest by the verdict: an applied action hides
the offer it has just spent, a refusal puts the button back. Watching it
run is what put that there, along with the strip's layout — gogobee
answers a bout with a whole sentence of damage, which the equip strip's
two-column row squeezed into a column and wrapped the verb.

Claude-Session: https://claude.ai/code/session_012bxpQQJDjC1mTtLN3VVtBQ
This commit is contained in:
prosolis
2026-07-24 18:55:42 -07:00
parent b19ab5eff0
commit 6b0aae9f4a
10 changed files with 1178 additions and 9 deletions
+26 -1
View File
@@ -82,6 +82,18 @@ type SiegePastView struct {
type siegePage struct {
pageData
Siege SiegeView
// The viewer's own standing in the muster, when they are signed in and have
// an adventurer. This is the only personal thing on an otherwise wholly
// public page, and it exists to hang one button off: the war room is where
// somebody realises the town needs them, so it is where they should be able
// to answer.
//
// YouFought reads a snapshot up to two minutes old, so it decides what the
// page OFFERS and never what the game allows — a bout taken in Matrix inside
// that window comes back from gogobee as rejected_already_fought, which is
// the honest answer and the one the strip shows.
YouOnBoard bool
YouFought bool
}
// handleSiegeIngest replaces the war room with gogobee's latest snapshot.
@@ -153,11 +165,24 @@ func (s *Server) handleSiegePage(w http.ResponseWriter, r *http.Request) {
base := s.base(r)
base.Active = "adventure"
view := s.siege()
page := siegePage{pageData: base, Siege: view}
if base.User != nil {
if token, ok := storage.SelfToken(buyerLocalpart(base.User)); ok {
page.YouOnBoard = true
for _, d := range view.Fought {
if d.Token == token {
page.YouFought = true
break
}
}
}
}
// Unlike the who page this one is NOT noindex: it names a boss and a town,
// and the defender list is character names that are already public on the
// board. There is nothing here that ties a page to a person more than
// /adventure already does.
s.render(w, "siege", siegePage{pageData: base, Siege: s.siege()})
s.render(w, "siege", page)
}
// handleSiegeAPI serves the war room as JSON for the page's own re-poll. This