Skip to content
Merged
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ node_modules
AGENTS.md
DESIGN.md
TASKS.md
docs/
.agents
skills-lock.json
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ unit-testable with plain `cargo test`.
| Sand | Falls and piles; sinks through water |
| Water | Flows and spreads |
| Stone | Static obstacle |
| Fire | Rises, flickers, burns out |
| Wood | Static; ignites from adjacent fire |
| Erase / Clear | Remove cells |
| Fire | Rises, flickers, burns out; heats neighbors |
| Wood | Static; ignites when heat reaches 80 |
| Steam | Rises; condenses when cool; water boils next to fire |
| Erase / Clear | Remove cells |

## Quickstart

Expand Down
3 changes: 3 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ <h1>Sandboxed</h1>
<button data-mat="5">
<span class="swatch" style="background: #764824"></span>Wood
</button>
<button data-mat="6">
<span class="swatch" style="background: #d8dce8"></span>Steam
</button>
<button data-mat="0">
<span class="swatch" style="background: #1a1a24"></span>Erase
</button>
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ impl Simulation {
self.world.height
}

/// Paint `material` (0=erase, 1=sand, 2=water, 3=stone, 4=fire, 5=wood) at (x, y).
/// Paint `material` (0=erase, 1=sand, 2=water, 3=stone, 4=fire, 5=wood, 6=steam) at (x, y).
pub fn paint(&mut self, x: usize, y: usize, material: u8, radius: usize) {
if let Some(cell) = Cell::from_u8(material) {
self.world.paint(x, y, cell, radius);
Expand Down
61 changes: 57 additions & 4 deletions src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,44 @@ fn put(frame: &mut [u8], i: usize, r: i16, g: i16, b: i16) {
frame[o + 3] = 255;
}

fn warm(r: i16, g: i16, b: i16, heat: u8) -> (i16, i16, i16) {
let t = heat as i16;
(r + t / 2, g, b.saturating_sub(t / 3))
}

/// Write the whole world into `frame` (must be width*height*4 bytes).
pub fn draw(world: &World, frame: &mut [u8]) {
for (i, cell) in world.cells().iter().enumerate() {
let x = i % world.width;
let y = i / world.width;
let j = jitter(x, y);
let h = world.heat()[i];
match cell {
Cell::Empty => put(frame, i, 12, 12, 18),
Cell::Sand => put(frame, i, 194 + j, 168 + j, 96 + j / 2),
Cell::Water => put(frame, i, 40, 90 + j, 200 + j),
Cell::Stone => put(frame, i, 110 + j / 2, 110 + j / 2, 118),
Cell::Sand => {
let (r, g, b) = warm(194 + j, 168 + j, 96 + j / 2, h);
put(frame, i, r, g, b);
}
Cell::Water => {
let (r, g, b) = warm(40, 90 + j, 200 + j, h);
put(frame, i, r, g, b);
}
Cell::Stone => {
let (r, g, b) = warm(110 + j / 2, 110 + j / 2, 118, h);
put(frame, i, r, g, b);
}
Cell::Fire => {
let t = world.ttl()[i] as i16;
put(frame, i, 255, 32 + t * 4, 16);
}
Cell::Wood => put(frame, i, 118 + j / 2, 72 + j / 2, 36),
Cell::Wood => {
let (r, g, b) = warm(118 + j / 2, 72 + j / 2, 36, h);
put(frame, i, r, g, b);
}
Cell::Steam => {
let (r, g, b) = warm(216 + j / 2, 220 + j / 2, 232, h);
put(frame, i, r, g, b);
}
}
}
}
Expand All @@ -44,6 +66,37 @@ mod tests {
use super::*;
use crate::world::{Cell, World};

#[test]
fn steam_pixels_are_pale() {
let mut world = World::new(1, 1);
world.paint(0, 0, Cell::Steam, 0);
let mut frame = vec![0u8; 4];
draw(&world, &mut frame);
let (r, g, b) = (frame[0], frame[1], frame[2]);
assert!(
r > 180 && g > 180 && b > 180,
"expected pale steam, got {r},{g},{b}"
);
assert_eq!(frame[3], 255);
}

#[test]
fn hot_wood_is_warmer_than_cold_wood() {
let mut cold = World::new(1, 1);
cold.paint(0, 0, Cell::Wood, 0);
let mut hot = World::new(1, 1);
hot.paint(0, 0, Cell::Wood, 0);
hot.add_heat_for_test(0, 0, 70);
let mut fc = vec![0u8; 4];
let mut fh = vec![0u8; 4];
draw(&cold, &mut fc);
draw(&hot, &mut fh);
assert!(
fh[0] > fc[0] || fh[2] < fc[2],
"hot wood should be redder or less blue: cold={fc:?} hot={fh:?}"
);
}

#[test]
fn fire_pixels_are_orange_red() {
let mut world = World::new(2, 1);
Expand Down
Loading
Loading