spriteforge/tests/test_procedural.py

75 lines
3.9 KiB
Python

import hashlib
from pathlib import Path
import numpy as np
import pytest
from spriteforge.backends import BackendContext, create_backend
from spriteforge.backends.procedural import generator_names
from spriteforge.manifest import AssetSpec
from spriteforge.postprocess import build_palette, encode_frames, normalize_asset
def spec(generator:str,**params)->AssetSpec:
return AssetSpec(backend="procedural",generator=generator,params=params,dirs=1,seed=123)
@pytest.mark.parametrize("name",["floor_tile","wall_tile","decal"])
def test_generators_are_deterministic_and_nonempty(name):
backend=create_backend("procedural");context=BackendContext(Path.cwd(),"test")
a=backend.generate(spec(name),context);b=backend.generate(spec(name),context)
assert np.array_equal(a.frames[0].rgba,b.frames[0].rgba)
assert np.count_nonzero(a.frames[0].rgba[...,3])>0
assert a.frames[0].rgba.dtype==np.uint8
def test_seed_changes_noise():
backend=create_backend("procedural");context=BackendContext(Path.cwd(),"test")
a=backend.generate(spec("floor_tile"),context)
changed=spec("floor_tile").model_copy(update={"seed":124})
b=backend.generate(changed,context)
assert not np.array_equal(a.frames[0].rgba,b.frames[0].rgba)
def test_variants_and_postprocess_streams():
raw=create_backend("procedural").generate(spec("decal",variants=3,width=31,height=17),BackendContext(Path.cwd(),"x"))
normalized=normalize_asset(raw,"24x12")
palette=build_palette([normalized]);frames=encode_frames(normalized,palette,dither=True)
assert len(frames)==3 and len(palette)==256
assert all(f.width<=24 and f.height<=12 for f in frames)
assert all(f.normals_xy is not None and f.depth is not None for f in frames)
def test_explicit_source_scale_downsamples_small_supersampled_silhouette():
raw=create_backend("procedural").generate(spec("decal",width=40,height=20),BackendContext(Path.cwd(),"x"))
normalized=normalize_asset(raw,"96x112",0.25)
frame=normalized.frames[0]
assert frame.rgba.shape[1]<=10 and frame.rgba.shape[0]<=5
def test_unknown_generator_is_textual():
with pytest.raises(ValueError,match="available"):
create_backend("procedural").generate(spec("missing"),BackendContext(Path.cwd(),"x"))
def test_unknown_generator_parameter_is_rejected():
with pytest.raises(ValueError,match="heigth"):
create_backend("procedural").generate(spec("wall_tile",heigth=20),BackendContext(Path.cwd(),"x"))
def test_registry_contains_three_generators():
assert {"floor_tile","wall_tile","decal"}<=set(generator_names())
def test_single_frame_generator_still_refuses_extra_directions():
turned=spec("floor_tile").model_copy(update={"dirs":4})
with pytest.raises(ValueError,match="dirs: 1"):
create_backend("procedural").generate(turned,BackendContext(Path.cwd(),"x"))
# Слепки старых генераторов: расширение бэкенда последовательностями не имеет
# права поменять ни один байт уже собранных ассетов.
GOLDEN={"floor_tile":("4d1c4610ac48af25d8acdc8f33f6f75416ca493b764dbc45d35afd5d99d38cf5",{"width":32,"height":16,"variants":2}),
"wall_tile":("3d89b51db3989e1c9fad63a3f0682d504a8f7a5055a4ab94c19a69351c344096",{"width":32,"top_height":16,"height":24}),
"decal":("654dc4cff086dafb1285914e78120055e2b9adbcfd0697011968b6176697c17f",{"width":24,"height":12})}
@pytest.mark.parametrize("name",sorted(GOLDEN))
def test_existing_generators_are_byte_identical(name):
digest,params=GOLDEN[name]
frozen=AssetSpec(backend="procedural",generator=name,params=params,dirs=1,seed=77,fps=10)
asset=create_backend("procedural").generate(frozen,BackendContext(Path.cwd(),"t"))
hasher=hashlib.sha256()
for frame in asset.frames:
hasher.update(frame.rgba.tobytes())
hasher.update(str((frame.pivot_x,frame.pivot_y,frame.duration_ms)).encode())
assert hasher.hexdigest()==digest