Bringing a dungeon lift up through a crater lake in Godot
A 41 m shaft under a volcano's lake, a hole cut in the water mesh, a causeway the party can't jump off, and two map bugs it turned up.
The Breakout Site’s lift now comes out at the summit of the island’s volcano, in the middle of the crater lake. Plain concrete walls, 13 m per side, rise out of the water around the lift’s top. A doorway on the north side opens onto a causeway 4 m wide that runs 71 m across the lake to the crater’s rim, where it ends in a small walled yard.
The walls are a stand-in for a tower I’ll build later. The path down the volcano and the town aren’t built either.
Why the summit?
The lift used to come out on a shoulder of the volcano, 53 m up, because the summit didn’t fit the site when it was measured. I wanted it at the summit with a tower coming out of the water. For now I asked for the lift to be inside some walls in the middle of the lake with a path going to the edge.
How long does the shaft need to be?
The first estimate was about 62 m. That came from the site’s dug rectangle. The far corners of the rectangle are under falling ground and looked like they’d poke out of the volcano.
But those corners aren’t open squares. Measured over only the squares that are actually open, the shaft needs 39.1 m. That number is set by floor 4’s 14 m hall beside the lift, which has to stay under the lake bed at 290.6 m. I built the shaft at 41.2 m, which leaves 2.1 m of bed over that hall.
The site’s floors keep their stacking and only the top shaft grows. The other option was sinking the whole site. The dungeon marker has a new float field for it:
{"kind": "dungeon", "at": [960, 2880], "y": 315.7, "fields": {"top_shaft": 41.2}}
y is the deck, 4 m over the lake’s level of 311.73 m. A marker that’s given a y is no longer snapped to the terrain. Before the dungeon is built, a function reads the plan’s surface record, makes the plan deeper by the difference, and sets the top lift’s rise to the new length. It never goes shorter than the map’s, and a field of 0 puts it back.
My other lifts run at 1.5 m per second, which I’d said looked right. At that speed 41.2 m is a 27.5 second ride each way. So a top shaft that’s longer than the one its map draws gets a car just fast enough for a 12 second ride, which here is 3.43 m per second. The other three lifts are untouched.
Building the walls from boxes
Everything around the lift’s top is built by one script of static functions in the marker’s local space, out of boxes with a concrete material, so it all has collision. The walls are 0.8 m thick, go 3.6 m over the deck and 27 m down from it to under the lake bed. Three walls are solid. The fourth is solid under the deck, with two jambs and a lintel around a doorway 2.6 m wide and 2.8 m high. The deck is a slab with a 3 m square hole for the lift. The causeway is 0.5 m thick with a 1 m square pier every 12 m.
The path goes north because the crater is nearly round and every outer flank is 46 to 55 degrees. The north rim’s crest is the lowest and flattest of the four directions, at 315.6 to 315.7 m, which is level with the deck. So the path lands without a step.
Cutting a hole in the lake
The lake’s surface was one flat mesh written by the island’s generator, with no hole in it. It would have drawn water across the walls and down the shaft.
The generator now asks each lift head marker for a dry rectangle, which is the walls’ outer square minus the thickness of one wall, so the water’s edge is inside the wall. A polygon with a hole can’t be triangulated directly. So the lake’s outline is intersected with four bands around the rectangle (west of it, east of it, and the two between) using Geometry2D.intersect_polygons. Each piece is triangulated with triangulate_polygon, the winding is corrected, and the pieces go into one ArrayMesh saved over the lake’s old mesh with the water shader.
Why couldn’t the party walk off the end of the path?
My slope rule stops the party on ground steeper than 42 degrees. It stopped the party on the path’s last 2 m, where the causeway runs into a bank of 53 degrees and the terrain is within 0.75 m of the deck.
I exempted built floors from the rule. A node in a slope_floors group has a list of world boxes, from the floor’s top to head height. If the party is inside one, the rule returns the velocity unchanged.
Fencing the rim so nobody falls in
Deep water does nothing in this game. There’s no water rule, and no fall damage. A party that stepped off the path fell 17 m to the lake bed, walked around under the water, and couldn’t get out because the banks are too steep all the way around. A party that stepped over the rim slid down a flank it couldn’t climb back up.
So the causeway now ends in a walled yard: a level pad 8 m by 8 m with walls 1.4 m high all around, and the only gap is where the causeway comes in. This is my default until the way down exists, and the yard goes away when that’s built.
I had the party’s jump measured in the test, and it came to 0.91 m. The causeway’s parapets were 1 m high, which is only 0.09 m over the best jump. That’s no margin for a capsule’s round foot. The test found the party could jump onto a parapet, stand on it, and fall 22 m into the lake. I raised the parapets to 1.4 m too, which is 0.49 m over the jump, and they now run all the way to the yard.
The crest is only about 2 m wide and the flank is 7.5 m below the pad’s far edge, so a pad sitting on the ground would hang in the air. I didn’t cut the terrain. The pad sits on a solid box 10 m deep that goes down into the ground.
On the far wall there’s a plate that says WAY DOWN, and looking at it shows “// NOT YET GROWN. THE RIM HOLDS.” Pressing E does nothing. It’s a small Node3D in the interactable group that shows a chat bubble while the party is looking at it.
The test tries to get out:
- It probes each of the yard’s four sides with a ray every quarter meter, at 0.5 m and 1.3 m up. Every probe has to hit wall except at the path’s mouth.
- It walks at each side every half meter, runs at each side from three places with a jump early and late in the run, and takes the four corners diagonally while jumping.
- It scrapes along both parapets for their whole length walking, running and jumping, both ways.
The check is that the party never ends up more than 1 m below the deck.
Why did the map say I was on the fourth floor?
Standing on the deck, the minimap was empty and the map screen said FOURTH FLOOR UP with nothing on it. Paged down to GROUND, it showed lake and none of the structure.
The first guess was that the lake’s rectangles on the map hadn’t been cut. That wasn’t it. The map only draws water where the ground is under the water level and nothing is built there, and it had already marked the deck, path and yard as open.
The real cause was how the map decides which floor the party is on. It uses the party’s height over the terrain under it. On the deck the terrain is the lake bed, 17 m down, so the party counted as four floors up. What the party had seen was recorded on that floor too, so the ground level’s cells were never marked seen.
The fix looks at the solids, not the place. For each floor a streamed thing has on the ground level, the map survey samples the terrain under it about every 8 m. If the floor’s top over any of those points would put it on another level, the floor is raised. A raised floor gets a zone, a box from 1.5 m under the floor’s top (so a lift car coming up to the deck is inside it), grown 1.6 m past the floor’s edge. Inside a zone, the level is counted from the thing’s marker, the same as a dungeon’s floors. It works for anything built over water, a gorge or a pit.
Two sets of boxes had to be kept off the map: the wall under the doorway’s sill and the piers under the causeway. Their tops are at floor height, so they were drawn as a wall across the doorway and posts down the middle of the path.
Fixing a texel that was 5.0196 m and not 5
The map’s lake still lay across the yard after that, and a dry strip of rock crossed the water. That was a second bug, in the baked overview of the terrain.
The bake keeps every fifth vertex of a 512-vertex region. That’s 102.4 texels, truncated to 102 by integer division. But the bake wrote the texel step as 5.0 m:
overview.step = vertex_spacing * keep # old: 5.0
overview.step = side * vertex_spacing / tile # new: 5.0196
A height’s position is the region’s origin plus index times step, so every region further from the world’s north-west corner was off by about 2 m more. At the lake the map’s ground was 25 m west and 32 m north of the real ground, and 40 m off at the far side of the 10 km island. The mean distance between the map’s heights and the real ground around the lake went from 13.2 m to 1.36 m.
A bake from before the fix is now reported as stale, so the island’s overview has to be baked again, which took 6.4 s. My 2 km worlds and 16 km islands weren’t affected because their step divides a region evenly.
What’s left?
- The tower, the path down the volcano and the town.
- What deep water does to a party. Only the way into the water from the path is shut.
- The crest shows through the floor where the path meets the yard. The ground there is up to 0.3 m over the deck.
- The map’s heights are still half a texel off, 2.5 m on the 10 km island.
- Whether the 12 second ride at 3.43 m per second feels right. I’ll judge it from the film.