N7/E4: Seasonal events — 1-week holiday skins

Four anchor holidays (Hallowtide, Midwinter Feast, Sweethearts' Revel,
First Bloom) each get a 7-day window (anchor ±3 days) that layers three
things on the world, all reusing existing machinery:

  1. A themed Omen that overrides the weekly rotation. Reuses the B3 omen
     effect fields, so the non-combat rule holds; kept behind activeOmen's
     simOmenDisabled guard (and activeSeason honours it too) so no season
     path can reach the balance sim or move the golden.
  2. A curated curio shelf at Luigi's — existing registry items rotated to
     the front of dailyCuriosStock, so no net-new power enters the economy.
     Off-season output is byte-identical to before.
  3. A themed road visitor via the ambient seam — a season_visitor event
     that leaves a sellable keepsake + coin gift. No combat: the ambient
     seam still never opens a fight.

Pure function of the UTC date + anchor calendar — no schema, ticker, or
persistence, same discipline as the Omen and holiday calendar. Season
banner rides the existing morning DM (no net-new scheduled message).

go test ./internal/plugin ./internal/db green; combat golden byte-identical.

Claude-Session: https://claude.ai/code/session_017mEwUmmS7aQTP2NQXj6rUa
This commit is contained in:
prosolis
2026-07-10 20:30:15 -07:00
parent 1a47a2fdee
commit a6f1de4e74
6 changed files with 414 additions and 7 deletions

View File

@@ -84,6 +84,11 @@ func activeOmen() omen {
if simOmenDisabled {
return omen{Key: "none", Name: "None"}
}
// N7/E4 — a live season's themed omen overrides the weekly rotation. Kept
// behind the simOmenDisabled guard above so the balance sim never sees it.
if s, ok := activeSeason(); ok {
return s.Omen
}
y, w := time.Now().UTC().ISOWeek()
return omenForWeek(y, w)
}