Spent the afternoon rewriting the pixel shader pipeline. The old approach was brute-forcing per-pixel lighting calculations when we could batch them through a deferred rendering pass instead.
The new pipeline is roughly 3x faster in our test scene. Still need to handle transparency correctly — transparent objects need a forward pass layered on top.
// deferred lighting accumulation
for (light in scene.lights) {
accumBuffer += calcLight(
gBuffer.normal,
gBuffer.albedo,
light.position
);
}
Tomorrow: integrate the shadow map atlas. The current per-light shadow approach won't scale past 8 dynamic lights.
2026.03.17
tilemap overhaul
Completely rebuilt the tilemap system from scratch. The old autotile approach used a massive lookup table — 256 entries for each tile type. The new system uses a bitmask approach with Wang tiles instead.
Wang tiles cut our tile asset count by 60%. The bitmask lookup is O(1) and the visual quality is actually better — natural-looking transitions between terrain types without visible repetition.
Finally cracked the procedural cloud system. Layered Perlin noise with domain warping gives us clouds that feel organic and shift naturally with wind direction.
Three noise layers: base shape (low frequency), detail edges (high frequency), and domain warp (shifts UV coordinates for organic movement). The warp layer is the secret sauce — without it, clouds look like blobs.
let warp = noise(uv * 0.3 + time * 0.02);
let warped_uv = uv + warp * 0.15;
let cloud = fbm(warped_uv, 4, 0.5);
Performance is good — runs at 60fps on integrated GPUs. The trick is computing the noise in a low-res buffer and upscaling with bilateral filtering.
2026.03.11
dialogue system v2
Rewrote the dialogue system from a flat array to a proper graph structure. Each node can branch, loop, or gate on game state. The editor now shows connections visually.
The graph approach means NPCs can remember past conversations. State gates check inventory, quest progress, even time-of-day. Feels much more alive than the linear scripts we had before.
Rebuilt the character controller with coyote time and input buffering. The old controller felt stiff — missed jumps at ledge edges, unresponsive dash cancels. Now it feels buttery.
Key changes: 6-frame coyote window (100ms at 60fps), 4-frame input buffer for jump, and variable jump height via hold duration. Also added a subtle squash/stretch on land and launch.
The squash/stretch is subtle but makes a huge difference in game feel. 20% compression on land, 20% stretch on jump. Spring back to normal over 6 frames.
2026.03.05
sound design notes
Spent the day recording and processing foley for the forest biome. Kitchen pans + crinkled paper = surprisingly good "magical sparkle" effect when pitch-shifted and layered.
Sound palette for the forest area:
Ambient: layered wind recordings at different frequencies
Footsteps: gravel samples with randomized pitch (0.9 - 1.1x)
UI: soft bell tones, pentatonic scale
Combat: reversed cymbal crashes for "charge up" effect
The randomized pitch on footsteps prevents the "machine gun" repetition problem. Even 10% variation makes them sound completely natural. Also added velocity-based volume — walking is quieter than running.
fn play_footstep(velocity: f32) {
let sample = footsteps.random();
let pitch = rng.range(0.9, 1.1);
let volume = lerp(0.3, 1.0, velocity / MAX_SPEED);
audio.play(sample, pitch, volume);
}