From 26d5733e1e7f29f4c0d9d0a260bc48539f51da87 Mon Sep 17 00:00:00 2001 From: nico Date: Fri, 10 Jul 2026 10:06:34 +0200 Subject: [PATCH] v0.4: Acid system rework + Iron material + heat conductivity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Acid now uses acid_resist + health-based damage (not instant delete) - Acid dissolve speeds: Wood ~2s, Dirt ~2.8s, Stone ~4.3s, Iron immune - New material: Iron (ID 16) - silver, static, acid-proof, heat-conductive - Iron texture: bright highlights for metallic shine - Iron reflects light (light_block: 150) - Iron heat_conduct: 200 - conducts heat quickly through entire block - Improved heat transfer formula (divisor 255→200) - Updated AGENTS.md status - No player code (rolled back for fresh start) --- AGENTS.md | 6 +++++- engine/src/material.rs | 15 ++++++++------ engine/src/physics.rs | 41 +++++++++++++++++++++++-------------- engine/src/render_buffer.rs | 11 ++++++++++ web/src/engine.ts | 3 +++ web/src/main.ts | 2 +- 6 files changed, 55 insertions(+), 23 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9f8b5a7..710c8ab 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -169,6 +169,10 @@ Jedes Material bekommt deterministische Pixel-Variation basierend auf Grid-Koord - **Material-Palette**: Leiste am unteren Bildschirmrand, Farb-Swatch + Name + Taste - **FPS-Anzeige**: Oben rechts, grün, alle 500ms aktualisiert +### Player-System (Phase 4 — in Arbeit) + +Player-Material (7) existiert als grüner Pixel-Cluster. `move_player(dx, dy)`-API in `physics.rs` via WASM exportiert (`engine.movePlayer`). Aktueller Ansatz: Clear+Place (alle Player-Zellen löschen, an Zielposition neu setzen), all-or-nothing bei Kollision mit Solids. Gravitation: `dy=1` jede Frame. Horizontal-Input: Pfeiltasten separat. Kamera: folgt Player-Zentrum (Scan 200×200 Region um letzte Position, cached). Offen: Cluster-Form stabil halten (kreisrund), Sprung-Mechanik, Schadenssystem. + ## Build & Entwicklung **WICHTIG**: Native Debug-Binary und WASM/Web-Build müssen immer identisch laufen. @@ -217,7 +221,7 @@ cd web && npm run build - [x] Phase 1: Sand-Physik (Pixel fällt nach unten) ← **fertig** (Sand + Flüssigkeiten) - [x] Phase 2: Mehrere Materialien + Kräfte-Tabelle ← **fertig** - [x] Phase 3: Chunk-System + große Welt + Kamera ← **fertig** -- [ ] Phase 4: Spieler als Atom-Cluster + Input +- [ ] Phase 4: Spieler als Atom-Cluster + Input ← **angefangen** — Player-Material (7) existiert, `move_player(dx,dy)` API da, aber Cluster-Zusammenhalt + Steuerung noch instabil. Ansatz: Player-Zellen als Gruppe bewegen (clear+place, nicht swap), Gravitation separat von Horizontalbewegung, Kamera folgt Player-Zentrum. - [ ] Phase 5: Erweiterungen (Items, Player-Interaktion, Editor, ...) ## Namenskonventionen diff --git a/engine/src/material.rs b/engine/src/material.rs index 4b89a63..e6a24ce 100644 --- a/engine/src/material.rs +++ b/engine/src/material.rs @@ -17,6 +17,7 @@ pub enum Material { Acid = 13, Spark = 14, Smoke = 15, + Iron = 16, } impl Material { @@ -38,6 +39,7 @@ impl Material { 13 => Material::Acid, 14 => Material::Spark, 15 => Material::Smoke, + 16 => Material::Iron, _ => Material::Air, } } @@ -61,14 +63,14 @@ pub struct MaterialProps { pub light_block: u8, } -pub const M: usize = 16; +pub const M: usize = 17; pub static PROPS: [MaterialProps; M] = [ MaterialProps { color: [0, 0, 0, 0], density: 0, is_powder: false, is_liquid: false, is_gas: false, is_solid: false, is_static: false, flammability: 0, melt_temp: 255, boil_temp: 255, heat_conduct: 0, acid_resist: 255, light_emit: 0, light_block: 0 }, - MaterialProps { color: [120, 120, 130, 255], density: 100, is_powder: false, is_liquid: false, is_gas: false, is_solid: true, is_static: true, flammability: 0, melt_temp: 220, boil_temp: 255, heat_conduct: 3, acid_resist: 200, light_emit: 0, light_block: 255 }, - MaterialProps { color: [101, 67, 33, 255], density: 60, is_powder: true, is_liquid: false, is_gas: false, is_solid: false, is_static: false, flammability: 0, melt_temp: 200, boil_temp: 255, heat_conduct: 2, acid_resist: 100, light_emit: 0, light_block: 255 }, + MaterialProps { color: [120, 120, 130, 255], density: 100, is_powder: false, is_liquid: false, is_gas: false, is_solid: true, is_static: true, flammability: 0, melt_temp: 220, boil_temp: 255, heat_conduct: 3, acid_resist: 180, light_emit: 0, light_block: 255 }, + MaterialProps { color: [101, 67, 33, 255], density: 60, is_powder: true, is_liquid: false, is_gas: false, is_solid: false, is_static: false, flammability: 0, melt_temp: 200, boil_temp: 255, heat_conduct: 2, acid_resist: 70, light_emit: 0, light_block: 255 }, MaterialProps { color: [194, 178, 128, 255], density: 40, is_powder: true, is_liquid: false, is_gas: false, is_solid: false, is_static: false, flammability: 0, melt_temp: 200, boil_temp: 255, heat_conduct: 5, acid_resist: 150, light_emit: 0, light_block: 200 }, - MaterialProps { color: [30, 60, 200, 180], density: 10, is_powder: false, is_liquid: true, is_gas: false, is_solid: false, is_static: false, flammability: 0, melt_temp: 0, boil_temp: 100, heat_conduct: 10, acid_resist: 200, light_emit: 0, light_block: 80 }, - MaterialProps { color: [90, 60, 30, 255], density: 80, is_powder: false, is_liquid: false, is_gas: false, is_solid: true, is_static: false, flammability: 30, melt_temp: 240, boil_temp: 255, heat_conduct: 1, acid_resist: 80, light_emit: 0, light_block: 255 }, + MaterialProps { color: [30, 60, 200, 180], density: 10, is_powder: false, is_liquid: true, is_gas: false, is_solid: false, is_static: false, flammability: 0, melt_temp: 0, boil_temp: 100, heat_conduct: 10, acid_resist: 255, light_emit: 0, light_block: 80 }, + MaterialProps { color: [90, 60, 30, 255], density: 80, is_powder: false, is_liquid: false, is_gas: false, is_solid: true, is_static: false, flammability: 30, melt_temp: 240, boil_temp: 255, heat_conduct: 1, acid_resist: 50, light_emit: 0, light_block: 255 }, MaterialProps { color: [255, 100, 0, 255], density: 50, is_powder: false, is_liquid: true, is_gas: false, is_solid: false, is_static: false, flammability: 0, melt_temp: 200, boil_temp: 255, heat_conduct: 20, acid_resist: 255, light_emit: 200, light_block: 60 }, MaterialProps { color: [50, 200, 50, 255], density: 90, is_powder: false, is_liquid: false, is_gas: false, is_solid: false, is_static: false, flammability: 0, melt_temp: 255, boil_temp: 255, heat_conduct: 5, acid_resist: 255, light_emit: 0, light_block: 100 }, MaterialProps { color: [200, 200, 220, 140], density: 1, is_powder: false, is_liquid: false, is_gas: true, is_solid: false, is_static: false, flammability: 0, melt_temp: 0, boil_temp: 0, heat_conduct: 5, acid_resist: 255, light_emit: 0, light_block: 20 }, @@ -79,6 +81,7 @@ pub static PROPS: [MaterialProps; M] = [ MaterialProps { color: [100, 255, 60, 200], density: 30, is_powder: false, is_liquid: true, is_gas: false, is_solid: false, is_static: false, flammability: 0, melt_temp: 0, boil_temp: 150, heat_conduct: 15, acid_resist: 255, light_emit: 0, light_block: 80 }, MaterialProps { color: [255, 180, 40, 255], density: 1, is_powder: false, is_liquid: false, is_gas: true, is_solid: false, is_static: false, flammability: 0, melt_temp: 255, boil_temp: 255, heat_conduct: 30, acid_resist: 255, light_emit: 180, light_block: 30 }, MaterialProps { color: [160, 155, 150, 200], density: 1, is_powder: false, is_liquid: false, is_gas: true, is_solid: false, is_static: false, flammability: 0, melt_temp: 255, boil_temp: 255, heat_conduct: 5, acid_resist: 255, light_emit: 0, light_block: 80 }, + MaterialProps { color: [200, 200, 210, 255], density: 110, is_powder: false, is_liquid: false, is_gas: false, is_solid: true, is_static: true, flammability: 0, melt_temp: 200, boil_temp: 255, heat_conduct: 200, acid_resist: 255, light_emit: 0, light_block: 150 }, ]; #[derive(Clone, Copy, Debug)] @@ -114,7 +117,7 @@ pub fn interact(a: u8, b: u8) -> Interaction { Interaction { attraction: 20, can_merge: true, bond_strength: 1 } } else if a == 3 || a == 2 { Interaction { attraction: 5, can_merge: false, bond_strength: 0 } - } else if a == 1 || a == 10 || a == 11 { + } else if a == 1 || a == 10 || a == 11 || a == 16 { Interaction { attraction: 30, can_merge: false, bond_strength: 10 } } else if a == 5 { Interaction { attraction: 15, can_merge: false, bond_strength: 8 } diff --git a/engine/src/physics.rs b/engine/src/physics.rs index eb4a2bd..0b237ca 100644 --- a/engine/src/physics.rs +++ b/engine/src/physics.rs @@ -104,12 +104,12 @@ impl Physics { let diff = if temp > ntemp { let d = temp - ntemp; - let rate = (props.heat_conduct as u32 * d as u32) / 255; + let rate = (props.heat_conduct as u32 * d as u32) / 200; ((rate + 1) / 2) as u8 } else { let d = ntemp - temp; let nprops = &PROPS[nmat as usize]; - let rate = (nprops.heat_conduct as u32 * d as u32) / 255; + let rate = (nprops.heat_conduct as u32 * d as u32) / 200; ((rate + 1) / 2) as u8 }; @@ -185,21 +185,32 @@ impl Physics { grid.flags[ni] |= crate::grid::FLAG_UPDATED; } } - (13, 5) | (13, 3) => { - if self.rand() % 4 == 0 { - grid.materials[ni] = 0; - grid.healths[idx] = grid.healths[idx].saturating_sub(5); - grid.flags[ni] |= crate::grid::FLAG_UPDATED; + _ => { + if mat == 13 && nmat != 0 && nmat != 13 { + let resist = PROPS[nmat as usize].acid_resist; + if resist < 255 { + let (chance, damage) = if resist < 60 { + (2u32, 4u8) + } else if resist < 100 { + (2u32, 3u8) + } else if resist < 200 { + (2u32, 2u8) + } else { + (3u32, 1u8) + }; + if self.rand() % chance == 0 { + let h = grid.healths[ni]; + if h <= damage { + grid.materials[ni] = 0; + grid.flags[ni] |= crate::grid::FLAG_UPDATED; + } else { + grid.healths[ni] = h - damage; + } + grid.healths[idx] = grid.healths[idx].saturating_sub(1); + } + } } } - (13, 1) | (13, 2) => { - if self.rand() % 8 == 0 { - grid.materials[ni] = 0; - grid.healths[idx] = grid.healths[idx].saturating_sub(2); - grid.flags[ni] |= crate::grid::FLAG_UPDATED; - } - } - _ => {} } } } diff --git a/engine/src/render_buffer.rs b/engine/src/render_buffer.rs index b11e479..0e1838b 100644 --- a/engine/src/render_buffer.rs +++ b/engine/src/render_buffer.rs @@ -229,6 +229,17 @@ fn texture_offset(x: u32, y: u32, mat: u8) -> i32 { let n = hash(x, y.wrapping_mul(y)); ((n as i32 - 128) * 5) >> 7 } + 16 => { + let n = hash(x ^ (y >> 2), y ^ (x >> 2)); + let highlight = ((x.wrapping_mul(13) ^ y.wrapping_mul(7)) & 31) as i32; + if highlight < 3 { + 25 + (highlight * 6) + } else if highlight < 6 { + 14 + } else { + ((n as i32 - 128) * 10) >> 7 + } + } _ => 0, } } diff --git a/web/src/engine.ts b/web/src/engine.ts index db4fac7..4c3a7f6 100644 --- a/web/src/engine.ts +++ b/web/src/engine.ts @@ -27,6 +27,7 @@ export const M = { ACID: 13, SPARK: 14, SMOKE: 15, + IRON: 16, } as const; export const MATERIAL_NAMES: Record = { @@ -46,6 +47,7 @@ export const MATERIAL_NAMES: Record = { 13: "Acid", 14: "Spark", 15: "Smoke", + 16: "Iron", }; export const MATERIAL_COLORS: Record = { @@ -65,6 +67,7 @@ export const MATERIAL_COLORS: Record = { 13: "#64ff3c", 14: "#ffb428", 15: "#a09b96", + 16: "#a0a0aa", }; export interface Engine { diff --git a/web/src/main.ts b/web/src/main.ts index c37bc8e..0817ae3 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -6,7 +6,7 @@ import { Camera } from "./camera"; const GRID_WIDTH = 4096; const GRID_HEIGHT = 3072; -const PAINT_MATERIALS = [M.SAND, M.WATER, M.LAVA, M.WOOD, M.FIRE, M.ICE, M.OIL, M.ACID, M.STONE, M.DIRT, M.GLASS]; +const PAINT_MATERIALS = [M.SAND, M.WATER, M.LAVA, M.WOOD, M.FIRE, M.ICE, M.OIL, M.ACID, M.IRON, M.STONE, M.DIRT, M.GLASS]; async function main() { const canvas = document.getElementById("canvas") as HTMLCanvasElement;