adventure: give a finished run a report worth sharing
The liveblog answers "what is happening" — it is capped, it scrolls, and six hours after a run ends it is gone, because the adventurer page is about now. Nothing answered the question asked afterwards, usually by somebody who wasn't watching: what WAS that run. So a dispatch announcing a clear or a death was a paragraph about an outcome with no way back to what produced it. The report is that way back. The whole log uncapped, the numbers rolled up, and the single worst hit the party took pulled out of the middle where it otherwise reads as one line among forty. It is stable for a fortnight, which is what makes it a thing worth linking from a dispatch and worth sending to somebody. It is assembled from the same beats through the same renderer as the liveblog. A report that told a different story from the log it was built out of would be the more convincing of the two and the less true. The summary is the exception and the only prose on the channel: gogobee's model reads the finished run back and says what it was about, which is a judgement no template makes. It rides a summary beat rather than its own endpoint, so it inherits the whole channel — idempotent, retried, impossible to attach to a run that doesn't exist — and it passes the same class of guard a dispatch lede does before it reaches a public page. Visibility is the adventurer page's rule exactly, and that matters more here than anywhere: the report outlives the log by a fortnight and is linked from a public dispatch, so it is the surface most likely to still be reachable after somebody opts out. Coming off the board closes it, including through links minted days earlier. Claude-Session: https://claude.ai/code/session_012bxpQQJDjC1mTtLN3VVtBQ
This commit is contained in:
@@ -24,21 +24,25 @@ import (
|
||||
// former transport field kept here: for a treasure_found it carries the item's
|
||||
// name, which is the fact, not the delivery.
|
||||
type AdvEvent struct {
|
||||
GUID string `json:"guid"`
|
||||
EventType string `json:"event_type"`
|
||||
Tier string `json:"tier"`
|
||||
Subject string `json:"subject"`
|
||||
Opponent string `json:"opponent"`
|
||||
Boss string `json:"boss"`
|
||||
Zone string `json:"zone"`
|
||||
Region string `json:"region"`
|
||||
Level int `json:"level"`
|
||||
Tally int `json:"tally"`
|
||||
Outcome string `json:"outcome"`
|
||||
Milestone string `json:"milestone"`
|
||||
Stakes string `json:"stakes"`
|
||||
Actors []string `json:"actors"`
|
||||
OccurredAt int64 `json:"occurred_at"`
|
||||
GUID string `json:"guid"`
|
||||
EventType string `json:"event_type"`
|
||||
Tier string `json:"tier"`
|
||||
Subject string `json:"subject"`
|
||||
Opponent string `json:"opponent"`
|
||||
Boss string `json:"boss"`
|
||||
Zone string `json:"zone"`
|
||||
Region string `json:"region"`
|
||||
Level int `json:"level"`
|
||||
Tally int `json:"tally"`
|
||||
Outcome string `json:"outcome"`
|
||||
Milestone string `json:"milestone"`
|
||||
Stakes string `json:"stakes"`
|
||||
Actors []string `json:"actors"`
|
||||
// RunID is set on the dispatches that are the *ending* of an expedition — a
|
||||
// clear, a retreat, a death. It is the join from "how it went" to "what
|
||||
// happened", and it is empty on every other kind of fact.
|
||||
RunID string `json:"run_id,omitempty"`
|
||||
OccurredAt int64 `json:"occurred_at"`
|
||||
}
|
||||
|
||||
// InsertAdventureEvent records a fact. Idempotent on guid via INSERT OR IGNORE:
|
||||
@@ -54,10 +58,11 @@ func InsertAdventureEvent(e *AdvEvent) error {
|
||||
_, err = Get().Exec(`
|
||||
INSERT OR IGNORE INTO adventure_events
|
||||
(guid, event_type, tier, subject, opponent, boss, zone, region,
|
||||
level, tally, outcome, milestone, stakes, actors, occurred_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
level, tally, outcome, milestone, stakes, actors, run_id, occurred_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
e.GUID, e.EventType, e.Tier, e.Subject, e.Opponent, e.Boss, e.Zone,
|
||||
e.Region, e.Level, e.Tally, e.Outcome, e.Milestone, e.Stakes, string(actors), e.OccurredAt)
|
||||
e.Region, e.Level, e.Tally, e.Outcome, e.Milestone, e.Stakes, string(actors),
|
||||
e.RunID, e.OccurredAt)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -71,13 +76,13 @@ func AdventureEventByGUID(guid string) (*AdvEvent, error) {
|
||||
return nil, nil
|
||||
}
|
||||
var e AdvEvent
|
||||
var tier, subject, opponent, boss, zone, region, outcome, milestone, stakes, actors sql.NullString
|
||||
var tier, subject, opponent, boss, zone, region, outcome, milestone, stakes, actors, runID sql.NullString
|
||||
err := Get().QueryRow(`
|
||||
SELECT guid, event_type, tier, subject, opponent, boss, zone, region,
|
||||
level, tally, outcome, milestone, stakes, actors, occurred_at
|
||||
level, tally, outcome, milestone, stakes, actors, run_id, occurred_at
|
||||
FROM adventure_events WHERE guid = ?`, guid).Scan(
|
||||
&e.GUID, &e.EventType, &tier, &subject, &opponent, &boss, &zone, ®ion,
|
||||
&e.Level, &e.Tally, &outcome, &milestone, &stakes, &actors, &e.OccurredAt)
|
||||
&e.Level, &e.Tally, &outcome, &milestone, &stakes, &actors, &runID, &e.OccurredAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -87,6 +92,7 @@ func AdventureEventByGUID(guid string) (*AdvEvent, error) {
|
||||
e.Tier, e.Subject, e.Opponent = tier.String, subject.String, opponent.String
|
||||
e.Boss, e.Zone, e.Region = boss.String, zone.String, region.String
|
||||
e.Outcome, e.Milestone, e.Stakes = outcome.String, milestone.String, stakes.String
|
||||
e.RunID = runID.String
|
||||
if actors.String != "" {
|
||||
_ = json.Unmarshal([]byte(actors.String), &e.Actors)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user