FireMaze — Multilevel Mazes

Configure vertical floor transitions, stair geometry, and floor thickness for multi-floor mazes.

← Back to FireMaze

1. Overview

FireMaze supports up to 20 vertical levels. When floors > 1:

  1. The 2D maze grid is cloned into a 3D array cells[z][y][x] via _expand_cells_to_3d.
  2. Staircases are generated to connect adjacent floor levels.
  3. All vertical positioning (walls, floors, roofs, props, vertex painting, editor raycasts) uses level_height = wall_height + floor_thickness instead of wall_height alone.
  4. The interactive editor provides a stair tool for placing, removing, and rotating stairs in the viewport.

2. Floor Configuration

Maze Settings panel

Floors

floors — Integer, 1–20, default 1

The number of vertical levels in the maze. When set to 1, the maze is single-level (no stairs, no 3D cell expansion).

When floors > 1:

  • The generation algorithm expands the 2D grid to 3D by copying the base layer to all floors.
  • Stairs are placed on random open cells between each floor transition.
  • Image masking is automatically disabled (masked cells would break stair connectivity).
  • The Stairs Per Level (stair_count), Stair Footprint, Stair Style, and Stair Direction controls appear in the panel.

3D Cell Expansion

The function _expand_cells_to_3d in common_helpers.py:

  1. Copies the 2D cell grid to floors layers: cells_3d = [deepcopy(cells_2d) for _ in range(floors)].
  2. Calls _place_stairs to carve openings and place stair records.
  3. Returns the 3D cell array and the list of placed stair definitions.

Stair footprint cells are force-opened on both the source floor and the destination floor by _force_cell_open.

3. Level Height

When floor_thickness > 0, the effective height of each floor level changes:

level_height = wall_height + floor_thickness

This value is used across the entire codebase for vertical positioning:

SystemUsage
Mesh builders (rect, polar)z_off = z * level_height for floor, wall, roof, stair placement
Guide pathSpline point Z = ho + z_coord * level_height
Vertex paintingFloor level mapping from vertex Z: z = int((pz - 1e-6) / level_height)
Prop spawningProp Z = z * level_height + 0.6 * wh (torches) or z * level_height (chests/doors)
Interactive editorFloor hit detection: z_hit = int(offset_loc.z / level_height), face direction: ROOF if loc.z > (z_hit * level_height + wh * 0.5)

4. Stair Generation

Algorithm: _place_stairs in maze_algorithms/common_helpers.py (rectangular) and inline in polar_maze.py

Candidate Selection

For each floor transition (from floor z to z+1), the algorithm collects candidate cells that are:

  • Open (not walls).
  • Not on the outer perimeter (at least 1 cell away from all borders for rectangular grids).
  • Not already occupied by another stair footprint.

For polar mazes, candidates are open cells on rings r >= 2 (not the center or first ring).

Stairs Per Level

stair_count — Integer, 1–50, default 1

The number of staircases to place between each adjacent floor pair. If stair_count exceeds available candidate cells, all available candidates are used.

Stair Footprint

stair_footprint — Enum: 1x1, 1x2, 2x2 (rectangular only). Polar always uses 1x1.

The footprint defines how many cells a staircase occupies on each floor.

1x1 (Spiral)

  • A single cell footprint.
  • The stair occupies exactly one cell on both the source and destination floors.
  • The procedural geometry is a spiral staircase: central post + 12 wedge steps with a 360° rotation, rising the full wall_height.

1x2 (Straight)

  • Two cells in a line.
  • The second cell's position depends on orientation:
    • N: (x, y-1)
    • S: (x, y+1)
    • E: (x+1, y)
    • W: (x-1, y)
  • The starting cell receives the procedural stair geometry or custom stair mesh; the second cell is carved open on both floors to serve as a landing.

2x2 (U-Turn)

  • Four cells forming a 2×2 block: (x, y), (x+1, y), (x, y+1), (x+1, y+1).
  • The starting cell receives the procedural stair geometry or custom stair mesh. The remaining three cells are carved open on both floors as landing/open shaft.

Stair Style

stair_style — Enum: stair (Staircase), ramp (Ramp)

Controls the procedural geometry type:

StyleGeometrySteps
Staircase (stair)Spiral staircase: central cylindrical post + 12 wedge-shaped steps, 360° rotation. Includes top landing platform.12 steps, rise_per_step = wh / 12
Ramp (ramp)Sloped quadrilateral top surface with solid wedge side panels. Runs in the +Y direction.Continuous slope

Procedural Geometry Details

Spiral Staircase (_build_spiral_stair_1x1):

  1. Top landing platform — A flat rectangular slab at z_offset + wh (aligned to +Y side).
  2. Central post — 8-segment cylindrical column from z_offset to z_offset + wh.
  3. 12 wedge steps — Each step:
    • Top face (winding corrected for upward normal).
    • Bottom face (downward normal).
    • Outer riser, inner riser, CW side (back riser), CCW side (front riser).

Ramp (_build_ramp_1x1):

  1. Top surface — Sloped quadrilateral rising from z_offset at -y to z_offset + wh at +y.
  2. Bottom face — Flat rectangle at z_offset.
  3. Side panels — Triangular wedge at x = -t2 (left) and x = t2 (right).
  4. Front face — Vertical wall at the high end (y = t2).

Stair Direction

stair_direction — Enum: N, E, S, W

Controls the orientation/facing of placed stairs:

Grid typeCompassPolar equivalent
RectangularN / E / S / W
PolarN → CCW, E → OUT, S → CW, W → INCCW / OUT / CW / IN

When set to 'random' (during generation), a random orientation is chosen for each stair.

Polar Stair Placement

In polar_maze.py, stair placement follows the same pattern with polar-specific coordinate mapping:

  1. Candidate open cells on rings r >= 2 are collected per floor.
  2. Orientation is chosen randomly or mapped from stair_direction to polar orientation.
  3. Each stair is defined as {'z': z, 'x': theta, 'y': r, 'type': style, 'footprint': '1x1', 'orientation': polar_orient}.
  4. Footprint cells are force-opened on both levels.
  5. All polar stairs use 1x1 footprint only.

5. Floor Thickness

floor_thickness — Float, 0.0–10.0, default 0.0

Adds physical depth to the floor slab between levels. When greater than zero:

  • Floor tiles are generated as 6-face boxes (top, bottom, 4 sides) instead of single quads.
  • The function _add_floor_tile_transformed generates:
    • Top face (walkable surface, Z=0 local).
    • Bottom face (underside, Z=-thickness local).
    • 4 side faces joining top and bottom edges.
  • Each face receives its own material index (0 for top, 1 for bottom, 2 for sides).
  • Custom floor/roof meshes are placed at the top surface and do not scale with thickness (a UI warning is shown when custom meshes are used with thickness).
  • All vertical positioning uses level_height as described above.

Material Slots

When floor_thickness > 0, generated floor tiles use three material slots:

SlotFaceUsage
0TopWalkable surface — typically same as floor material
1BottomUnderside slab
2SidesEdge faces of the slab

6. Interactive Editing

When floors > 1, the editor provides stair-specific controls.

Floor Level Selector

edit_floor_level — Integer, 0 to floors-1

Targets a specific floor for editing. All clicks (wall toggle, mesh cycle, stair tool) apply to this floor level. Automatically clamped to valid range on entering edit mode.

Stair Tool

When the edit tool is set to Toggle Stairs (edit_tool = 'stair'):

ActionResult
Left-click on open cellPlaces a stair connecting the current floor to the level above. Both the clicked cell and the cell above are force-opened.
Left-click on existing stairRemoves the stair.
Shift+left-click on existing stairRotates orientation: N→E→S→W→N (rect) or CCW→OUT→CW→IN→CCW (polar).
Left-click on top floorReports warning — cannot place stairs on the top floor.

The stair is created with the current property values (stair_style, stair_footprint, stair_direction). For polar mazes, compass direction is mapped to polar orientation.

Dirty Cell Tracking

Stair edits mark cells on both the source floor (z_hit) and destination floor (z_hit + 1) as dirty, plus their Moore neighborhoods (rectangular) or overlapping sectors (polar). Both floors are incrementally rebuilt.

Edit Helper

The hidden helper mesh is generated with force_simple=True, producing flat-faced geometry across all floors for precise raycasting.

7. Prop & Vertex Integration

Vertex Painting

Vertex painting maps each vertex to its floor level:

z = max(0, min(maze_data.floors - 1, int((pz - 1e-6) / level_height)))

Dead-end detection, path highlighting, and distance gradients all operate per-floor across all levels.

Prop Spawning

Props are spawned per floor:

  • Torches at z * level_height + 0.6 * wh.
  • Chests at z * level_height.
  • Doors at entrance (floor 0) and exits (top floor).

Stair cells are excluded from chest placement via _build_stair_top_bottom_sets, which returns all cells occupied by stair footprints on both the source and destination floors.

Guide Path

The BFS guide path operates in 3D across all floors, finding the shortest route through the multilevel maze including stair connections between levels.

8. UI Reference

Maze Settings Panel

ControlPropertyShown when
FloorsfloorsAlways
Floor Thicknessfloor_thicknessAlways
Stair Footprintstair_footprintfloors > 1 and rect grid
Stair Stylestair_stylefloors > 1
Stair Directionstair_directionfloors > 1
Stairs Per Levelstair_countfloors > 1

Generation & Editing Section

ControlPropertyShown when
Floor Leveledit_floor_levelfloors > 1
Edit Tooledit_toolfloors > 1 and editing
Stair Direction (editor)stair_directionedit_tool == 'stair' and editing

Alert Box (Edit Mode)

When editing with floors > 1, the alert box shows:

  • "Floor {n} of {total}"
  • Tool-specific shortcut hints for stair placement/rotation.