Making a generated island the default world in Godot
The procedural island replaced the hand-built scene as the play world, with a marker overlay file, a start inside the dungeon and the old world kept for tests.
The island my world generator makes is now the world the game runs in. A new game wakes the party in a vat inside the Breakout Site, they walk to the lift on floor 4, ride it up 15.6 m, and come out on the island with the volcano above and the inner sea below.
Why did the world change?
When I saw the Breakout Site with its lifts working, it was in the old hand-built world scene. I said it was amazing but it had been placed in the test world, and the real world is the generated one I use for exploring. The old one needed to go.
I didn’t delete the old scene, though. Twenty tests, the content validator and the content registry all run in it, and the old game’s places and quests only exist there. So I moved it into the tests’ fixtures and every reference now calls it WorldScene.FIELD. The old places aren’t on the island yet. I’m leaving them for now, and quests are a separate thing I can mess with later.
Choosing the world, and what happens when it isn’t built
WorldScene has a DEFAULT constant, which is now the painted island’s scene, and a FIELD constant for the old one. It also has an exported start_in, which is the dungeon a new game begins inside, and a static plain_start flag that tests and the explore tool set when they want to start on the ground.
The island is a build output and it isn’t in git, so a fresh checkout doesn’t have one. A new script, WorldMissing, checks whether the island is built, and if it isn’t, puts a notice on screen saying how to build it. New game, Continue, Load and the skip-intro path all ask it first, and they show the notice and start nothing. An --old-world flag still plays the old scene. The island’s tests skip themselves in a checkout with no island.
Keeping hand-placed markers out of a generated scene
The island’s scene file is written by the generator, so a marker I placed in it by hand would be lost on the next build. The things the generator doesn’t make are kept in a separate JSON file, keyed by island:
{
"wishbone.small.paint": {
"start_in": "breakout_site",
"markers": [
{"name": "...", "kind": "dungeon", "id": "breakout_site",
"at": [1206, 2528], "turn": 0, "why": "..."}
]
}
}
The generator reads it and puts the markers in. at is x and z, and the marker is placed on the island’s ground height at that point unless a y is given. turn is in quarter turns. A --scene-only option rewrites just the scene without regenerating the terrain, which takes minutes and not the full build.
Where could a four-floor dungeon go?
Placing the site took the longest. Every ceiling in it has to be under the ground. The site is 171 m by 120 m, and floor 4 has a tall hall only 1.6 m under the lift’s top, so it needs ground that’s both high and gentle.
The summit doesn’t fit. The top of the volcano is a rim about 320 m up around a crater lake, with flanks of about 32 degrees. The highest ground that worked is a shoulder on the volcano’s north-east side, 53 m up. The marker went in at (1206, 52.7, 2528), which puts floor 1 37.6 m under the ground.
Starting a new game inside the dungeon
The dungeon module reads the world’s start_in as the last of its sources for where to start, and starts the party inside that dungeon.
There was an ordering problem. A real opening makes the party after that start has happened, so the party is empty at the time. A game that starts inside is supposed to begin with nothing, and the party used to be made with eight starting items. So a function hooked to the party’s members_changed signal strips the starting kit as each member is made. The result is four characters with nothing on them.
A new game is ready in 2.1 s headless. A real opening through to the party being created takes 8.2 s, and 6.5 s of that is the opening’s wait.
What broke?
I ran the whole suite on the branch: 137 tests in 35 minutes 40 seconds, with 9 failing. Three were mine:
- Two tests assumed the party starts on the ground. They set
plain_startnow. - The controls test caught the new notice reading the raw Escape key. It goes through the game’s escape check now.
- My performance budget rig counted nodes under hidden parents, and the site’s floors sit under hidden parents. The rig now only puts away what’s actually showing in the tree.
Five of the others fail the same way on the main branch, and one was a baseline that the merge brought in. I fixed the three and re-ran them green, but I didn’t run the whole suite again after that.
I also put the island’s rivers into the game with a frame-time test. The nearest creek is over 200 m from the lift, so none show in the film.
In a window by the lift top, the frame time averaged 6.0 ms with a worst of 7.9 ms, at 992 draw calls and 0.70 million triangles. Video memory was 478 MB, and 11 of the island’s 982 terrain chunks were loaded.
What’s left?
On 11 October I asked for the lift to come out in the middle of the summit’s crater lake, inside plain walls, with a path to the shore, to be built into a tower later. That isn’t in this build. The lift still comes out on the shoulder.
I measured what the summit would take. The crater lake is 160 m square with its surface at 311.73 m and 21.2 m of water in the middle. Outside the rim the ground falls to 247 m at 160 m from the center. The first estimate was that every ceiling would have to be under about 250 m, which makes the top lift about 62 m and not 15.6 m. The lake is also one flat mesh that would cut through the shaft, and the hole in the terrain would be under 21 m of water.
Two more things are open. The old game’s places and quests aren’t on the island, so the content validator reports 28 errors there. And a save doesn’t say which world it was made in.