1. Overview
FireMaze supports up to 20 vertical levels. When floors > 1:
- The 2D maze grid is cloned into a 3D array
cells[z][y][x]via_expand_cells_to_3d. - Staircases are generated to connect adjacent floor levels.
- All vertical positioning (walls, floors, roofs, props, vertex painting, editor raycasts) uses
level_height = wall_height + floor_thicknessinstead ofwall_heightalone. - 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:
- Copies the 2D cell grid to
floorslayers:cells_3d = [deepcopy(cells_2d) for _ in range(floors)]. - Calls
_place_stairsto carve openings and place stair records. - 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:
| System | Usage |
|---|---|
| Mesh builders (rect, polar) | z_off = z * level_height for floor, wall, roof, stair placement |
| Guide path | Spline point Z = ho + z_coord * level_height |
| Vertex painting | Floor level mapping from vertex Z: z = int((pz - 1e-6) / level_height) |
| Prop spawning | Prop Z = z * level_height + 0.6 * wh (torches) or z * level_height (chests/doors) |
| Interactive editor | Floor 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)
- N:
- 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:
| Style | Geometry | Steps |
|---|---|---|
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):
- Top landing platform — A flat rectangular slab at
z_offset + wh(aligned to +Y side). - Central post — 8-segment cylindrical column from
z_offsettoz_offset + wh. - 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):
- Top surface — Sloped quadrilateral rising from
z_offsetat-ytoz_offset + what+y. - Bottom face — Flat rectangle at
z_offset. - Side panels — Triangular wedge at x = -t2 (left) and x = t2 (right).
- 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 type | Compass | Polar equivalent |
|---|---|---|
| Rectangular | N / E / S / W | — |
| Polar | N → CCW, E → OUT, S → CW, W → IN | CCW / 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:
- Candidate open cells on rings
r >= 2are collected per floor. - Orientation is chosen randomly or mapped from
stair_directionto polar orientation. - Each stair is defined as
{'z': z, 'x': theta, 'y': r, 'type': style, 'footprint': '1x1', 'orientation': polar_orient}. - Footprint cells are force-opened on both levels.
- All polar stairs use
1x1footprint 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_transformedgenerates:- 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_heightas described above.
Material Slots
When floor_thickness > 0, generated floor tiles use three material slots:
| Slot | Face | Usage |
|---|---|---|
| 0 | Top | Walkable surface — typically same as floor material |
| 1 | Bottom | Underside slab |
| 2 | Sides | Edge 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'):
| Action | Result |
|---|---|
| Left-click on open cell | Places 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 stair | Removes the stair. |
| Shift+left-click on existing stair | Rotates orientation: N→E→S→W→N (rect) or CCW→OUT→CW→IN→CCW (polar). |
| Left-click on top floor | Reports 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
| Control | Property | Shown when |
|---|---|---|
| Floors | floors | Always |
| Floor Thickness | floor_thickness | Always |
| Stair Footprint | stair_footprint | floors > 1 and rect grid |
| Stair Style | stair_style | floors > 1 |
| Stair Direction | stair_direction | floors > 1 |
| Stairs Per Level | stair_count | floors > 1 |
Generation & Editing Section
| Control | Property | Shown when |
|---|---|---|
| Floor Level | edit_floor_level | floors > 1 |
| Edit Tool | edit_tool | floors > 1 and editing |
| Stair Direction (editor) | stair_direction | edit_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.