WaveController
The game needed escalating difficulty. Spawning the same three campers every round gets old fast. The WaveController manages wave-based spawning: each wave defines what to spawn, how many, how fast, and where. Between waves there’s a breather period. Difficulty scales by adjusting spawn rates, quantities, and introducing new entity types.
This is all config-driven — you define waves as data tables, not code:
waves = {
{ count = 3, delay = 2, type = "Camper" },
{ count = 5, delay = 1.5, type = "Camper" },
{ count = 5, delay = 1, type = "HungryCamper" },
}
The WaveController iterates through the table, spawning entities according to each wave’s spec, then waits for the wave to be cleared (or a timeout) before advancing. Adding a new wave is adding a row to the table. Adding a new entity type is registering it with the spawner — the WaveController doesn’t need to know anything about what it’s spawning.
Central Router
NPCs now have a configurable anchor system driven by a central Router. Instead of each NPC independently deciding where to go (which leads to clumping and weird pathfinding), the Router assigns destinations based on game state. Need all campers to gather at the campfire? The Router distributes them around the fire pit with spacing. Need them to scatter? Same system, different config.
The Router also handles the NPC anchor system — physical points in the world where NPCs can be assigned. Each anchor has a capacity and a type, and the Router matches NPCs to anchors based on availability and proximity. This prevents the “20 NPCs trying to stand on the same spot” problem.
Lib/Game pattern
The project structure was getting messy. Scripts, assets, and game-specific code all lived in one flat directory. We refactored to a Lib/Game split:
- Lib — Reusable framework code (System, GUI, InputCapture, Debug, etc.)
- Game — Game-specific code (campfire assets, scoring, run modes)
This separation matters because we’re already thinking about reuse. The framework should be game-agnostic. The campfire prototype is just one game built on it — and as it turns out, it won’t be the game we ship. The dungeon crawler that becomes IT GETS WORSE will use the same Lib with completely different Game code.
Also spent time adding debug logging for the invisible camper issue from yesterday, because we wanted to make sure it was genuinely fixed and not just masked by the ground-contact change.