NATION ZERO · JAMM · THE ASSEMBLY 1995

Inside the Voxel Landscape

From a recursive byte height field to rotating valleys and traveling waves: the green terrain’s table-driven column renderer.

Original routines, isolated controls

This preview calls the translated original recursive generator, smoothing filter, map rotation, and terrain rasterizer. The controls explore these operations outside the demo’s timeline. The height field is also used for illustrative surface color; the original color texture and VGA planar presentation are not reproduced here. Each of the 160 rendered columns is expanded to two screen pixels.

Interactive Pipeline Inspector

Loading original terrain routines…

 

Switch to the source map to distinguish height animation from map rotation. Uncheck rotation and enable waves: the heights continue to change. Drag relief toward zero to flatten the landscape. Pause freezes phase and angle; the sliders still work.

The frame pipeline

01

Generate the terrain

A recursive subdivision fills a 256×256 byte field from seeded corners, neighbor averages, and integer pseudorandom perturbations.

fn_2188d · 0x2188d..0x21ae7
02

Smooth the field

Two passes average the center twice and all eight neighboring bytes. This softens abrupt changes while preserving valleys.

fn_2165b · fn_215ed
03

Animate height independently

The original controller changes relief, scrolling offsets, rotation angle, and wave phase separately. Wave mode replaces the height field using a radial table and a periodic lookup.

fn_3d5d9 · 0x3d7ba..0x3d82b
04

Rotate the map

A fixed-point sine/cosine basis samples the source field into a 256×256 work map. The routine generates 128 rows and duplicates samples horizontally and vertically.

fn_3dbd4 · 0x3dbd4
05

Project and fill columns

For 60 depth slices, project 160 height samples. Compare them with stored column endpoints, fill newly exposed vertical spans, and interpolate their color indices.

fn_3d997 · 0x3d997..0x3dba2

A height field, not a pile of cubes

“Voxel” names the visual style here. The renderer consumes one height byte per map location, not arbitrary 3D occupancy or polygon faces. It produces the landscape with projected columns and remembered endpoints. A height field cannot represent overhangs.

height = GS[sample_address]
scaled_height = (height × relief) >> 8
projected_height = (scaled_height × slice_scale) >> 10
screen_endpoint = slice_center − projected_height
visible_span = new_endpoint − previous_endpoint

These are conceptual names for the original integer operations. The lift retains word wrapping, signed comparisons, clipping, and color interpolation. The 60 slices start with counters 0x226 and 0x3d; each slice changes them by −8 and +4. Perspective scale and horizontal sample spacing are recomputed at every slice.

How the waves move

The radial table is built by fn_211f4 on a 128×128 grid. Adding phase selects different values in the lookup at 0x3c0d6. The selected byte is duplicated into a 2×2 block in the 256×256 height map. Changing phase changes the geometry even with rotation stopped.

lookup_index = radial_byte + phase
height_byte = wave_lookup[lookup_index]
phase = (phase + event_carry) mod 512

The original ADD word,−1 generates carry for a nonzero old counter; ADC advances the phase. Ignoring that carry froze the waves in an earlier port. Phase progression is event-driven in the intro; this inspector uses a paced clock to make the operation visible.

Projection: one horizontal slice at a time

The outer loop is a sweep of 60 map rows. For each row it computes 160 samples, one for each output column. The map has already been rotated; the rasterizer reads along a horizontal line in that map. It changes both sample spacing and height scale for the next row, so the rows are projected with different perspective factors.

k = slice number, 0..59
A = 550 − 8k                 // DS:[0x3d2cc]
B = 61 + 4k                 // DS:[0x3d2d0]
map_step = 2 × floor(2846000 / B)
height_scale = floor(360 × floor(70000 / A) / 256)
row_center = floor(52000 / A) + 10

for column x = 0..159:
    map_accumulator = (x − 79) × map_step
    sample_x = (map_accumulator >> 16) & 255
    H = rotated_height[(row_address + sample_x) & 65535]
    H_relief = floor(H × relief / 256)
    displacement = floor(H_relief × height_scale / 1024)
    endpoint_word = (row_center − displacement) & 65535
    endpoint_byte = endpoint_word if endpoint_word < 256 else 255

The accumulator begins at −80 steps and is advanced before each read, hence x − 79. Its byte at bits 16..23 selects the map coordinate. After a slice, the row address advances by 512 bytes, and A and B change by −8 and +4. These are the original integer counters, not a textbook camera-distance variable. Larger height bytes subtract more from the row center, lifting the terrain edge on screen. Setting relief to zero removes that height-dependent displacement.

The endpoint encoding is unusual: any nonzero high byte, including a negative wrapped result, forces its low byte to 255. This is the executable’s byte sentinel behavior, not a conventional clamp to the nearest screen edge.

Inside one column: from endpoint to vertical span

The rasterizer remembers a pair of bytes per column: the preceding slice’s endpoint and its color index. It produces another pair for the current slice, then compares their endpoints. It fills a span only when current − previous > 0. This is the original endpoint test; it should not be confused with a general per-pixel Z-buffer.

previous = previous_endpoint_byte + vertical_offset
current  = current_endpoint_byte + vertical_offset
length   = current − previous

if length > 0:
    written_length = min(length, 200)
    address = work_buffer + previous × 320 + column
    color_accumulator = previous_color × 256
    color_step = trunc(signed8(current_color − previous_color) × 256 / length)
    repeat written_length times:
        write byte (color_accumulator >> 8) at address
        color_accumulator += color_step
        address += 320

Adding 320 moves one row downward without changing the column. The previous endpoint is included; the current endpoint is excluded. The 8-bit color difference is sign-extended before division, and the division truncates toward zero. Color indices are interpolated; RGB comes later through the palette. The routine caps the span length at 200, which is not full coordinate clipping. Its work buffer has padding, and the browser preview selects the displayed region separately.

After processing all columns, the new endpoint/color pairs become the previous pairs for the next slice. The first previous endpoints are initialized to 160 with color zero. This small state table lets the renderer bridge adjacent projected terrain rows with filled columns, without storing a depth value for every screen pixel.

Try one column’s calculation

This arithmetic inspector is independent of the animated terrain above. Vary a slice or height to see whether it produces a drawable span. The diagram shows work-buffer row coordinates before the display-region crop.

row 0row 255One byte wide · stride 320

Terrain generation and smoothing

The generator seeds four corners at offsets 0, 0x80, 0x8000 and 0x8080 with 0, 254, 254 and 0. Recursive calls halve the subdivision step, combine neighbors, apply a signed random displacement, and clamp generated values to 0..254. The origin subtraction in the recursive byte coordinates matters: omitting it saturated much of the field and flattened the visible valleys.

smooth = floor((2 × center + eight_neighbors) / 10)

The smoothing routine wraps each full byte address modulo 65536. It uses separate source and destination fields; it is not an in-place blur. The preview runs the same seed, recursive lift, and two filter passes from executable data.

Color and VGA presentation

Height and color are separate reads in fn_3d997. The projected endpoints store an 8-bit coordinate and color; an integer gradient fills each visible span. The demo then transfers the 160-column work region in two VGA map-mask passes, 0x03 and 0x0c, selecting paired planes. That layout differs from a normal packed browser framebuffer.

This preview uses the executable’s green DAC table at 0x3bdd0 and expands columns horizontally in Canvas. The terrain shape comes from the translated rasterizer, while its height-based color sampling is an inspection aid. It does not claim exact original scanout, texture, fade, or demo timing.