# brogue_gen — Godot GDExtension API Reference

Brogue-style dungeon generator for Godot 4.3+. Pure C99 generator under the hood; Godot-native Dictionary interface on top.

**Scope.** Map generation only. One class, one method. Call `BrogueGen.generate(seed, depth)` and get back everything you need to spawn a level: terrain grid, liquids, markers, rooms, machines, chokepoints, stairs. Gameplay is yours to write.

---

## Table of contents

- [Installation](#installation)
- [Project layout](#project-layout)
- [Class `BrogueGen`](#class-broguegen)
- [Returned dictionary schema](#returned-dictionary-schema)
- [Data structures](#data-structures)
- [Enums](#enums)
- [Usage pattern](#usage-pattern)
- [FOV reference (`BrogueFOV`)](#fov-reference)
- [Determinism guarantees](#determinism-guarantees)
- [Build and rebuild](#build-and-rebuild)
- [Troubleshooting](#troubleshooting)

---

## Installation

Drop the `addons/brogue_gen/` directory into any Godot 4.3+ project:

```
your_project/
└── addons/
    └── brogue_gen/
        ├── brogue_gen.gdextension    ← registered by Godot scan
        └── bin/
            └── libbrogue_gen.linux.template_debug.x86_64.so
```

Re-open the project once so Godot scans and registers the extension. `BrogueGen` then appears in **Create New Node** and `class_name` auto-complete.

**Compatibility:** The `.gdextension` manifest declares `compatibility_minimum = "4.3"`. The extension is built against `godot-cpp` 4.5 and loads in any Godot 4.3+, including 4.6.

**Linux x86_64 only for now.** Windows / macOS require building additional targets and adding paths to the `[libraries]` section of the `.gdextension` manifest.

---

## Project layout

```
brogue-genesis/
├── src/                       pure C99 generator (no Godot, no SDL)
│   └── gen/                   room carving, lakes, machines, chokepoints, ...
├── godot/                     GDExtension wrapper
│   ├── godot-cpp/             submodule, branch 4.5
│   ├── src/
│   │   ├── register_types.*   module init
│   │   ├── brogue_gen.*       BrogueGen class
│   │   └── grid_to_dict.*     shared grid → Dictionary serializer
│   ├── SConstruct             build script
│   └── brogue_gen.gdextension source-of-truth manifest
└── demo/                      reference Godot 4.6 project
    ├── addons/brogue_gen/                ← build output
    │   ├── brogue_gen.gdextension
    │   └── bin/libbrogue_gen...so
    ├── project.godot
    ├── scenes/
    └── scripts/
```

Build with `make godot` from the repo root.

---

## Class `BrogueGen`

`extends Node`

One-shot blocking dungeon generator. Instantiate, call `generate()`, free.

Typical generation time on the reference machine: **under 20 ms per level** including machines. Safe to call synchronously in `_ready()`.

### `generate(seed: int, depth: int) -> Dictionary`

Runs the full pipeline (accretion → loops → lakes → wreaths → bridges → walls → stairs → chokepoints → machines) and returns a Dictionary describing the final dungeon.

Parameters:

| name | type | range | notes |
|---|---|---|---|
| `seed` | int | any 64-bit | same seed + depth always yields identical output |
| `depth` | int | 1–26 | influences liquid type weights |

```gdscript
var gen := BrogueGen.new()
var grid: Dictionary = gen.generate(2026, 1)
gen.free()
```

---

## Returned dictionary schema

| key | type | notes |
|---|---|---|
| `seed` | `int` | echoed back |
| `depth` | `int` | 1..26 |
| `width` | `int` | always `79` |
| `height` | `int` | always `29` |
| `terrain` | `PackedByteArray` | length `width * height`, row-major. Index = `y * width + x`. Values in [terrain enum](#terrain-enum-terrain-byte). |
| `liquid` | `PackedByteArray` | same layout. Values in [liquid enum](#liquid-enum). Meaningful only when `terrain[i] == T_LIQUID`. |
| `surface` | `PackedByteArray` | semantic marker byte. Values in [marker enum](#marker-enum). `MK_NONE` for un-marked cells. |
| `flags` | `PackedInt32Array` | bitmask of [cell flags](#cell-flags). |
| `room_id` | `PackedByteArray` | 1–255 inside a room, `0` elsewhere. |
| `machine_id` | `PackedByteArray` | 1–255 inside a machine, `0` elsewhere. |
| `stairs_up` | `Vector2i` | always valid grid coords; `(-1, -1)` only on degenerate seeds. |
| `stairs_down` | `Vector2i` | farthest reachable floor from `stairs_up` (BFS). |
| `rooms` | `Array[Dictionary]` | see [room dictionary](#room-dictionary). |
| `machines` | `Array[Dictionary]` | see [machine dictionary](#machine-dictionary). |
| `chokepoints` | `Array[Vector2i]` | every articulation point in the map graph. |
| `gate_sites` | `Array[Vector2i]` | the "best" chokepoint per pocket — ideal vestibule candidates. Subset of `chokepoints`. |

---

## Data structures

### Room dictionary

Each entry in `grid["rooms"]`:

```gdscript
{
    "id": int,                        # matches room_id in the cell arrays
    "cells": Array[Vector2i],         # every cell that belongs to this room
}
```

### Machine dictionary

Each entry in `grid["machines"]`:

```gdscript
{
    "id": int,                        # matches machine_id in the cell arrays
    "interior_cells": Array[Vector2i],
    "gate": Vector2i,                 # the one F_MACHINE_GATE cell; (-1,-1) if unflagged
    "markers": Dictionary,            # marker_id (int) -> Array[Vector2i]
}
```

Example:

```gdscript
var m: Dictionary = grid["machines"][0]
for marker_id in m["markers"]:
    var positions: Array = m["markers"][marker_id]
    match marker_id:
        4: # MK_PEDESTAL
            for p in positions:
                spawn_pedestal(p)
```

---

## Enums

### Terrain enum (`terrain` byte)

| id | constant | meaning |
|---|---|---|
| 0 | `T_NOTHING` | outside the dungeon — not walkable |
| 1 | `T_FLOOR` | room floor |
| 2 | `T_WALL` | wall |
| 3 | `T_DOOR` | door in a room wall |
| 4 | `T_CORRIDOR` | corridor between rooms |
| 5 | `T_LIQUID` | water / lava / chasm / brimstone — see `liquid` byte |
| 6 | `T_BRIDGE` | walkable span across water/chasm |
| 7 | `T_STAIRS_UP` | up-stair |
| 8 | `T_STAIRS_DOWN` | down-stair |

`T_FLOOR`, `T_CORRIDOR`, `T_DOOR`, `T_BRIDGE`, `T_STAIRS_UP`, `T_STAIRS_DOWN` are **passable**.

### Liquid enum (`liquid` byte)

| id | constant | bridges allowed | |
|---|---|---|---|
| 0 | `L_NONE` | — | |
| 1 | `L_WATER` | yes | shallow levels |
| 2 | `L_LAVA` | no | damages walker |
| 3 | `L_CHASM` | yes | gameplay-defined fall behavior |
| 4 | `L_BRIMSTONE` | no | lowest depths |

### Marker enum (`surface` byte)

Semantic placement markers. The generator stamps these; gameplay interprets them.

| id | constant | typical use |
|---|---|---|
| 0 | `MK_NONE` | no marker |
| 1 | `MK_CARPET` | decorative floor tint (paint pass) |
| 2 | `MK_ALTAR` | item pedestal on an altar |
| 3 | `MK_CAGE` | item behind a lock |
| 4 | `MK_PEDESTAL` | item on pedestal |
| 5 | `MK_STATUE` | decorative statue |
| 6 | `MK_TORCH` | point light source |
| 7 | `MK_BRAZIER` | point light source (stronger) |
| 8 | `MK_CHEST` | item container |
| 9 | `MK_SPAWN_MONSTER` | spawn an enemy here |
| 10 | `MK_SPAWN_BOSS` | spawn a boss here |
| 11 | `MK_KEY_ITEM` | the key that unlocks this machine's parent |

### Cell flags

Bitmask in `flags[]`. Test with `(flags[idx] & F_X) != 0`.

| bit | value | constant | meaning |
|---|---|---|---|
| 0 | `0x0001` | `F_IN_ROOM` | cell is room floor |
| 1 | `0x0002` | `F_IN_LOOP` | cell was carved by the loop pass |
| 2 | `0x0004` | `F_CHOKEPT` | reserved |
| 3 | `0x0008` | `F_BRIDGE` | cell is a bridge (redundant with `T_BRIDGE`) |
| 4 | `0x0010` | `F_WREATH` | shoreline cell (adjacent to liquid) |
| 6 | `0x0040` | `F_IN_MACHINE` | cell belongs to a machine |
| 7 | `0x0080` | `F_MACHINE_INTERIOR` | interior (not boundary) |
| 8 | `0x0100` | `F_MACHINE_GATE` | the vestibule / entry |
| 9 | `0x0200` | `F_MACHINE_IMPREGNABLE` | cell is protected from later modification (reserved) |
| 10 | `0x0400` | `F_MACHINE_KEY` | key goes here (gameplay hint) |

---

## Usage pattern

Generate a level, instantiate everything up front, hand off to gameplay.

```gdscript
func build_level(seed: int, depth: int) -> void:
    var gen := BrogueGen.new()
    var grid: Dictionary = gen.generate(seed, depth)
    gen.free()

    # Player at the up-stair.
    $Player.position = _grid_to_world(grid["stairs_up"])

    # Goal marker at the down-stair.
    $GoalMarker.position = _grid_to_world(grid["stairs_down"])

    # One reward per machine.
    for m in grid["machines"]:
        for marker_id in m["markers"]:
            for cell in m["markers"][marker_id]:
                spawn_for_marker(marker_id, cell)

    # Monsters proportional to room size.
    for room in grid["rooms"]:
        var size: int = (room["cells"] as Array).size()
        if size > 40 and randf() < 0.6:
            spawn_monster(_random_cell(room["cells"]))
```

---

## FOV reference

`demo/scripts/fov.gd` ships a reference `BrogueFOV` class. It is **not** part of the extension — it's pure GDScript you can copy into your project and modify.

```gdscript
# demo/scripts/fov.gd
class_name BrogueFOV extends RefCounted

static func compute(grid: Dictionary, origin: Vector2i, radius: int) -> PackedByteArray
```

Symmetric recursive shadowcasting, tile-accurate, O(visible cells). Opaque cells = walls, nothing, and non-water liquids (lava / chasm / brimstone block sight; water does not).

Example:

```gdscript
var vis := BrogueFOV.compute(grid, player_pos, 8)
for y in grid["height"]:
    for x in grid["width"]:
        if vis[y * grid["width"] + x] == 1:
            mark_visible(x, y)
```

---

## Determinism guarantees

- Same `(seed, depth)` input always produces identical output, across runs, platforms, and future minor versions of the plugin.
- The C generator uses a PCG32 RNG seeded from the `seed` argument only. No platform calls (`time()`, `rand()`) are read.

---

## Build and rebuild

From the repo root:

```bash
make godot              # builds the extension
make godot-clean        # removes build artifacts
```

This runs `scons -j$(nproc) platform=linux target=template_debug` in `godot/`. The resulting `.so` and manifest land together in `demo/addons/brogue_gen/` per Godot's plugin convention.

For release builds:

```bash
cd godot && scons -j$(nproc) platform=linux target=template_release
```

To add a new platform (Windows/macOS), add the SCons target + append a new line to `brogue_gen.gdextension`:

```
windows.debug.x86_64  = "res://bin/libbrogue_gen.windows.template_debug.x86_64.dll"
```

---

## Troubleshooting

**"Could not find type BrogueGen" / "Cannot get class 'BrogueGen'".**
Godot has not registered the extension. Check:
1. `addons/brogue_gen/brogue_gen.gdextension` exists in the project.
2. `addons/brogue_gen/bin/libbrogue_gen...so` exists and matches the path in the manifest.
3. The project has been opened in the editor at least once (this triggers the extension scan).
4. Delete the `.godot/` cache directory and re-open if in doubt.

**Godot 4.6 crashes on startup (SIGABRT in `WaylandThread`).**
This is a Godot 4.6.1 Wayland bug, not the extension. Launch with X11 instead:
```bash
godot --display-driver x11 --path /path/to/your/project
```
