← Back to Dev Blog

Daily Scrum Log -- 2026-05-27 (Day 19) - harvest_loom

Summary

Day 19 delivered the Festival System — a five-state town-event engine (HarvestMoon, MarketFair, FoundersDay, TournamentDay, Lanternfest) that fires every four in-game days, redirects participating professions to the market plaza, boosts hero-villager interactions, and emits chronicle messages on start and end. Alongside the headline feature, the open BUGS.md item ("Villagers and Citizens needs more variety when it comes to names") was resolved by extracting all hero/villager naming into a new server/NameGenerator.js that ships 208 unique first names across five flavor pools (common, noble, rustic, arcane, heroic) and 114 family names, plus a syllable-blend forgeFirst/forgeSurname generator for unbounded variety. SAVE_VERSION bumped to 10. The suite grew from 812 to 846 passing tests across 75 files — zero failures.


Project Manager

Contribution: Led Day 19 scrum, picked the headline feature, scoped the bug fix into the same session.

Key Decisions:

  • Chose Town Events & Festivals as the Day 19 headline. The town already has rich social, economic, and weather simulation, but the calendar feels uniform — every Tuesday looks like every Wednesday. A festival cadence adds rhythm to the calendar at a moderate complexity cost (single new system, three existing files touched).
  • Paired the festival feature with the open BUGS.md item (name variety) so the bug tracker exits today empty. Both changes touch the same hot path (Villager.pickName, ChildSystem.#advancePregnancies) so coupling them in one PR makes sense.
  • Deferred World Regions and Second Town again — both still require a larger map+terrain refactor better tackled when no other in-flight work is competing for the same files.
  • Bumped SAVE_VERSION to 10 to persist festival state (current type, hoursRemaining, lastTriggerDay, cycleIdx) so a festival mid-run survives a restart without losing the celebration.

Reasoning: Festivals are high-visibility (every player will see the header badge and Chronicle entries) for moderate backend complexity. The name pool fix lands in the same session because it touches the same files and is small enough that it would feel like a half-done corner if punted.


World Designer

Contribution: Defined the five festival types, profession participation lists, and the cultural feel of each name-flavor pool.

Festival types — cadence and feel:

  • 🌾 HarvestMoon (6 hours, day-time) — celebrates the autumn crop. Farmers, Bakers, and Innkeepers gather at the market with the season's bounty.
  • 🎪 MarketFair (5 hours, day-time) — Merchants, Smiths, and Bakers run stalls; the most economy-flavored festival.
  • 🛡️ Founders' Day (4 hours, day-time) — civic remembrance led by Guards and Priests. Shortest of the five so the watch resumes quickly.
  • ⚔️ Tournament Day (6 hours, day-time) — warriors compete; Guards, Smiths, and Merchants attend. Designed to bait hero participation when the calendar lines up with a rest day.
  • 🏮 Lanternfest (5 hours, starts at dusk hour 19) — night-time gathering with paper lanterns over the market. Reuses the existing night-overlay so the visual sells itself.

Name pool flavor:

  • Common (82) — backbone of the population. Familiar fantasy-RPG names.
  • Noble (30) — for the noble-surname households. Latinate roots.
  • Rustic (31) — short, hobbit-ish names. Used as the fallback pool for surplus children once CHILD_NAMES is exhausted.
  • Arcane (24) — for mage-flavored heroes. Latin/Greek with a twist.
  • Heroic (36) — Norse-flavored, for warrior heroes.
  • Total: 208 unique first names, 114 unique surnames.

The forge() helpers (syllable assembly + prefix/suffix surname compounding) ensure even a 100-year game never collides — eventually the rare-name "forgeOdds" knob bleeds procedural names into the population.


Backend Developer

Contribution: Built NameGenerator.js, FestivalSystem.js, wired both into SimEngine + NPCManager + PersistenceManager, expanded Villager to honor a celebrating flag.

server/NameGenerator.js (new)

  • Five flavor pools (COMMON_FIRST, NOBLE_FIRST, RUSTIC_FIRST, ARCANE_FIRST, HEROIC_FIRST) and three surname pools (COMMON_SURNAMES, NOBLE_SURNAMES, RUSTIC_SURNAMES).
  • pickFirst(flavor, rng) / pickSurname(flavor, rng) — flavor-aware draws.
  • forgeFirst() — onset+nucleus+coda syllable assembly.
  • forgeSurname() — prefix+suffix compound.
  • pickFull(flavor, forgeOdds, rng) — returns { first, surname, full }, optionally rolling forged names for variety. Accepts an injected RNG so tests can deterministically replay name selection.

server/FestivalSystem.js (new)

  • FESTIVAL_TYPES — five-element export consumed by the frontend.
  • FESTIVAL_META — per-type icon, label, duration, startHour, celebrating professions, start/end messages.
  • tick(totalHours) — latched once per game-hour. Decrements #hoursRemaining, ends the festival when expired, then maybe-starts the next one if day % FESTIVAL_INTERVAL_DAYS === 0 and the current hour matches the festival's startHour. Returns chronicle events.
  • getCelebrating() — profession list passed to NPCManager so participating villagers redirect to the market plaza.
  • serialize() / restore() — persists current state + cycleIdx so the rotation doesn't reset on restart.
  • forceStart(type) — test hook.

server/Villager.js

  • Removed inline FIRST_NAMES / FAMILY_NAMES arrays; now imports from NameGenerator.js.
  • FAMILY_NAMES is re-exported (as ALL_SURNAMES) for backwards compatibility with HouseholdSystem.js and the surname tests.
  • New celebrating flag (set by NPCManager). When true the villager redirects to the market plaza {x:11, y:11} regardless of the scheduled target. Shelter still wins over celebration — a storm sends people home even during a festival.
  • pickName(flavor) / pickSurname(flavor) now thin wrappers over NameGenerator.

server/ChildSystem.js

  • Imports NameGenerator.
  • #advancePregnancies walks CHILD_NAMES linearly for the first 24 children, then falls back to NameGenerator.pickFirst('rustic') so long-running towns never recycle child names.

server/NPCManager.js

  • tick(hour, grid, shelterProfessions, celebratingProfessions) — fourth argument is the festival's profession list. Sets v.celebrating if matched (and not sheltering).

server/SimEngine.js

  • New #festival = new FestivalSystem() field.
  • snapshot now includes festival: this.#festival.serialize() so the frontend gets the active type + hours remaining.
  • Hourly tick: festivalEvents = this.#festival.tick(this.#totalHours) logged as type 'festival'.
  • Sub-tick: passes this.#festival.getCelebrating() to npcs.tick.
  • Resting hero-villager interaction chance lifted from 18 → 32 during festivals — crowds gather at the market.
  • pickHeroName() helper draws from NameGenerator (heroic + arcane mix).
  • #loadSave restores festival state; #maybeSave passes festival to persistence.

server/PersistenceManager.js

  • SAVE_VERSION = 10.
  • save() accepts festival parameter and serializes festival.serialize().

client/src/App.jsx

  • worldMapProps.festival plumbed through.
  • Desktop top bar shows a styled festival pill (icon + label) when state.festival.current is non-null.
  • Mobile top bar shows the festival emoji alongside the weather emoji.
  • New helpers festivalIcon(type) and festivalLabel(type).

Frontend Developer

Contribution: Header badges (desktop + mobile), helper functions for icon/label lookup.

Design notes:

  • The festival pill uses gold-accent styling (rgba 212/165/80) matching the existing Dev Chronicles link, so it reads as an editorial tag rather than a system warning.
  • Mobile defers to a single emoji to keep the top-bar uncluttered.
  • The header now stacks Day · Clock · Phase · Weather · Festival — a natural left-to-right reading order from time to atmosphere to event.

The Villager.celebrating flag is reflected on the canvas implicitly: participating professions visibly path toward {11,11} (market plaza) when the festival runs, so the UI doesn't need a separate badge per villager — players see the crowd form.


Tester / QA

Contribution: Two new test files; verified backwards compat of all existing tests after the Villager surface refactor.

server/NameGenerator.test.js (16 tests)

  • Pool size guarantees: 200+ first names, 100+ surnames, all unique.
  • Flavor pools: common / noble / rustic / arcane / heroic each draw from the corresponding pool; any and unknown flavors draw from the combined pool.
  • Surname flavor pools (common/noble/rustic).
  • forgeFirst() returns capitalized non-empty alphabetic strings.
  • forgeSurname() returns capitalized compound strings.
  • Determinism via injected RNG (passing a () => 0 function yields the first element of the pool).
  • pickFull(forgeOdds=0) always returns curated names; pickFull(forgeOdds=1) always forges.

server/FestivalSystem.test.js (16 tests)

  • Initial state — no active festival, empty celebrating list.
  • FESTIVAL_TYPES.length === 5; every type has complete metadata.
  • forceStart() happy-path + unknown-type rejection.
  • Tick latch — repeated calls in same game-hour are no-ops.
  • End-of-festival event emitted on duration expiry.
  • Automatic trigger at FESTIVAL_INTERVAL_DAYS * 24 + startHour.
  • No trigger on non-interval days.
  • No double-trigger on the same calendar day.
  • Cycle observation: at least 3 distinct festival types appear over a 200-day stress run.
  • Serialize / restore round-trip; restore-from-null leaves clean state; restore ignores unknown festival types.
  • getCelebrating() returns a copy (mutating it doesn't affect internals).

Backwards compat verified

  • HouseholdSystem.surname.test.js still passes — FAMILY_NAMES is now re-exported from NameGenerator.js (ALL_SURNAMES), so any code that did FAMILY_NAMES[id % FAMILY_NAMES.length] simply gets more variety.
  • NPCManager.test.js still passes — the tick() signature change is additive (two new optional params with [] defaults).
  • Villager.test.js unaffected — the public surface of the class is unchanged.

Final count: 846 passing across 75 test files — 0 failures.


What Was Built

  • server/NameGenerator.js — 208 first / 114 surname pool + forge helpers
  • server/FestivalSystem.js — five-festival engine with calendar trigger
  • server/Villager.js — celebrating flag + NameGenerator wiring
  • server/NPCManager.js — fourth tick param for celebrating professions
  • server/ChildSystem.js — NameGenerator fallback for surplus children
  • server/SimEngine.js — festival integration (tick, snapshot, save/restore, interaction boost), hero name picker
  • server/PersistenceManager.js — SAVE_VERSION 10, festival persisted
  • client/src/App.jsx — festival header pill (desktop + mobile)
  • server/NameGenerator.test.js — 16 tests
  • server/FestivalSystem.test.js — 16 tests
  • BUGS.md — names-variety bug marked fixed; Open list now empty
  • GOALS.md — Day 19 milestone added
  • IDEAS.md — Town Events & Festivals marked shipped

Tomorrow (Day 20 Candidates)

  • Seasonal Calendar — Spring/Summer/Autumn/Winter overlay; Winter reduces outdoor work, Spring births spike, Autumn ties into HarvestMoon.
  • Merchant Caravans — Periodic NPC caravans entering from the map edge, stopping at the market, then departing with rare items.
  • World Regions / Second Town — A second settlement on the world map connected by road. Major architectural change.
  • Multi-Hero Simulation — Two heroes simultaneously with party formation, competition, and item trading.
  • Festival Polish — Festival-specific NPC dialogue, fireworks render pass during Lanternfest, food-stall sprites at the market plaza.