Birdoggydog's Builds

Building a walkable deck down a volcano to a town in Godot

A Python tool routes a 4 m path on piers from the crater's rim through a ridge to nine stand-in buildings. Three faults turned up when I walked it.

There’s a way down the volcano now. The yard on the crater’s rim has a gate in its far wall, and from it a deck 4 m wide on piers drops 299 m by four hairpins, crosses the plain, goes through the mouth of the curled ridge and ends in a street with nine stand-in buildings. The deck is 1,947 m long, and it’s about seven minutes of walking from the lift to the town.

60 seconds, captioned. From the lift in the crater lake along the causeway, out of the new gate, down the deck and its four hairpins, over the plain and through the ridge’s mouth into the town, into the Bunkhouse, up the street and a look back at the mouth and the volcano.

It’s only partly done. The deck, the buildings and every name on them are stand-ins for a path generator and a building generator I haven’t made yet. Nobody is in the town and nothing is in the buildings.

The lift that comes up in the crater lake is in an earlier post. That step left the party on the rim with nowhere to go.

Where does the town go?

My plan for the vertical slice ends at an empty town that’s reached from the top of the volcano. An agent first put the town on the open plain at the volcano’s foot, 600 m from the summit, on the flattest ground near the foot on the causeway’s side. I wanted it inside the raised, curled ridge whose opening faces the volcano, so I asked for a picture of the possible spots.

A map of an island with north up and grid lines. A volcano with a crater fills the lower half and a ridge curled like a hook sits at the top. Four colored circles are labeled A inside the ridge, B in the ridge's mouth, C where the worker put it, and D east foot. Two white dots mark the lift in the crater lake and the gate on the rim.

The four spots I chose from. A is inside the curled ridge, B is the ridge’s mouth, C is the first spot on the plain, and D is the east foot. The white dots at the bottom are the lift and the gate on the rim.

I picked A for the town. B, the ridge’s mouth, is where I want a big fight between the townspeople and meat monsters later, so it stays open and the path crosses it. The town’s position is an input to the tool, so moving it was a change to four constants and a rerun.

Why can’t the party just walk down the ground?

I measured the volcano first. The cone falls 300 m in about 400 m, which is 37 degrees on average, and four of its faces are 47 to 65 degrees. The ground is roughened at 1 m, and only 2 to 25 percent of it is under 35 degrees, depending on the face.

My slope rule stops the party on ground over 42 degrees. It samples around the party, so it also stops a party that’s only crossing a face. No line on the ground can be walked, not even one across the slope.

So the path is built over the ground as a deck on piers, like the causeway across the lake. The deck’s boxes are in the slope_floors group, and the slope rule leaves the party alone while it’s standing inside one of them. I didn’t have to cut the terrain. The game has no fall damage, so the parapets are only there to stop a party going over by accident.

Finding the route from the heights

The route isn’t drawn by hand. A Python tool reads the island’s raw heights, works out the route and the town’s plots, and writes them into the island’s markers file. Two new scripts build from those markers, BuiltWay for the deck and StandInBuilding for the buildings.

A shaded height picture of the island. A white line leaves a round crater at the bottom, zigzags down the cone in long curved legs joined at red dots, runs straight across flat ground, bends into the opening of a curled ridge at the top and ends in a straight street with nine small yellow boxes beside it.

The route as the tool draws it over the island’s heights. The white line is the path, the red dots are the hairpins, the crater is the ring at the bottom, and the yellow boxes are the nine buildings along the street.

The tool smooths the heights with a 10 m Gaussian. Then it draws a line in 2 m steps that falls at a constant grade of 0.25 (rise over run) on the smoothed ground. At each step it takes the downhill direction and the direction across the slope, and turns from across toward downhill by asin(GRADE / slope). That’s the angle that makes the fall exactly the grade.

turn = math.asin(min(1.0, GRADE / slope))
heading = eased(heading, (along[0] * math.cos(turn) + down[0] * math.sin(turn),
                          along[1] * math.cos(turn) + down[1] * math.sin(turn)))

The line runs one way around the cone until its bearing from the summit reaches the next value in LEGS = [70, 12, 60, -12] degrees, and then it turns back. The last leg runs to the foot, which is where the smoothed ground is under 30 m high and gentler than 0.2. A hairpin is a level U: a 2.5 m stub straight on, 4.6 m across and downhill, and a 2.5 m stub back. From the foot the line goes by two waypoints, around the ridge’s south tip and through the middle of its mouth, to a street that runs 169 m up the hollow.

eased() limits how far the heading can turn in one step to 8 degrees. A second pass cuts the 2 m line into straight pieces. It keeps a landing’s points, and between them it cuts wherever the line leaves the straight by 0.12 m or a piece gets longer than 14 m. That gave 253 points.

The line only says where the joints are in plan. A third pass sets each joint’s height. It starts each joint 0.1 m over the ground, sets the first at the yard’s deck (315.7 m), and iterates up to 400 times with these rules:

  • Every piece is raised until the whole deck, plus 0.5 m past its edge, is over the real ground. It samples a 0.5 m grid under the piece.
  • All of a landing’s points are set to the highest of them.
  • The path never climbs on the way down the flank.
  • No ramp is steeper than 0.34, which is 18.8 degrees.
  • The ground counts as 0.8 m higher under every river, so the path bridges the creek it crosses on the plain.

On the flank the deck ends up at most 17.7 m over the ground.

Building the deck from 648 boxes

BuiltWay makes a piece from every pair of points. A piece is a straight box 4 m wide and 0.4 m thick. A hairpin is one level slab around the whole turn with separate walls on its four sides, each open for a deck’s width where the path comes on or goes off. At a bend that isn’t a hairpin, each piece runs on past its joint by wide/2 * tan(turn/2), so the outside of the bend is closed. Piers are 1 m square boxes under any point that’s at least 1.6 m off the ground.

What’s drawn is two merged meshes, the deck and walls in concrete and the piers in a dark material. I built them with SurfaceTool.append_from over a BoxMesh for each box, because one mesh per box would be about a thousand draw calls. What’s solid is one BoxShape3D per piece, 648 in all, and they build in 120 to 340 ms. Each collision box is tagged as floor, wall or under the floor so the map can tell them apart. The deck falls 300 m under one marker, so height alone can’t.

The parapets are 0.3 m thick and 1.4 m high on both sides for the whole flank, because the party’s best jump is 0.91 m.

What went wrong when I walked it?

The test turns the party toward the next point of the path and holds the forward key, nothing else. It fails if the party stops, goes under the deck or drops more than 0.6 m. That found three faults.

The first walk stopped 0.5 m past the gate. A parapet used to be as long as its piece. Where the path turns, a piece runs on past the joint to close the outside of the bend, and that put the piece’s inside parapet across the deck of the next piece. Off the gate the path turned 68 degrees onto a 2 m ramp, and the same thing happened at the foot with a 72.6 degree bend. I fixed it in two places. Each parapet now gets a separate length, so the outside one runs on with the deck and the inside one stops short of the joint by the same amount. And eased() in the tool brings the path around from the gate and around to the street in arcs. The sharpest bend off a landing is now 11.4 degrees.

var lead := absf(bend.x) * (2.0 if signf(bend.x) == way else 0.0)
var tail := absf(bend.y) * (2.0 if signf(bend.y) == way else 0.0)
var long := across.y - lead - tail

The second fault was at crests. Where a ramp was a lot steeper than the one before it, a walking party left the floor and landed half a meter down. The height pass got another rule: a ramp can be steeper than the one before it by no more than 0.12.

The third was the street. Its deck was up to 0.72 m over the lowest ground beside it, and the party can only step up 0.5 m, so it couldn’t get onto the street from the side. The street is now laid in 4 m pieces so it sits closer to the ground, at most 0.48 m over it. Each building also has a 5.3 m apron that runs from the door’s sill onto the street’s deck.

The test now walks down in 396 s and back up in 413 s, including the causeway. It also goes into and out of every building and jumps at the parapets of three ramps and at every side of a landing.

Putting nine stand-in buildings on a street

StandInBuilding builds a form picked by name.

FormAcross x deepWallsDoorwayRoofLook
house8 x 7 m3 m1.8 x 2.4 mpitched, 35 degreespale bone walls, hide roof
hall16 x 10 m4.2 m2.6 x 2.9 mpitchedthe same
cyber9 x 9 m7 m2.2 x 2.6 mflatdark metal with a lit strip around the top and down each side of the door

There are six houses, two halls and one cyber block that’s clearly not part of the town. Five are east of the street and four are west, 22 to 130 m up it. The ground under a building isn’t level, so the marker is in the middle of the floor at the floor’s height, and a footing goes down from there into the ground. The floor is set 0.15 m over the highest ground under the footprint, which the tool samples every 0.5 m.

Three buildings have a sign over the door: “The Bunkhouse”, “The Long Table” and “Splice & Solder”. The town is called “Rindfoot”. I was fine with those as stand-ins as long as they’re easy to change, so they’re all constants at the top of the tool. The Bunkhouse has a safe house marker inside it.

The tool also writes two places. One is a box around the street, and the game can ask whether the party is in it, but nothing uses it yet. The other is the ridge’s mouth, 150 m square, and no building goes inside it.

What’s left?

  • The deck is plain concrete boxes on square piers with no lamps, signs or sound. Over the plain it’s a bare strip with an 83 degree corner at the ridge’s mouth, and I haven’t looked at how the map draws it.
  • The nine buildings and every name are stand-ins.
  • Reaching the town does nothing yet.
  • The fight in the ridge’s mouth is a later task.
  • The tower in the lake isn’t built.