Birdoggydog's Builds

Counting missing monsters and speeding up dungeon sheets

A census showed none of the 58 dungeon monsters were missing, a fit check got three times faster, and eight ideas for cheaper dungeons were filed.

None of the Breakout Site’s monsters were missing. I thought a new game showed far fewer than the hundred or so I’d added, so I had an agent count them. Along the way it made reading a dungeon sheet three times faster, and it wrote up eight ways to make the next dungeon cheaper to build.

Why did a new game seem to have so few monsters?

Four floor plans in beige on black with colored dots for monsters, labeled with how many are in the sheet and how many in the game.

The four floors drawn from a new game’s census. Floor 1 has 36 monsters in the sheet and 36 in the game, and floors 2 to 4 have 9, 8 and 5.

I started a new game from the menu and, after the character creation room, saw far fewer monsters than I expected. So a new test, tests/monster_census.gd, goes through the menu the way a player does, picks the four tables, presses BEGIN and counts every monster. Then it checks each one against the sheet. It passes 14 of 14 checks, headless and in a window.

A new game has 60 monsters: the 58 from the sheet (36 on floor 1, then 9, 8 and 5) and 2 training dummies in the yard. Each one is spawned on the square the sheet gave it, stands on the floor and has its model at the height of its kind. The 109 I’d seen mentioned was the development start, which adds 42 monsters from the old field and 9 from two other dungeons.

Monsters that are asleep still exist. The sleeping rule I added earlier only skips their physics step, so they’re drawn like any other.

A first person view down a narrow corridor covered in red meat growth with two name and health bars for Meat Sacks in the middle.

The ward corridor on floor 1, the busiest stretch. Two Meat Sacks sit under the meat growth.

The real reason is that the floor is 108 m across and has about one monster per room. Only 6 are within 20 m of the ward corridor, and the Meat Sacks are under 1 m tall and gray-red in failing light, among the lumps of the meat layer. I picked “leave them as they are” on my review page, so there are no more monsters and the look stays.

How do you make a fit check three times faster?

Reading the Breakout Site’s sheets took 1.17 s, and I wanted to know where it went. I added a timer around each stage of building a dungeon from a sheet, ImportedDungeon.stage_usec, filled in by a small _lap() helper, and a test, tests/dungeon_build_stages.gd, that prints the stages. One function, DungeonPlan._check_fit(), took 0.88 s of the 1.17 s.

The fit check goes through every pack of monsters and asks which squares a body of that size can reach. It did that twice per pack, once for the biggest body the pack could grow to and once for the smallest, flooding the whole floor each time. Floor 1 has 36 single-monster packs, so that was 72 floods of the same floor.

The squares a body can pass that connect to each other form one stretch, and every square in a stretch reaches the same squares and is stopped by the same mouths. So the new _reach_kept() keeps a dictionary from the body, [wide, tall, flies], to the answers it has already worked out, and a pack that starts in a flooded stretch is handed the answer. A start the body doesn’t fit on is worked out alone, because it also reaches the stretches next to it.

I dumped the check’s lines, warnings, errors and pens for every sheet before and after, and they were byte-identical. Reading all the sheets went from 1,226 ms to 322 to 388 ms. A floor-1 build is 1.36 s, and 0.67 s of that is the furniture, which I’ve filed as its own item.

Why does the slowest test take so long?

dungeon_shapes walks the party around seven dungeons, and it had taken 902 s alone before the sleeping-monsters change and 218 s after. Headless, the engine takes one physics step per frame at 120 a second, so its 24,740 steps wait 206 s of real time.

I added GD_FAST=1 to the Godot wrapper script, which adds --fixed-fps 120 to the headless run. Each frame then counts as 1/120 s of play no matter how long it took, so the same 24,740 steps come as fast as the machine makes them. The test took 137 s and passed 74 of 74 checks. Engine.time_scale = 8 took 40 s, but each step is coarser, so I dropped it. GD_FAST is opt-in because it breaks the tests that measure real frame times.

Where do the tokens for a dungeon go?

Over the last six days, dungeon work was 129.5 M weighted tokens of the 682 M I used in total, across 95 agent runs. Dressing models took 66 M, placement and import features 43 M, and the sessions that drew sheets 14 M. The sheet itself isn’t the cost. The Breakout Site’s sheet is 50 KB with 58 packs, 31 notes, 27 doors, 10 traps and 6 chests, and sheet JSON was 0.5 percent of what the agents carried. Bash output was 22 percent, the model kit 22, code reads 18 and 609 pictures 9.

So the eight proposals are about taking placement and checking off the agents. Monsters placed by a rule instead of 58 hand-written rows, room kinds in content/room_kinds.json that decide a room’s furniture, monsters, chests and traps, an overhead map the importer draws so nobody needs screenshots, fixtures as table rows instead of importer code, and sheets generated from a list of rooms and links. I chose those five to build before the next dungeon, and they’re in progress. The other three are cleanups: 36 lines of harmless UID warnings printed by every Godot run, running the walking tests with GD_FAST in the suite, and building the site’s furniture as one MultiMesh per part.