aboutsummaryrefslogtreecommitdiff
path: root/crates/texm/src/tests.rs
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-02-19 03:46:23 +0300
committerValentin Popov <valentin@popov.link>2026-02-19 03:46:23 +0300
commit0e19660eb5122c8c52d5e909927884ad5c50b813 (patch)
tree6a53c24544ca828f08c2b6872d568b1edc1a4cef /crates/texm/src/tests.rs
parent8a69872576eed41a918643be52a80fe74a054974 (diff)
downloadfparkan-0e19660eb5122c8c52d5e909927884ad5c50b813.tar.xz
fparkan-0e19660eb5122c8c52d5e909927884ad5c50b813.zip
Refactor documentation structure and add new specifications
- Updated MSH documentation to reflect changes in material, wear, and texture specifications. - Introduced new `render.md` file detailing the render pipeline process. - Removed outdated sections from `runtime-pipeline.md` and redirected to `render.md`. - Added detailed specifications for `Texm` texture format and `WEAR` wear table. - Updated navigation in `mkdocs.yml` to align with new documentation structure.
Diffstat (limited to 'crates/texm/src/tests.rs')
-rw-r--r--crates/texm/src/tests.rs150
1 files changed, 150 insertions, 0 deletions
diff --git a/crates/texm/src/tests.rs b/crates/texm/src/tests.rs
new file mode 100644
index 0000000..d021346
--- /dev/null
+++ b/crates/texm/src/tests.rs
@@ -0,0 +1,150 @@
+use super::*;
+use nres::Archive;
+use std::fs;
+use std::path::{Path, PathBuf};
+
+fn collect_files_recursive(root: &Path, out: &mut Vec<PathBuf>) {
+ let Ok(entries) = fs::read_dir(root) else {
+ return;
+ };
+ for entry in entries.flatten() {
+ let path = entry.path();
+ if path.is_dir() {
+ collect_files_recursive(&path, out);
+ } else if path.is_file() {
+ out.push(path);
+ }
+ }
+}
+
+fn nres_test_files() -> Vec<PathBuf> {
+ let root = Path::new(env!("CARGO_MANIFEST_DIR"))
+ .join("..")
+ .join("..")
+ .join("testdata");
+ let mut files = Vec::new();
+ collect_files_recursive(&root, &mut files);
+ files.sort();
+ files
+ .into_iter()
+ .filter(|path| {
+ fs::read(path)
+ .map(|bytes| bytes.get(0..4) == Some(b"NRes"))
+ .unwrap_or(false)
+ })
+ .collect()
+}
+
+#[test]
+fn texm_parse_all_game_textures() {
+ let archives = nres_test_files();
+ if archives.is_empty() {
+ eprintln!("skipping texm_parse_all_game_textures: no NRes files in testdata");
+ return;
+ }
+
+ let mut texm_total = 0usize;
+ let mut texm_with_page = 0usize;
+ for archive_path in archives {
+ let archive = Archive::open_path(&archive_path)
+ .unwrap_or_else(|err| panic!("failed to open {}: {err}", archive_path.display()));
+
+ for entry in archive.entries() {
+ if entry.meta.kind != TEXM_MAGIC {
+ continue;
+ }
+ texm_total += 1;
+ let payload = archive.read(entry.id).unwrap_or_else(|err| {
+ panic!(
+ "failed to read Texm entry '{}' in {}: {err}",
+ entry.meta.name,
+ archive_path.display()
+ )
+ });
+ let texture = parse_texm(payload.as_slice()).unwrap_or_else(|err| {
+ panic!(
+ "failed to parse Texm '{}' in {}: {err}",
+ entry.meta.name,
+ archive_path.display()
+ )
+ });
+ if !texture.page_rects.is_empty() {
+ texm_with_page += 1;
+ }
+
+ assert!(
+ texture.core_size() <= payload.as_slice().len(),
+ "core size must be within payload for '{}' in {}",
+ entry.meta.name,
+ archive_path.display()
+ );
+ assert_eq!(
+ usize::try_from(texture.header.mip_count).ok(),
+ Some(texture.mip_levels.len()),
+ "mip count mismatch for '{}' in {}",
+ entry.meta.name,
+ archive_path.display()
+ );
+ }
+ }
+
+ assert!(texm_total > 0, "no Texm textures found");
+ assert!(
+ texm_with_page > 0,
+ "expected at least one Texm texture with Page chunk"
+ );
+}
+
+#[test]
+fn texm_parse_minimal_argb8888_no_page() {
+ let mut payload = Vec::new();
+ payload.extend_from_slice(&TEXM_MAGIC.to_le_bytes());
+ payload.extend_from_slice(&1u32.to_le_bytes()); // width
+ payload.extend_from_slice(&1u32.to_le_bytes()); // height
+ payload.extend_from_slice(&1u32.to_le_bytes()); // mip_count
+ payload.extend_from_slice(&0u32.to_le_bytes()); // flags4
+ payload.extend_from_slice(&0u32.to_le_bytes()); // flags5
+ payload.extend_from_slice(&0u32.to_le_bytes()); // unk6
+ payload.extend_from_slice(&8888u32.to_le_bytes()); // format
+ payload.extend_from_slice(&[1, 2, 3, 4]); // one pixel
+
+ let parsed = parse_texm(&payload).expect("failed to parse minimal texm");
+ assert_eq!(parsed.header.width, 1);
+ assert_eq!(parsed.header.height, 1);
+ assert_eq!(parsed.mip_levels.len(), 1);
+ assert!(parsed.page_rects.is_empty());
+}
+
+#[test]
+fn texm_parse_indexed_with_page_chunk() {
+ let mut payload = Vec::new();
+ payload.extend_from_slice(&TEXM_MAGIC.to_le_bytes());
+ payload.extend_from_slice(&2u32.to_le_bytes()); // width
+ payload.extend_from_slice(&2u32.to_le_bytes()); // height
+ payload.extend_from_slice(&1u32.to_le_bytes()); // mip_count
+ payload.extend_from_slice(&0u32.to_le_bytes()); // flags4
+ payload.extend_from_slice(&0u32.to_le_bytes()); // flags5
+ payload.extend_from_slice(&0u32.to_le_bytes()); // unk6
+ payload.extend_from_slice(&0u32.to_le_bytes()); // format indexed8
+ payload.extend_from_slice(&[0u8; 1024]); // palette
+ payload.extend_from_slice(&[1, 2, 3, 4]); // pixels
+ payload.extend_from_slice(&PAGE_MAGIC.to_le_bytes());
+ payload.extend_from_slice(&1u32.to_le_bytes()); // rect_count
+ payload.extend_from_slice(&0i16.to_le_bytes()); // x
+ payload.extend_from_slice(&2i16.to_le_bytes()); // w
+ payload.extend_from_slice(&0i16.to_le_bytes()); // y
+ payload.extend_from_slice(&2i16.to_le_bytes()); // h
+
+ let parsed = parse_texm(&payload).expect("failed to parse indexed texm");
+ assert!(parsed.palette.is_some());
+ assert_eq!(parsed.page_rects.len(), 1);
+ assert_eq!(
+ parsed.page_rects[0],
+ PageRect {
+ x: 0,
+ w: 2,
+ y: 0,
+ h: 2
+ }
+ );
+}