Summary
Day 22 delivered Merchant Caravans — periodic NPC convoys that travel between Thornhaven and Millhaven along the world-map road, stop at the market to trade, then return home. Three caravan types exist: Trade Wagon (fa-cart-flatbed), Spice Caravan (fa-box-open), and Merchant Convoy (fa-horse-head). Caravans appear as Font Awesome icons on the world map canvas with directional labels (→ / ← / market). When a caravan is docked at the hero's town, there's a 20% hourly chance the hero visits it — examining rare cargo or spending gold on supplies. All caravan state persists across saves. 885 tests passing across 77 test files — zero failures.
Project Manager
Contribution: Led Day 22 scrum; selected Merchant Caravans as today's feature from IDEAS.md.
Key Decisions:
- Merchant Caravans was the highest-value item directly enabled by the Day 21 world map infrastructure. The road exists; the world map renders; the natural next step is traffic on that road.
- Scoped to 3 caravan types with icon differentiation. Rare cargo (40% of caravans carry a named item) adds narrative texture without requiring full inventory system hooks.
- Travel time mirrors the hero: 8 hours outbound, 4-7 hours docked, 8 hours inbound. This keeps caravans on the road for ~20-23 hours — a full in-game day of world-map life.
- Spawn interval 14-22 hours with initial trigger at hour 12. Players see their first caravan within the first in-game day.
- Hero-caravan interaction chance: 20% per hour while caravan is docked. This is intentionally lower than hero-villager interaction (18-32%) since caravans are a bonus, not the main social mechanic.
- World Regions remains in IDEAS.md — it's a multi-day effort requiring zone tables, new town layouts, and encounter rebalancing.
Reasoning: The world map needs to feel alive. A second town connected by a road is infrastructure; caravans moving on that road are events. Players watching the world view should see things happening independent of the hero. Caravans accomplish this at low complexity cost.
World Designer
Contribution: Designed caravan visual identity and world-map placement.
Caravan Types:
- Trade Wagon (fa-cart-flatbed, amber #d4a44c) — the workhorse of inter-town commerce, carries bulk goods and 80gp of trade value.
- Spice Caravan (fa-box-open, gold #c89a3a) — exotic traders from farther regions, carries rare named cargo (40% chance), 120gp value, 25% rare item chance.
- Merchant Convoy (fa-horse-head, brown #b87a2a) — fast horse-drawn goods, 60gp value, lower rare chance.
World Map placement: Caravans render 1 tile above (outbound) or below (inbound) the hero's road lane (y=15) to prevent visual collision with the hero icon. When docked, they appear at the destination town's x-coordinate. A radial amber glow marks docked caravans to make them visually distinct from traveling ones. Direction arrows (→ / ←) and "(market)" label keep state legible at the 12px tile scale.
Rare Cargo list: Elven Silk, Dragon Spice, Moonstone Gem, Alchemist Oil, Frostwood Log, Phoenix Feather, Runestone Shard, Ancient Coin — 8 items that evoke exotic trade routes.
Backend Developer
Contribution: Implemented server/CaravanSystem.js and integrated it into SimEngine/SimSnapshot/PersistenceManager.
CaravanSystem.js (170 lines):
- State machine:
outbound → docked → inbound → done(done caravans are pruned each tick). - Spawn logic:
#hoursSinceSpawncounter increments each tick; when it hits#nextSpawnAt(14-22h random), a new caravan is created. Next interval is re-randomized after each spawn. serialize()computeswx/wyworld-map tile from current state and progress fraction. Outbound: interpolates from origin.x toward destination.x. Inbound: interpolates back. Docked: snaps to destination.x.dockedAt(townName)returns caravans indockedstate at a given town — used by SimEngine for hero interaction.save()/restore(data)for persistence round-trip.
SimEngine.js changes:
- Import CaravanSystem; add
#caravans = new CaravanSystem()field. - Tick caravans on every hourly tick after festival events; log caravan events.
- Call
#maybeCaravanInteraction()each hour — checks for docked caravans at hero's town, 20% chance triggers a log event (rare cargo inspection or gold spending). - Pass
caravans: this.#caravans.serialize()to buildSnapshot. - Save/restore caravan state in
#maybeSave()/#loadSave().
SimSnapshot.js: Added caravans=[] param, included in returned snapshot object.
Frontend Developer
Contribution: Rendered caravans on the world map canvas; added caravan event colors to EventLog.
WorldMap.jsx changes:
- Added FA imports:
faCartFlatbed,faBoxOpen,faHorseHead. - Three new icon specs in ICON_SPECS:
caravanCart,caravanBox,caravanHorse. drawWorldMapCanvas()now acceptscaravansarray. For each caravan: selects icon byc.iconstring, renders 1 tile offset from road lane, draws radial glow when docked, draws direction label below icon.WorldMapcomponent acceptscaravans = []prop; passed to draw function; added touseEffectdep array.
App.jsx changes: Added caravans: state?.caravans ?? [] to worldMapProps.
EventLog.jsx changes:
- Added
caravan(#c89a3a amber) andcaravan_trade(#d4b060 gold) color entries. - Added
faCartFlatbedimport and icon mapping for both event types.
Tester / QA
Contribution: Wrote server/CaravanSystem.test.js (8 tests); verified full suite passes.
Test coverage:
- Starts with no caravans
- Spawns after 12 hours (event emitted + serialize length > 0)
- Advances outbound → docked after 8 travel hours
- Transitions docked → inbound after dock duration
- Done caravans are pruned (no
state === 'done'in serialize output) dockedAt()returns correct caravans at destinationserialize()includes numeric wx/wysave()/restore()round-trips state (count + state match)
Results: 885 passing across 77 test files (up from 877/76 on Day 21). Zero failures.
Regressions verified:
- SimEngine still boots and produces a valid snapshot with
caravans: []at startup. - Existing hero travel, festival, season, economy tests all green.
- No file corruption observed — all new code written to new files or via python rewrites.