Three things, nothing more
The entire illusion rests on three observations about human vision. Internalize these and the code becomes obvious.
The world is a 2D grid
There is no 3D geometry. Every cell in a flat grid is either a wall or empty space. Building heights, floor types, and entity positions are just numbers stored in that grid. The renderer creates the illusion of a third dimension entirely.
One ray per screen column
From the player's position, rays are cast across the field of view — one ray per vertical column of pixels. Each ray travels through the grid until it strikes a wall, recording the distance to that wall. A 320-pixel-wide screen fires exactly 320 rays per frame.
Distance becomes wall height
This is the trick. A nearby wall hit → tall stripe on screen. A distant wall hit → short stripe. Your visual system interprets "tall = close, short = far" as genuine 3D depth. The renderer paints one vertical rectangle per column. That's the entire render loop.
See it in action
The left panel shows the true top-down map — the only data that exists. The right panel shows what the player sees. Click anywhere on the map to move the player. Adjust the sliders to understand how each parameter shapes the view.
How the distance becomes height
The formula is three lines. Understanding them is the entire course.
The fisheye correction is the most misunderstood part. Without it, rays at the edge of the FOV travel a longer path to the same wall than rays at the center. The correction multiplies each distance by the cosine of the ray's offset from the camera's center, projecting onto a flat plane rather than a sphere. Try setting ray count to 8 and rotating slowly in the demo — without the correction, walls would visibly bow outward.
From pixel columns to characters
The ASCII city demo (Grow Now! Games, August 2026) uses the exact same raycasting engine.
The only difference is the final rendering step: instead of painting a gray rectangle,
the renderer picks a character from a lookup table based on the wall distance.
Close walls get dense characters (#, █), far walls get sparse ones
(., :, -). Your eye perceives the density gradient
as shading and depth.
Dense = close
Characters like # █ @ M cover most of their cell.
Clustered together, they read as a dark, solid surface — the visual weight of a nearby wall.
Sparse = distant
Characters like . , ' - have minimal coverage.
Rows of them read as a pale, receding surface — the visual lightness of distance.
Color layer on top
The ASCII city adds a color channel — each character cell gets an RGBA tint based on wall face direction (north/south walls get one hue, east/west another) and distance. The characters provide luminance; the color provides atmosphere.
Switch the demo above to ASCII mode to see this in action. The same ray distances drive both renderers.
What raycasting can't do
The technique imposes hard constraints that id Software famously worked around in creative ways, and that the ASCII city demo inherits today.
No looking up or down
Because each screen column maps to exactly one horizontal ray angle, true vertical pitch is impossible. Wolfenstein had no stairs, no ramps, and you couldn't look up or down. A y-shear trick (shifting the horizon line) fakes a limited tilt, which is what the ASCII city uses for its pitch effect.
All floors must be the same height
Every wall in the grid is full-height. You cannot have a half-wall, a window ledge, or a step. Doom (1993) partially solved this with sector-based height fields. True room-over-room geometry required waiting for Quake's BSP engine in 1996.
No diagonal walls
The grid is axis-aligned. Diagonal surfaces require either faking them with sprites or a more sophisticated DDA (Digital Differential Analysis) algorithm that can test off-axis geometry — which the ASCII city's engine actually implements for its building facades.