Lab Journal
Plan 4 marathon: zones, roster, character creator, and trainer sprites

Plan 4 marathon: zones, roster, character creator, and trainer sprites

lab-journalvirusmon

Today was a full Plan 4 sprint on VirusMon — multi-zone foundation, world content, the virus roster, the character creator, and a bunch of polish that turned up along the way. By the end of the day the game has real maps, a 36-species roster, a paper-doll character creator, and the start of a trainer-preset system.

VirusMon

I started by reviewing the plan ledger and confirming Plans 1–3b were clean on main. The big blocker I'd flagged before was that ZoneRoom hardwired townMap/TOWN_SPAWN/TOWN_ENCOUNTERS instead of taking zone params, and zone transitions just didn't exist. That had to go first.

Plan 4a — multi-zone foundation. Refactored the room to accept zone parameters, added zone-change payloads typed from the shared protocol, and got the self-heal-via-reload path working when a zone re-join fails. The annoying bit was buffering roster and zone-change events across room handoffs — there's a window where the client has left one room but hasn't joined the next, and dropped messages cause weird ghost states. Fixed by buffering with a fail-closed dev gate. Also added a dev-only screenshot sink (window.__game handle) so I can grab milestone captures without manually framing them.

Plan 4b — world content. Pulled in a CC0 tilesheet and wrote a deterministic Tiled map generator that produces four maps. The big architectural win here: production zones now load from generated Tiled maps, and there's a proper parseTiledZone importer with tile-layer type guards. I froze the 4a maps as NODE_ENV=test fixture zones so room tests stop depending on production geometry — that's been a latent fragility for weeks. Native tilemap rendering with a scrolling 24x16 camera, and palette corrections from in-game verification (walls, roof, indoor tiles, tall grass all needed nudges). Route-2 ships with level 6–10 wilds.

Plan 4c — virus roster. This was the fun one. Brainstormed a real-malware name pool, then built a 36-species roster with archetype stats and 35 themed moves. Stage-scoped evolution on level-up, persisted through battle results. Rarity-weighted encounter tables with a guard for empty tables (learned that one the hard way during a test run). Added drain moves that siphon half damage as healing, with a test asserting the lifesteal lands before the faint — small but I wanted that ordering pinned down. Built the battle sprite pipeline with white-key processing and a rectangle fallback, plus dedupe for concurrent sprite loads (destroy-before-assign or you get tint bleed). Two new habitats shipped: route-3 and the server cellar.

Plan 4d — character creator. Schema v3 with persisted account appearance, a shared appearance model, character sheet, option manifest, and the paper-doll player rendering. The mirror meta-object in the zone importer lets me drop a lab mirror that triggers re-edit flow. The subtle bug here was that corrupt stored appearance was crashing joins — now it heals to null and sends the player through the creator instead. Also had to re-engage camera follow and self-tint after appearance rebuild, which I missed on the first pass and produced a very funny "camera locked on origin while the player walks off" bug.

Font fix. Somewhere in the middle of all this I finally fixed the blurry text. Root cause: every add.text call was missing fontFamily, so Phaser was rendering default Courier at 7–10px and then the 2.5× camera zoom was nearest-neighbor upscaling blurry serif glyphs. I self-hosted VT323 (a DEC terminal font, SIL OFL) rather than Press Start 2P because PS2P was overflowing roster titles, battle menu columns, and starter cards horizontally. Updated all five scenes to explicitly set fontFamily: 'VT323' and render text at native resolution via camera zoom. Crisp pixels everywhere now.

Trainer presets (in progress). Started on a trainer-preset system for the character creator. Got the design spec and implementation plan written, then hand-assembled the green-hood sprite from an irregular render. That was painful enough that I wrote tools/salvage_trainer.py — a reusable salvage tool for irregular renders — and used it for purple-tails. The blue-beanie and red-cap raw sheets are in; the plan now notes a facing-aware mirror-repair step because red-cap faces left and blue-beanie faces right, and the normalizer needs to handle both.

Also dropped some cruft along the way: unused adjustItem in roster.ts, dead setData and rows reset in RosterScene, and a stale xp comment. Small stuff but it had been bugging me.

Four plans landed today. VirusMon now has zones you can walk between, real malware to fight, and a character you actually create. That's the spine of the game.