Nation0 Effect Anatomy · fn_6098c / 0x6098c

Inside the Whirlpool.

How an early 1990s PC produced fluid, a 2D polar-mapped vortex and two-layer palette compositing.

Timeline: ~00:53 in Demo Resolution: 160×80 Source Grid VGA Hardware: CRTC Reg 9 Scanline Repeat + Nibble-Split DAC CPU Target: 486 DX2 / Pentium
About this illustration

The interactive preview illustrates polar lookup and nibble packing with procedural textures. It does not execute the original routines, reproduce their timing, or implement the original temporal lookup feedback. The DAC matrix is an illustrative palette, not a captured palette from a particular frame.

Interactive Pipeline Inspector

COMPOSITE · 2-LAYER VGA DAC
Screen Pos (X, Y) 160, 100
Polar Polar (r, θ) r: 0, θ: 0
Nibbles (L2 | L1) 0x0 | 0x0 → 0x00
DAC RGB Output (0, 0, 0)
PHASE 070
Active Inspection: Full composite mode. The CPU evaluates 80 scanlines of 80 sample pairs. Each byte packs Layer 1 (bits 0–3) and Layer 2 (bits 4–7). The VGA DAC looks up their precomputed summed color.

Rendering Pipeline Stages

Click a stage to inspect its specific math and memory registers.

01

Precompute Polar Grid & Alpha Matrix

160×100 coordinate map with aspect ratio correction and 64 KiB 2D alpha transition lookup table.

fn_21701 @ 0x21701 · DS:[0x21974] & DS:[0x5fa84]
02

Dual Sine Domain Warping

Layer 1 uses a single harmonic wave; Layer 2 uses dual-frequency Lissajous modulation with prime step counters.

fn_60d8b & fn_60df8 · Sine tables DS:[0x2198c] / DS:[0x2298c]
03

Polar Tunnel Sampling & Viscosity Blend

Sample texture in (r, θ) space and blend with preceding frame using the 64 KiB transition matrix.

fn_60eda (2×) & fn_60e96 · 0x5fa74 / 0x5fa7c
04

Nibble-Split Packing & 32-bit Bus Writes

Pack (Layer 2 << 4) | Layer 1. Pack 4 screen pixels into a 32-bit dword per write to video memory.

0x60c1f · SS:[0xa0c80] · 80 rows × 80 dword writes (320 output pixels)
05

Hardware Additive DAC & CRTC Scanline Doubling

CRTC Reg 9 repeats scanlines vertically. The DAC looks up a color already summed by the CPU during palette initialization.

OUT 0x3d4, 0x4309 · 256-color DAC at 0x603f7

How 2-Layer Transparency Works Without CPU Cost Nibble-Split DAC

A conventional per-pixel blend computes a weighted sum (which can use integer arithmetic): C = α·A + (1-α)·B.

Nation0 avoids a per-pixel RGB sum in the final compositor by encoding two 4-bit layer indices into one 8-bit byte, and precalculating the 256 VGA DAC palette registers as a precomputed color addition table:

1. Compositor Packing 0x60c1f
pixel_byte = (Layer2_Nibble << 4) | Layer1_Nibble
Takes Layer 2 (0..15) in high nibble and Layer 1 (0..15) in low nibble.
2. Additive DAC Initialization 0x60b29
DAC[(High << 4) | Low].RGB = min(Palette1[15 - Low].RGB + Palette2[High].RGB, 63)
Pre-populates all 256 VGA DAC color registers with the saturated sum of both layers.

Below is an illustrative 16×16 = 256 additive palette matrix using the same indexing scheme. The horizontal axis is Layer 1 (0..15), and the vertical axis is Layer 2 (0..15):

← Layer 1 (Low Nibble 0x0..0xF) → ↑ Layer 2 (High Nibble 0x0..0xF) ↓

The Mathematics of the Vortex Polar Transform & Lissajous

The illusion of rotational vortex depth and liquid distortion is created through three mathematical layers:

1. Polar Coordinate Transform fn_21701 @ 0x21701
y_scaled = floor(y * 552 / 512) // 4:3 Aspect ratio correction r = sqrt(x² + y_scaled²) // Integer sqrt via fn_37732 θ = atan2(y_scaled, x) // Fixed-point arctangent table Word = (((floor(r * 470 / 512) & 255) << 8) | (θ >> 2)) & 0x7FFF
Precomputes 160×100 grid centered at screen origin (0,0).
2. Dual Sine Domain Warping fn_60d8b & fn_60df8
// Layer 1 (Deep Vortex - Single harmonic): rowOffset₁ = ((40 · table(phase₁) >> 16) + 40) * 2 + 2*row − 1 phase₁ = (phase₁ & 4095) − 45 per generated row // Layer 2 (Surface Ripple - Compound Lissajous wave): u₂(y) = ½ [ (40 · sin₁(phase₂ - 35·y) / 65536 + 40)*2 + (50 · sin₂(phase₃ - 40·y) / 65536) ]
The finite lookup tables and wrapping phase counters produce periodic motion. Multiple increments vary the apparent motion; +12 and +22 are not coprime.
3. 64 KiB Viscosity Transition Blend fn_60e96 @ 0x60e96
Blend[A, B] = min( floor((45·A + 210·B) / 256), 14 ) // Equivalent to: 0.176·A + 0.824·B
Temporal frame persistence and fluid viscosity via 16-bit table lookup.

How (r, θ) Maps to 2D Texture Space & The Zero-Arithmetic Sampler Loop fn_60eda / 0x60eda

In 2D linear memory, texel (U, V) in a 256 × 256 texture has address offset: Offset = (V × 256) + U = (V << 8) | U.
Nation0 takes advantage of this power-of-two alignment: by scaling radius r ∈ [0, 255] into the high byte (V) and angle θ ∈ [0, 255] into the low byte (U), every 16-bit polar word is already the exact physical memory offset into the 64 KiB texture buffer.

BITS 15 .. 8 (HIGH BYTE)
Radius V (0 .. 255)
Texture Row Address
BITS 7 .. 0 (LOW BYTE)
Angle θ (0 .. 255)
Texture Column Address
Linear Byte Offset in RAM = (Radius << 8) | Angle  ≡  (V × 256) + U
Inner Assembly Sampler Loop fn_60eda @ 0x60eda
loc_60EDA: movzx ebx, word ptr [ebp + 4] ; Load (r, θ) polar word for pixel 3 mov al, byte ptr [esi + ebx] ; Direct texture lookup at DS:[ESI + (r<<8)|θ] movzx ebx, word ptr [ebp + 6] ; Load (r, θ) polar word for pixel 4 mov ah, byte ptr [esi + ebx] ; Direct texture lookup ror eax, 16 ; Rotate into high word of EAX movzx ebx, word ptr [ebp] ; Load (r, θ) polar word for pixel 1 mov al, byte ptr [esi + ebx] ; Direct texture lookup movzx ebx, word ptr [ebp + 2] ; Load (r, θ) polar word for pixel 2 mov ah, byte ptr [esi + ebx] ; Direct texture lookup mov dword ptr [edi], eax ; Store 4 sampled pixels to destination buffer! add edi, 4 add ebp, 8 dec ecx jnz loc_60EDA
1. Angular Rotation (ΔU)

Advancing texture data horizontally across columns (U-axis) shifts angles θ → θ + Δ θ, producing continuous vortex spin.

2. Radial Suction (ΔV)

Advancing texture data vertically down rows (V-axis) shifts radii r → r + Δ r, producing dynamic tunnel inward/outward suction.

3. Fluid Wave Ripple

Shifting rows via sine tables in fn_60d8b and fn_60df8 before polar sampling transforms straight waves into twisting fluid currents.

VGA CRTC Scanline Doubling & 32-bit Bus Optimization CRTC Register 0x09

The rasterizer reduces the number of generated samples and groups its framebuffer writes; an exact bandwidth or frame-rate claim depends on the machine. Nation0 uses two hardware-level speedups:

1. CRTC Line Repeat (OUT 0x3d4, 0x4309)

The routine reprograms VGA CRTC Register 0x09 (Maximum Scan Line register) with value 0x43. This hardware register instructs the VGA CRT controller to repeat each row multiple times on scanout. The CPU only generates 80 scanlines, each is repeated four times in 400-line scanout, equivalent to 160 logical rows with 20-row borders in the 320×200 player.

2. 32-bit Dword Pixel Packing

In the raster loop (0x60c75), two consecutive 8-bit pixels (lower and upper) are packed into a 32-bit dword: dword = lower | (lower << 8) | (upper << 16) | (upper << 24). A single 32-bit write writes 4 pixels to the screen at once.

Complete Frame Pipeline Overview

1. Setup 160×100 Polar Grid &
256-Color DAC Table
fn_21701 @ 0x21701
2. Domain Warp Dual Sine Deformations
Layer 1 & Layer 2
fn_60d8b & fn_60df8
3. Polar Sample Vortex Mapping &
64 KiB Motion Blend
fn_60eda & fn_60e96
4. Nibble Pack (L2 << 4) | L1
4 Pixels per 32-bit Write
0x60c1f @ SS:[A0C80]
5. Hardware DAC VGA Additive Light Sum
+ CRTC Scanline Repeat
OUT 0x3d4, 0x4309