Adv 2.0 D&D Phase 12 E3d: Feywild Crossing time distortion

§7.4 daily 1d6 distortion roll fires at briefing time:
  • 1–2 normal day (no override)
  • 3–4 half-day → 0.5× burn multiplier override
  • 5    double-day → 2.0× burn + extra wandering check at recap
  • 6    time loop → narration only; combat-link reads on next room

Refactors applyZoneTemporalPreBurn to return TemporalBurnOverride
(Multiplier-based) instead of a force-double bool. Sunken Temple's
Day-6 tidal piggybacks on the same override pipeline (2.0×) instead
of the siege-flag hack.

Persists the day's distortion bucket to RegionState["feywild_today"]
so the recap path can fire the §7.4 extra wandering check on
double-day.

Reuses flavor.FeywildTimeDistortionHalf / Double / Loop per
feedback_reuse_existing_flavor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-05-08 16:03:50 -07:00
parent 044baddcce
commit 3a000ada8c
3 changed files with 287 additions and 12 deletions

View File

@@ -150,15 +150,26 @@ func scanExpeditionRows(rows *sql.Rows) ([]*Expedition, error) {
// deliverBriefing rolls a day forward, applies supply burn, posts the
// morning briefing DM, appends a log entry, and stamps last_briefing_at.
func (p *AdventurePlugin) deliverBriefing(e *Expedition, now time.Time) error {
// E3: zone-specific temporal events that affect this morning's burn
// fire BEFORE applyDailyBurn (e.g. Sunken Temple Day 6 tidal forces
// 2× burn even on tier-2 where HarshMod is 1.5×). Force-double piggy-
// backs on the siege param's "≥2× floor" branch in applyDailyBurn.
forceDouble := applyZoneTemporalPreBurn(e, e.CurrentDay+1)
// E3: zone-specific temporal events fire BEFORE applyDailyBurn and
// can override the entire burn calculation with a fixed multiplier
// (Sunken Temple tidal 2.0×, Feywild half-day 0.5×, etc.).
burnOverride := applyZoneTemporalPreBurn(e, e.CurrentDay+1)
// Advance day + supply burn happen together at the morning rollover.
harsh := e.ThreatLevel > 60 || zoneTemporalHarsh(e)
newSupplies, burn := applyDailyBurn(e.Supplies, harsh, e.SiegeMode || forceDouble)
var newSupplies ExpeditionSupplies
var burn float32
if burnOverride.Multiplier > 0 {
burn = e.Supplies.DailyBurn * burnOverride.Multiplier
newSupplies = e.Supplies
newSupplies.Current -= burn
if newSupplies.Current < 0 {
newSupplies.Current = 0
}
newSupplies.ForagedToday = false
} else {
harsh := e.ThreatLevel > 60 || zoneTemporalHarsh(e)
newSupplies, burn = applyDailyBurn(e.Supplies, harsh, e.SiegeMode)
}
if err := updateSupplies(e.ID, newSupplies); err != nil {
return err
}
@@ -234,6 +245,15 @@ func (p *AdventurePlugin) deliverRecap(e *Expedition, now time.Time) error {
slog.Warn("expedition: night check", "expedition", e.ID, "err", err)
}
night = &nc
// §7.4: Feywild double-day fires an extra wandering check.
if e.ZoneID == ZoneFeywildCrossing {
if today, _ := e.RegionState["feywild_today"].(string); today == string(FeywildDistortionDouble) {
extra := resolveWanderingCheck(e, charClass, nil)
if err := processNightCheck(e, extra); err != nil {
slog.Warn("expedition: feywild extra night check", "expedition", e.ID, "err", err)
}
}
}
// Refresh in-memory threat after possible signs-of-passage bump.
if fresh, err := getExpedition(e.ID); err == nil && fresh != nil {
e.ThreatLevel = fresh.ThreatLevel