adventure: fix three review findings on the who page

- timelineLine named the viewer as their own rival on a lost duel: a
  rival_result arrives on the loser's page via the opponent column, so
  naming the opponent field pointed at themselves. Thread the page
  character's name through and name the other party.
- buildMapView drew a frontier room twice when two visited rooms both
  had a door to it (Nodes carried the id twice). Place each id once.
- proseGuard's board query ran before the IsGUIDSeen dedup, so a retried
  dispatch paid for it and threw it away. Check dedup first.
This commit is contained in:
prosolis
2026-07-17 10:31:15 -07:00
parent 8a5fea78ba
commit 9d9cfd9f9a
3 changed files with 32 additions and 13 deletions

View File

@@ -213,7 +213,7 @@ func (s *Server) handleAdventureWho(w http.ResponseWriter, r *http.Request) {
if page.MoreHistory {
events = events[:timelineCap]
}
page.Timeline = buildTimeline(s, events)
page.Timeline = buildTimeline(s, entry.Name, events)
}
// Owner enrichment: only when the signed-in user's localpart owns this exact
@@ -279,14 +279,14 @@ func (s *Server) handleAdventureWhoAPI(w http.ResponseWriter, r *http.Request) {
// ever: Josie brings down the Rotmother"), and forty of those stacked in a column
// read as a wall of shouting. The trail wants the noun, not the announcement. It
// also saves a join back to stories for every row.
func buildTimeline(s *Server, events []storage.AdvEvent) []timelineEntry {
func buildTimeline(s *Server, subject string, events []storage.AdvEvent) []timelineEntry {
out := make([]timelineEntry, 0, len(events))
for _, e := range events {
label, emoji := advEventMeta(e.EventType)
out = append(out, timelineEntry{
Emoji: emoji,
Label: label,
Line: timelineLine(e),
Line: timelineLine(subject, e),
When: time.Unix(e.OccurredAt, 0).UTC().Format("Jan 2, 2006"),
Permalink: s.advPermalink(e.GUID),
// A realm-first hoard is the treasure worth an eye; a plain find rides
@@ -301,7 +301,7 @@ func buildTimeline(s *Server, events []storage.AdvEvent) []timelineEntry {
// timelineLine is the short "what" of a fact: the monster, the zone, the rival.
// Empty is fine — the label and emoji already carry the event, and inventing
// filler for a fact that carries no nouns would just be noise.
func timelineLine(e storage.AdvEvent) string {
func timelineLine(subject string, e storage.AdvEvent) string {
inZone := func(s string) string {
if e.Zone == "" {
return s
@@ -321,8 +321,16 @@ func timelineLine(e storage.AdvEvent) string {
}
return e.Zone
case "rival_result", "pete_duel_win", "pete_duel_loss":
if e.Opponent != "" {
return "vs " + e.Opponent
// Name the *other* party. On the winner's page the rival is the opponent;
// on the loser's page this fact arrives via the opponent column, so the
// rival is the subject — naming e.Opponent there would point the viewer at
// themselves. Pete duels carry no character opponent, so this stays empty.
other := e.Opponent
if e.Opponent == subject {
other = e.Subject
}
if other != "" && other != subject {
return "vs " + other
}
return ""
case "milestone":