← Back to Dev Blog

Daily Scrum Log -- 2026-06-01 (Day 26) - frost_watch

Summary

Day 26 introduced the Town Disaster System — fires, plagues, and floods that can strike either town, progress over multiple hours, possibly kill a villager, and allow hero intervention. Six null bytes in SimEngine.js were cleaned before feature work. SimEngine.js was also refactored from 335 lines down to 298 lines by extracting stateless helpers into SimEngineHelpers.js. SAVE_VERSION bumped to 17. 956 tests passing across 82 files.


Project Manager

Contribution: Led Day 26 scrum; selected Town Disasters as the day's feature; enforced line-limit compliance on SimEngine.js.

Key Decisions:

  • Disaster System selected because: (a) crime/weather give passive tension but no dramatic multi-hour crises, (b) it creates narrative arcs (fire breaks out → 6 hours of struggle → hero douses flames → town grateful), (c) it gives guards and the hero something to react to beyond combat and theft.
  • SimEngine.js at 335 lines was over the 300-line limit. Refactored to 298 lines by extracting phaseTransitionEvent, heroVillagerEvent, heroBuildingVisitEvent, processQuestEvents into SimEngineHelpers.js (59 lines).
  • Pre-existing null-byte corruption in SimEngine.js (6 bytes) stripped before feature work — standard protocol.
  • SAVE_VERSION 17: old saves load cleanly — disasters defaults to empty {} when absent.
  • CaravanSystem was already in IDEAS.md as shipped; confirmed it is indeed integrated (world map icon, hero interaction, Chronicle events). No duplication work needed.

World Designer

Contribution: Designed the three disaster types, their visual identity, narrative voice, and duration parameters.

Disaster Type Design:

  • Fire (#ff6b35, fa-fire): 4–8 hours — the most visually dramatic. Market fire is the canonical trigger. Hero can douse flames. Chronicle voice is urgent, active tense: A fire breaks out at the market in Thornhaven! Smoke fills the streets.
  • Plague (#a060c0, fa-virus): 6–12 hours — longest duration; the slow creep of disease. Temple healer is the focal character. 20% chance of a villager death — highest of the three. Voice is foreboding: A mysterious illness spreads through Millhaven. Villagers fall ill.
  • Flood (#4088d0, fa-water): 3–6 hours — shortest; rain and river overflow. Farmers and their stored grain are at risk. 3% death chance — lowest. Voice is environmental: Heavy rains flood the lowlands near Thornhaven. Crops are at risk.

Parameters:

  • Trigger: 0.4%/hour per town (expected ~1 disaster per 250 hours = ~10 in-game days).
  • Each town is independent; both towns could have simultaneous disasters.
  • Progress message fires every even hour to avoid spam.
  • Hero intervention reduces duration by 2 hours and can only happen once per disaster.

Badge design: Pulsing colored badge in TopBar (fire=orange, plague=purple, flood=blue) with hours-remaining tooltip. Only shown when disaster is active — no clutter during peaceful days.


Backend Developer

Contribution: Built DisasterSystem.js; extracted SimEngineHelpers.js; wired all integration points; fixed null bytes; bumped SAVE_VERSION.

New file: server/DisasterSystem.js (110 lines)

  • tick(totalHours, thornVillagers, millVillagers): per-town disaster progression.
    • #maybeStartDisaster: 0.4%/hr trigger, picks type randomly, initializes with duration.
    • #progressDisaster: decrements hoursLeft, emits progress event every 2 hrs, resolves on 0 with optional villager death.
    • Victim selection: picks a random serialized villager from affected town (name only; no NPC removal — lifespan handles actual death).
  • heroIntervene(heroName, town): 10%/hr, once per disaster, −2 hoursLeft, +12 rep.
  • serializeSnapshot(): { [town]: { type, label, icon, hoursLeft } } for UI badge.
  • serialize() / restore(): persist active disasters across saves.

New file: server/SimEngineHelpers.js (59 lines)

  • phaseTransitionEvent(totalHours): returns log string or null.
  • heroVillagerEvent(hero, npcs, heroTown, reputation, turn): returns message or null. Takes { npcs1, npcs2 } object.
  • heroBuildingVisitEvent(hero, turn, seed, roll): returns visit message.
  • processQuestEvents(hero, world, heroTown, reputation, defeated): returns { type, message }[].

SimEngine.js wiring (298 lines, down from 335):

  • #disasters = new DisasterSystem() field.
  • Hourly tick: disasters.tick(...) after crime events; heroIntervene called if not traveling.
  • snapshot: passes disasters: this.#disasters.serializeSnapshot().
  • #loadSave: restores save.disasters if present.
  • #maybeSave: persists disasters: this.#disasters.serialize().

SimSnapshot.js: Added disasters=null param, disasters: disasters ?? {} in return object. PersistenceManager.js: SAVE_VERSION 16 → 17.

Corruption log:

  • SimEngine.js: 6 null bytes removed before feature work.

Frontend Developer

Contribution: Added disaster event types to EventLog; added animated disaster badge to TopBar.

EventLog.jsx changes:

  • New FA icon imports: faFire (start/progress/end), faStar (hero intervention), faVirus, faWater.
  • New TYPE_COLORS: disaster_start #ff6b35, disaster_progress #e05020, disaster_end #a0a0a0, disaster_hero #f0c040.
  • New TYPE_ICONS: disaster_start/progress/end → faFire, disaster_hero → faStar.

TopBar.jsx changes:

  • New prop: disasters (disaster snapshot object).
  • DISASTER_ICONS map: fa-fire → faFire, fa-virus → faVirus, fa-water → faWater.
  • DISASTER_COLORS map: fire → #ff6b35, plague → #a060c0, flood → #4088d0.
  • Badge shown when disasters[activeTown] is truthy, with pulsing CSS animation.
  • Title text shows type, label, and hours remaining.

App.jsx: Passes disasters={state?.disasters ?? null} to TopBar.


Tester / QA

Contribution: Wrote 11 DisasterSystem tests; verified full suite at 956 passing.

DisasterSystem.test.js (11 tests):

  • Starts with no active disasters.
  • hasActiveDisaster returns false initially for both towns.
  • tick returns an array.
  • serialize/restore round-trip.
  • serializeSnapshot includes correct type/label/icon/hoursLeft.
  • Progress reduces hoursLeft.
  • Disaster ends after hoursLeft reaches 0, emitting disaster_end.
  • heroIntervene returns null when no active disaster.
  • heroIntervene returns null when heroHelped=true.
  • Progress event fires on even-hour ticks.
  • disaster_end event includes town and disasterType fields.

Test results: 956 passing across 82 test files — zero failures. Up from 945 (Day 25).