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_NAMESis 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 ifday % FESTIVAL_INTERVAL_DAYS === 0and the current hour matches the festival'sstartHour. 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_NAMESarrays; now imports fromNameGenerator.js. FAMILY_NAMESis re-exported (asALL_SURNAMES) for backwards compatibility withHouseholdSystem.jsand the surname tests.- New
celebratingflag (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 overNameGenerator.
server/ChildSystem.js
- Imports
NameGenerator. #advancePregnancieswalksCHILD_NAMESlinearly for the first 24 children, then falls back toNameGenerator.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. Setsv.celebratingif matched (and not sheltering).
server/SimEngine.js
- New
#festival = new FestivalSystem()field. snapshotnow includesfestival: 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()tonpcs.tick. - Resting hero-villager interaction chance lifted from 18 → 32 during festivals — crowds gather at the market.
pickHeroName()helper draws fromNameGenerator(heroic + arcane mix).#loadSaverestores festival state;#maybeSavepasses festival to persistence.
server/PersistenceManager.js
SAVE_VERSION = 10.save()acceptsfestivalparameter and serializesfestival.serialize().
client/src/App.jsx
worldMapProps.festivalplumbed through.- Desktop top bar shows a styled festival pill (icon + label) when
state.festival.currentis non-null. - Mobile top bar shows the festival emoji alongside the weather emoji.
- New helpers
festivalIcon(type)andfestivalLabel(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 Chronicleslink, 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/heroiceach draw from the corresponding pool;anyand 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
() => 0function 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.jsstill passes —FAMILY_NAMESis now re-exported fromNameGenerator.js(ALL_SURNAMES), so any code that didFAMILY_NAMES[id % FAMILY_NAMES.length]simply gets more variety.NPCManager.test.jsstill passes — thetick()signature change is additive (two new optional params with[]defaults).Villager.test.jsunaffected — 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 helpersserver/FestivalSystem.js— five-festival engine with calendar triggerserver/Villager.js— celebrating flag + NameGenerator wiringserver/NPCManager.js— fourth tick param for celebrating professionsserver/ChildSystem.js— NameGenerator fallback for surplus childrenserver/SimEngine.js— festival integration (tick, snapshot, save/restore, interaction boost), hero name pickerserver/PersistenceManager.js— SAVE_VERSION 10, festival persistedclient/src/App.jsx— festival header pill (desktop + mobile)server/NameGenerator.test.js— 16 testsserver/FestivalSystem.test.js— 16 testsBUGS.md— names-variety bug marked fixed; Open list now emptyGOALS.md— Day 19 milestone addedIDEAS.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.