fix: review findings

- screen transitions snapshot the previous frame (ratatui swaps buffers
  after draw, so the old snapshot was always blank); frame loop extracted
  into a testable Runner
- .city loading rejects structurally inconsistent files: Grid deserialize
  checks size vs data, load_city checks header vs body and runs
  City::validate (now also checks Net link endpoints)
- saving from the map hands the live map screen back instead of a fresh
  explore screen
- signage glyphs covered by the unicode-width test

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Jeffrey Wilcke
2026-10-12 18:23:15 +02:00
parent 3c75674489
commit 59ba72f0e0
6 changed files with 286 additions and 41 deletions

View File

@@ -189,13 +189,42 @@ impl Dir {
}
}
/// Dense row-major grid of `T`.
/// Dense row-major grid of `T`. Deserialization rejects data that does not match `size`.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(try_from = "RawGrid<T>")]
#[serde(bound(deserialize = "T: Deserialize<'de>"))]
pub struct Grid<T> {
size: Size,
data: Vec<T>,
}
/// The on-disk shape of a grid, before the size/data consistency check.
#[derive(Deserialize)]
struct RawGrid<T> {
size: Size,
data: Vec<T>,
}
impl<T> TryFrom<RawGrid<T>> for Grid<T> {
type Error = String;
fn try_from(raw: RawGrid<T>) -> Result<Self, Self::Error> {
if raw.data.len() != raw.size.area() {
return Err(format!(
"grid of {}x{} needs {} cells, found {}",
raw.size.w,
raw.size.h,
raw.size.area(),
raw.data.len()
));
}
Ok(Grid {
size: raw.size,
data: raw.data,
})
}
}
impl<T: Clone> Grid<T> {
pub fn filled(size: Size, v: T) -> Self {
Self {
@@ -308,6 +337,24 @@ mod tests {
assert_eq!(g.count(|&v| v == 9), 4);
}
#[test]
fn grid_rejects_inconsistent_serialized_data() {
#[derive(serde::Serialize)]
struct Fake {
size: Size,
data: Vec<u8>,
}
let bad = ron::to_string(&Fake {
size: Size { w: 4, h: 3 },
data: vec![0; 5],
})
.unwrap();
assert!(ron::from_str::<Grid<u8>>(&bad).is_err());
let good = ron::to_string(&Grid::filled(Size { w: 4, h: 3 }, 7u8)).unwrap();
let g: Grid<u8> = ron::from_str(&good).unwrap();
assert_eq!(g.count(|&v| v == 7), 12);
}
#[test]
fn dir_delta_opposite() {
assert_eq!(Dir::N.delta(), (0, -1));