arrows or WASD: move — walk into a monster to attack it SPACE or Z: wait one turn (resting heals slowly) E: descend, while standing on the > Q: drink a potion R: new dungeon R rats, G goblins, O orcs, T trolls. ! potions, * gold, + a better blade. Get to level 8. The status line reads HP, depth, character level, gold, potions, and SEEN % — the fraction of the level your field of view has actually reached.
A turn-based dungeon crawler with BSP-generated levels and **recursive shadowcasting** field of view — so you see exactly what your character could see, walls throw real shadows, and everywhere you have been stays on the map as cold memory. THE INTERESTING BIT The thing that makes a roguelike feel like a roguelike is not the turns or the permadeath, it is that you can only see what your character could see. A radius check gives you a lit circle and X-ray vision through walls. Real field of view needs shadows, and shadows need occlusion geometry. This is Björn Bergström's recursive shadowcasting. The eight octants are swept independently — which is the trick that lets a single scan assume one monotonic direction and get away with plain slope comparisons. Each octant is scanned row by row outwards; every cell is tested against a cone bounded by two slopes; and when the scan hits a wall the cone splits: the part of it beyond the wall becomes a new, narrower cone starting one row further out, while the current scan carries on in what is left. Walls therefore cast shadows with hard edges that widen with distance, which is exactly what makes a corridor read as a corridor and a doorway read as a doorway. [It is called recursive, and here it is not, for a specific reason] goboscript local variables compile to ordinary variables with a mangled name, so they are shared across invocations — the language documentation says outright that locals are undefined behaviour in a recursive procedure. A recursive shadowcast written with locals would silently corrupt its own loop counters, and silently is the bad part. The fix is not a workaround, it is an observation about the algorithm. Each split cone only ever touches rows strictly beyond the current one, within a slope range disjoint from what the caller goes on to scan. The sub-cones are therefore independent, and can be processed in any order — so they go onto an explicit worklist of (row, start slope, end slope) triples and the whole thing flattens into a loop with a stack. Verified, not assumed. The tile map and visibility set were pulled out of the running .sb3 and compared against a textbook recursive implementation of the same algorithm in Python, on the same map, from the same tile: reference visible tiles: 48 project visible tiles: 48 missing from project: [] extra in project : [] MATCH Identical, tile for tile. And it is doing real work: on that sample, **136 tiles lay inside the 9-tile radius and only 48 were visible — 65% of the circle was in shadow.** That gap is the entire difference between shadowcasting and a distance check. [Everything else] Dungeons are BSP, using the same worklist trick: the map rectangle is split along its longer axis at a random 38–62% of the way across, recursively, until a leaf is small or the depth cap is hit; then a room is carved inside each leaf with a one-tile margin so neighbours never share a wall. Splitting the longer axis is what keeps leaves roughly square — split a fixed axis and every room comes out as a slot. Measured on a sample level: 14 rooms, 295 floor tiles, and a flood fill from the player reaches 295 of 295 — fully connected. Memory shading is two flags per tile rather than one. vis is recomputed from scratch every turn; seen is a monotonic OR of every vis there has ever been. All original - code, art and sound. See Inside is open.