feat(go-services): tracker share_* handlers (complete ingest) + shadow tuning

- share.go: cross-machine vital sharing (share_subscribe/unsubscribe/share_*),
  faithful port of the peer-state snapshot + plugin fan-out + /vital-sharing/peers.
  The last ingest handler — the Go tracker now handles every plugin event type.
- shadow consumer: drop the outbound keepalive ping (the firehose is never idle)
  and tighten the read-deadline watchdog to 12s for faster reconnect after the
  upstream's periodic eviction (full-firehose browser clients get evicted ~every
  90s; the watchdog recovers it, ~90% duty cycle). Production-bound /ws/position
  is unaffected (plugins connect to us; no eviction).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-24 11:27:25 +02:00
parent 27757636e4
commit 5b2db439a3
6 changed files with 171 additions and 27 deletions

View file

@ -32,16 +32,21 @@ type Ingestor struct {
liveCombatStats map[string]map[string]any
dungeonMapCache map[string]map[string]any
questStatus map[string]map[string]string
lastKills map[string]int // "session_id|character_name" -> kills
lastKills map[string]int // "session_id|character_name" -> kills
combatLastSession map[string]map[string]any // "char:session_id" -> last cumulative session
combatLifetimeCache map[string]map[string]any // character_name -> accumulated lifetime
vitalSubscribers map[string]bool
vitalPeerState map[string]map[string]any
plugins *pluginRegistry // for share_* fan-out + plugin_connected status
}
func newIngestor(pool *pgxpool.Pool, log *slog.Logger, broadcast func(map[string]any)) *Ingestor {
func newIngestor(pool *pgxpool.Pool, log *slog.Logger, broadcast func(map[string]any), plugins *pluginRegistry) *Ingestor {
return &Ingestor{
pool: pool,
log: log,
broadcast: broadcast,
plugins: plugins,
liveSnapshots: map[string]map[string]any{},
liveVitals: map[string]map[string]any{},
liveCharacterStats: map[string]map[string]any{},
@ -53,6 +58,8 @@ func newIngestor(pool *pgxpool.Pool, log *slog.Logger, broadcast func(map[string
lastKills: map[string]int{},
combatLastSession: map[string]map[string]any{},
combatLifetimeCache: map[string]map[string]any{},
vitalSubscribers: map[string]bool{},
vitalPeerState: map[string]map[string]any{},
}
}
@ -84,10 +91,17 @@ func (i *Ingestor) dispatch(ctx context.Context, data map[string]any) {
i.handleDungeonMap(data)
case t == "combat_stats":
i.handleCombatStats(ctx, data)
case t == "share_subscribe":
i.handleShareSubscribe(data)
case t == "share_unsubscribe":
i.handleShareUnsubscribe(data)
return // unsubscribe broadcasts its own share_peer_removed; don't re-broadcast
case strings.HasPrefix(t, "share_"):
i.handleShareUpdate(t, data)
case t == "register":
// no DB / no broadcast; plugin_conns belongs to the /ws/position server
case strings.HasPrefix(t, "share_"), t == "chat":
// share_* handled in a later pass; chat is broadcast-only
case t == "chat":
// broadcast-only
}
if i.broadcast != nil {
i.broadcast(data)