the huge refactor

This commit is contained in:
2026-07-24 21:52:05 -05:00
parent 5146cc9bcc
commit 917f4b1bf0
43 changed files with 1807 additions and 3267 deletions
+30 -28
View File
@@ -5,13 +5,14 @@
//! give boards some non-distracting visual flavor (textured ground) with little
//! authoring effort, including randomly-generated "grass" / "dirt" / "stone".
//!
//! The actual placement / per-cell expansion happens in [`crate::layer`] during
//! The actual placement / per-cell expansion happens in [`crate::board_spec`] during
//! map load: a floor palette entry either names one of these generators (a fresh
//! glyph is rolled per grid cell) or gives a fixed glyph. This module only owns
//! the generators themselves.
use crate::glyph::Glyph;
use color::Rgba8;
use serde::{Deserialize, Serialize};
use tinyrand::{Probability, Rand, Seeded, StdRand};
/// A board's floor: the cosmetic backdrop drawn beneath everything, replacing the
@@ -21,7 +22,7 @@ use tinyrand::{Probability, Rand, Seeded, StdRand};
///
/// Three forms: [`Blank`](Floor::Blank) (the canonical empty/black cell shows
/// through), [`Fixed`](Floor::Fixed) (one glyph tiled across the whole board), or
/// [`Biome`](Floor::Biome) (a procedural [`FloorGenerator`] texture). A biome keeps
/// [`Biome`](Floor::Biome) (a procedural [`FloorBiome`] texture). A biome keeps
/// its generator (so save re-emits the generator name) alongside a per-cell glyph
/// buffer pre-rolled once at load from [`FLOOR_SEED`] — deterministic, and the
/// direct replacement for the old per-cell floor-layer rolling.
@@ -35,7 +36,7 @@ pub enum Floor {
/// `glyphs` buffer holding one pre-rolled glyph per cell (row-major).
Biome {
/// The generator this biome was built from; re-emitted on save.
generator: FloorGenerator,
generator: FloorBiome,
/// One pre-rolled glyph per cell (`width * height`, row-major).
glyphs: Vec<Glyph>,
},
@@ -52,7 +53,7 @@ impl Floor {
/// Builds a [`Floor::Biome`] for a `width × height` board, pre-rolling one glyph
/// per cell from a [`FLOOR_SEED`]-seeded PRNG (so the result is deterministic and
/// depends only on the board dimensions + generator).
pub(crate) fn biome(generator: FloorGenerator, width: usize, height: usize) -> Floor {
pub(crate) fn biome(generator: FloorBiome, width: usize, height: usize) -> Floor {
let mut rng = StdRand::seed(FLOOR_SEED);
let glyphs = (0..width * height).map(|_| generator.generate(&mut rng)).collect();
Floor::Biome { generator, glyphs }
@@ -80,8 +81,9 @@ pub(crate) const FLOOR_SEED: u64 = 0x_C0FF_EE15_F100_0001;
/// and the probability/character set of its scattered "texture" glyphs; the
/// colors are deliberately dark and low-saturation so foreground objects stay
/// readable against them.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FloorGenerator {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum FloorBiome {
/// Random green-to-greenish-yellow ground with a fairly high chance of grassy
/// characters (comma, period, backquote, apostrophe).
Grass,
@@ -91,24 +93,24 @@ pub enum FloorGenerator {
Stone,
}
impl FloorGenerator {
impl FloorBiome {
/// Parses a generator from its map-file name, or `None` if unrecognized.
pub fn from_name(name: &str) -> Option<FloorGenerator> {
pub fn from_name(name: &str) -> Option<FloorBiome> {
match name {
"grass" => Some(FloorGenerator::Grass),
"dirt" => Some(FloorGenerator::Dirt),
"stone" => Some(FloorGenerator::Stone),
"grass" => Some(FloorBiome::Grass),
"dirt" => Some(FloorBiome::Dirt),
"stone" => Some(FloorBiome::Stone),
_ => None,
}
}
/// The generator's map-file name (inverse of [`from_name`](FloorGenerator::from_name)),
/// The generator's map-file name (inverse of [`from_name`](FloorBiome::from_name)),
/// re-emitted on save so a biome floor round-trips.
pub fn name(&self) -> &'static str {
match self {
FloorGenerator::Grass => "grass",
FloorGenerator::Dirt => "dirt",
FloorGenerator::Stone => "stone",
FloorBiome::Grass => "grass",
FloorBiome::Dirt => "dirt",
FloorBiome::Stone => "stone",
}
}
@@ -121,7 +123,7 @@ impl FloorGenerator {
// (background color ranges, texture probability, texture chars).
let (bg, chars, prob) = match self {
// Green → greenish-yellow: g dominant, a touch of r for the yellow tilt.
FloorGenerator::Grass => (
FloorBiome::Grass => (
Rgba8 {
r: shade(rng, 20, 55),
g: shade(rng, 45, 85),
@@ -132,7 +134,7 @@ impl FloorGenerator {
0.35,
),
// Brown: r highest, g mid, b low.
FloorGenerator::Dirt => (
FloorBiome::Dirt => (
Rgba8 {
r: shade(rng, 45, 75),
g: shade(rng, 30, 50),
@@ -143,7 +145,7 @@ impl FloorGenerator {
0.20,
),
// Gray: all channels share one shade.
FloorGenerator::Stone => {
FloorBiome::Stone => {
let v = shade(rng, 40, 70);
(
Rgba8 {
@@ -199,7 +201,7 @@ mod tests {
/// Rolls `count` glyphs from `generator` against a freshly-seeded RNG, the
/// same way the layer builder does.
fn roll(generator: FloorGenerator, count: usize) -> Vec<Glyph> {
fn roll(generator: FloorBiome, count: usize) -> Vec<Glyph> {
let mut rng = StdRand::seed(FLOOR_SEED);
(0..count).map(|_| generator.generate(&mut rng)).collect()
}
@@ -207,24 +209,24 @@ mod tests {
#[test]
fn from_name_parses_known_generators() {
assert_eq!(
FloorGenerator::from_name("grass"),
Some(FloorGenerator::Grass)
FloorBiome::from_name("grass"),
Some(FloorBiome::Grass)
);
assert_eq!(
FloorGenerator::from_name("dirt"),
Some(FloorGenerator::Dirt)
FloorBiome::from_name("dirt"),
Some(FloorBiome::Dirt)
);
assert_eq!(
FloorGenerator::from_name("stone"),
Some(FloorGenerator::Stone)
FloorBiome::from_name("stone"),
Some(FloorBiome::Stone)
);
assert_eq!(FloorGenerator::from_name("lava"), None);
assert_eq!(FloorBiome::from_name("lava"), None);
}
#[test]
fn grass_generator_stays_in_scheme_and_is_deterministic() {
let a = roll(FloorGenerator::Grass, 64);
let b = roll(FloorGenerator::Grass, 64);
let a = roll(FloorBiome::Grass, 64);
let b = roll(FloorBiome::Grass, 64);
assert_eq!(a.len(), 64);
// Same seed → identical rolls across builds.
assert!(a.iter().zip(&b).all(|(x, y)| x == y));