Revert the migration's square spawnBounds back to homeRange (the lossless inverse), which
restores main's exact homeRange lines and is far more readable for the common vendor/wildlife
case. Data diff vs main collapses to essentially just type->$type (5.9k from ~250k). ~39
genuinely non-square records keep spawnBounds. Whitespace+representation only; values and
loaded bounds are unchanged (verified by AllSpawnFilesLoad/BoundsEquivalence/LegacyHomeRange).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The migration step pretty-printed every file (JSON.stringify indent 2), exploding
location/entries/spawnBounds across many lines (~250k-line diff). Restore the original
compact layout: a container renders inline only when all its values are scalars and the
line fits in 100 columns, so `location`/`spawnBounds`/each entry inline while `entries`
stays expanded (uniform across single- and multi-entry spawners). UTF-8 without BOM
(the old BOM was an artifact of the lost external exporter; the in-game [ExportSpawners
already writes no BOM). Whitespace-only; values unchanged. Cuts the data diff ~95%.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Rename "type" -> "$type" (first key, required for STJ polymorphism) in
all 109 spawn JSON files (5,612 records).
- Convert "homeRange" + "location" -> "spawnBounds" object, dropping homeRange.
Runtime keeps reading homeRange so un-migrated/external files still load.
- Add BoundsEquivalenceTests (hr=0/1/3/7) validating that the script's toBounds
formula produces the same Rectangle3D as the runtime homeRange path.
- Add MigratedDataLoadTests loading a real migrated file as List<SpawnerDto>
and round-tripping each DTO through ToSpawner().
- Add tools/spawner-json-migrate/migrate.mjs (offline Node script).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
### Summary
- Add z-restricted spawnBounds to 35 spawners across 15 files to prevent creatures from spawning on incorrect floors
- Building spawners (Pyramid, Fire dungeon, Shame, Vendors) use minZ = spawner_z to prevent spawning below
- Ground level spawners (Graveyards, Outdoors, Tokuno) use minZ = -128 for natural terrain variation
- Multi-floor structures in Ilshenar use floor-specific Z restrictions
### Test plan
- Verify spawners in Fire dungeon building don't spawn creatures on floors below
- Verify Pyramid spawners keep creatures on their respective levels
- Verify ground level spawners (graveyards, outdoors) still spawn correctly on terrain
- Check Ilshenar multi-floor structure spawns creatures on correct floors