feat(@projects): add wasm grid utility hook

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-05-01 00:27:33 -04:00
parent 0e92eec0b4
commit b5a9a218b1
6 changed files with 136 additions and 3 deletions

View file

@ -23,6 +23,7 @@
"@types/styled-components": "^5.1.34",
"@vitejs/plugin-react": "^4.4.1",
"typescript": "^5.8.3",
"vite": "^6.3.3"
"vite": "^6.3.3",
"vite-plugin-wasm": "^3.6.0"
}
}

View file

@ -0,0 +1,31 @@
import { useState, useEffect, useRef } from "react";
import type { WasmGrid } from "../../../../../../.local/build/wasm/magic_civ_physics";
type GridState =
| { status: "idle" }
| { status: "loading" }
| { status: "ready"; grid: WasmGrid }
| { status: "error"; message: string };
export function useWasmGrid(seed: number, mapSize: string = "tiny"): GridState {
const [state, setState] = useState<GridState>({ status: "idle" });
const genRef = useRef(0);
useEffect(() => {
const gen = ++genRef.current;
setState({ status: "loading" });
import("../../../../../../.local/build/wasm/magic_civ_physics")
.then(({ WasmGrid }) => {
if (gen !== genRef.current) return;
const grid = WasmGrid.generateForLab(BigInt(seed), mapSize);
setState({ status: "ready", grid });
})
.catch((err: unknown) => {
if (gen !== genRef.current) return;
setState({ status: "error", message: String(err) });
});
}, [seed, mapSize]);
return state;
}

View file

@ -1,9 +1,11 @@
import path from "path";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import wasm from "vite-plugin-wasm";
export default defineConfig({
plugins: [react()],
plugins: [wasm(), react()],
build: { target: "esnext" },
resolve: {
alias: {
"@game-data": path.resolve(__dirname, "../../../public/games/age-of-dwarves/data"),
@ -26,6 +28,7 @@ export default defineConfig({
path.resolve(__dirname, "../../../public"),
path.resolve(__dirname, "../../../.local/audio-alternatives"),
path.resolve(__dirname, "../../../.local/audio-staging"),
path.resolve(__dirname, "../../../.local/build/wasm"),
path.resolve(__dirname, "../../reports"),
],
},

3
pnpm-lock.yaml generated
View file

@ -50,6 +50,9 @@ importers:
vite:
specifier: ^6.3.3
version: 6.4.2(@types/node@25.6.0)(tsx@4.21.0)
vite-plugin-wasm:
specifier: ^3.6.0
version: 3.6.0(vite@6.4.2(@types/node@25.6.0)(tsx@4.21.0))
public/games/age-of-dwarves/guide:
dependencies:

View file

@ -0,0 +1,62 @@
{
"schema_version": "1",
"default": "earthlike",
"earthlike": {
"id": "earthlike",
"name": "Earthlike",
"description": "Familiar continents, moderate climate, balanced rainfall.",
"axes": {
"landmass": "continents",
"climate": "temperate",
"moisture": "balanced",
"age": "mature",
"sea_level": "standard"
},
"param_overrides": {
"plate_count": 10,
"convergent_bias": 0.40,
"divergent_bias": -0.25,
"sea_level": 0.28,
"ocean_percentage": { "target": 0.40 },
"temp_offset": 0.0,
"latitude_gradient": 0.7,
"base_precip_offset": 0.0,
"windward_boost": 1.5,
"leeward_factor": 0.4,
"erosion_iterations": 5,
"seasonality_scale": 0.8,
"rain_shadow_factor": 1.0
},
"thumbnail": "world_shapes/previews/earthlike.png",
"is_default": true
},
"axes": {
"landmass": ["pangaea", "continents", "archipelago", "islands", "shattered"],
"climate": ["cold", "temperate", "hot", "extreme"],
"moisture": ["arid", "dry", "balanced", "wet", "lush"],
"age": ["young", "mature", "ancient"],
"sea_level": ["low", "standard", "high"]
},
"presets": [
"landmass/pangaea.json",
"landmass/continents.json",
"landmass/archipelago.json",
"landmass/islands.json",
"landmass/shattered.json",
"climate/cold.json",
"climate/temperate.json",
"climate/hot.json",
"climate/extreme.json",
"moisture/arid.json",
"moisture/dry.json",
"moisture/balanced.json",
"moisture/wet.json",
"moisture/lush.json",
"age/young.json",
"age/mature.json",
"age/ancient.json",
"sea_level/low.json",
"sea_level/standard.json",
"sea_level/high.json"
]
}

View file

@ -1,11 +1,12 @@
//! WASM API surface — exposes Rust simulation to the web guide via wasm-bindgen.
//! All public functions match the signatures specified in Task #2.
//! See: public/games/age-of-dwarves/docs/terrain/WORLDGEN_PIPELINE.md
use wasm_bindgen::prelude::*;
use mc_core::grid::{GridState, biome_registry::{has_tag, BiomeTag}};
use mc_climate::{ClimatePhysics, EcologyPhysics, step_atmospheric_chemistry};
use mc_mapgen::MapGenerator;
use mc_mapgen::{MapGenerator, seed::{derive as derive_seed, SeedDomain}};
/// WASM-exposed grid handle wrapping GridState.
#[wasm_bindgen]
@ -36,6 +37,19 @@ impl WasmGrid {
Ok(Self { inner })
}
/// Run the full worldgen pipeline (tectonics → climate → erosion → hydrology)
/// and return a populated WasmGrid ready for tile_*_json queries.
/// `map_size` accepts "duel" | "tiny" | "small" | "standard" | "large" | "huge".
/// Used by the design lab's per-layer playground pages (p1-53).
/// See: public/games/age-of-dwarves/docs/terrain/WORLDGEN_PIPELINE.md
#[wasm_bindgen(js_name = "generateForLab")]
pub fn generate_for_lab(seed: u64, map_size: &str) -> WasmGrid {
let gen = MapGenerator::new("{}");
WasmGrid {
inner: gen.generate(seed, map_size),
}
}
#[wasm_bindgen(getter)]
pub fn width(&self) -> i32 {
self.inner.width
@ -329,6 +343,25 @@ impl WasmEcologyPhysics {
}
}
/// Derive a deterministic sub-seed for the given domain from a map seed.
/// `domain` matches SeedDomain discriminants: 0=Tectonics, 1=Erosion,
/// 2=Hydrology, 3=Climate, 4=FloraSelect, 5=FaunaSelect.
/// Used by the RNG playground page (p1-53).
/// See: public/games/age-of-dwarves/docs/terrain/WORLDGEN_PIPELINE.md
#[wasm_bindgen(js_name = "seedDerive")]
pub fn wasm_seed_derive(map_seed: u64, domain: u8) -> u64 {
let d = match domain {
0 => SeedDomain::Tectonics,
1 => SeedDomain::Erosion,
2 => SeedDomain::Hydrology,
3 => SeedDomain::Climate,
4 => SeedDomain::FloraSelect,
5 => SeedDomain::FaunaSelect,
_ => SeedDomain::Tectonics,
};
derive_seed(map_seed, d)
}
/// WASM-exposed atmospheric chemistry step.
#[wasm_bindgen(js_name = "stepAtmosphericChemistry")]
pub fn wasm_step_atmospheric_chemistry(grid: &mut WasmGrid, spec_json: &str) {