Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
140 lines
6.0 KiB
Python
140 lines
6.0 KiB
Python
"""
|
|
Presets and travel modes: the two controls meant to be usable without tooltips.
|
|
"""
|
|
import pytest
|
|
import torch
|
|
|
|
from helpers import FakeModel
|
|
from snk.core import presets, shader_noise
|
|
from snk.core.presets import PRESETS, TRAVEL_MODES, apply_preset, basis_for, is_collapse
|
|
|
|
CPU = torch.device("cpu")
|
|
PARAMS = {"scale": 1.0, "octaves": 2.0, "warp_strength": 0.7, "phase_shift": 0.5,
|
|
"time": 0.0, "shape_type": "none", "color_scheme": "none", "intensity": 0.8}
|
|
|
|
|
|
# --- travel modes ---------------------------------------------------------------------
|
|
|
|
@pytest.mark.parametrize("shape", [(1, 4, 48, 48), (1, 24, 5, 16, 16), (1, 128, 16, 16)])
|
|
def test_the_modes_are_ordered_by_how_wide_they_leave_the_noise(shape):
|
|
ranks = {}
|
|
for mode in ("jump", "drift", "walk"):
|
|
noise = shader_noise.generate(shape, PARAMS, "domain_warp", 8888, CPU,
|
|
decorrelate=True, basis=basis_for(mode))
|
|
ranks[mode] = shader_noise.effective_channel_rank(noise)
|
|
|
|
assert ranks["jump"] == pytest.approx(1.0, abs=0.01), "jump is a deliberate collapse"
|
|
if shape[1] > basis_for("drift"):
|
|
# Strict, so drift collapsing into walk fails instead of passing as equal.
|
|
assert ranks["jump"] < ranks["drift"] < ranks["walk"], ranks
|
|
else:
|
|
# Four channels are all the directions there are: drift has nothing to narrow.
|
|
assert ranks["jump"] < ranks["drift"] == ranks["walk"], ranks
|
|
|
|
|
|
@pytest.mark.parametrize("shader_type", ["domain_warp", "tensor_field", "curl_noise",
|
|
"temporal_coherent", "gaussian"])
|
|
def test_jump_collapses_every_generator_not_just_the_narrow_ones(shader_type):
|
|
"""
|
|
jump has to mean the same thing whatever shader is chosen, otherwise
|
|
shader_type is not a usable coordinate in jump-space. tensor_field natively
|
|
spans 90 of 128 channels and must still come back at rank 1.
|
|
"""
|
|
shape = (1, 24, 5, 16, 16)
|
|
noise = shader_noise.generate(shape, PARAMS, shader_type, 8888, CPU,
|
|
decorrelate=True, basis=basis_for("jump"))
|
|
assert shader_noise.effective_channel_rank(noise) == pytest.approx(1.0, abs=0.01)
|
|
|
|
|
|
def test_jump_is_not_blocked_by_the_widening_guards():
|
|
"""
|
|
The guards exist to stop a remix narrowing the noise. jump wants exactly that,
|
|
so it must bypass them rather than be silently turned into a no-op.
|
|
"""
|
|
shape = (1, 24, 5, 16, 16)
|
|
stock = shader_noise.generate(shape, PARAMS, "tensor_field", 8888, CPU)
|
|
jumped = shader_noise.generate(shape, PARAMS, "tensor_field", 8888, CPU,
|
|
decorrelate=True, basis=basis_for("jump"))
|
|
assert shader_noise.effective_channel_rank(stock) > 10
|
|
assert shader_noise.effective_channel_rank(jumped) < 1.5
|
|
assert not torch.equal(stock, jumped)
|
|
|
|
|
|
def test_an_unknown_mode_falls_back_rather_than_raising():
|
|
assert basis_for("teleport") == TRAVEL_MODES[presets.DEFAULT_TRAVEL_MODE]
|
|
assert is_collapse("jump") and not is_collapse("walk")
|
|
|
|
|
|
# --- presets --------------------------------------------------------------------------
|
|
|
|
def test_custom_changes_nothing():
|
|
values = dict(shader_type="curl_noise", shader_strength=0.9, blend_mode="add",
|
|
travel_mode="drift", stage_progression="fine_to_coarse",
|
|
shape_type="rays", normalize_strength=False)
|
|
assert apply_preset("custom", values) == values
|
|
|
|
|
|
def test_an_unknown_preset_is_treated_as_custom():
|
|
"""A workflow saved against a later version must still run here."""
|
|
values = dict(shader_strength=0.42)
|
|
assert apply_preset("hyperdrive", values) == values
|
|
|
|
|
|
@pytest.mark.parametrize("name", [n for n in PRESETS if n != "custom"])
|
|
def test_every_preset_sets_a_complete_consistent_bundle(name):
|
|
"""
|
|
The point of a preset is that the settings agree with each other, so a partial
|
|
one would leave a stale widget value in the middle of a tuned combination.
|
|
"""
|
|
overrides = PRESETS[name]
|
|
assert set(overrides) == set(presets.PRESET_KEYS), name
|
|
assert overrides["travel_mode"] in TRAVEL_MODES
|
|
assert 0.0 <= overrides["shader_strength"] <= 1.0
|
|
assert overrides["normalize_strength"] is True, "strengths are quoted on one scale"
|
|
assert name in presets.PRESET_DESCRIPTIONS
|
|
|
|
|
|
def test_a_preset_overrides_the_widgets_it_names():
|
|
got = apply_preset("explore", dict(shader_strength=0.99, blend_mode="add", seed=7))
|
|
assert got["shader_strength"] == PRESETS["explore"]["shader_strength"]
|
|
assert got["blend_mode"] == PRESETS["explore"]["blend_mode"]
|
|
assert got["seed"] == 7, "inputs a preset does not name are left alone"
|
|
|
|
|
|
def test_exclusion_protects_a_walked_parameter():
|
|
"""Otherwise a preset that pins strength turns a strength ramp into one frame."""
|
|
got = apply_preset("explore", dict(shader_strength=0.77), exclude=("shader_strength",))
|
|
assert got["shader_strength"] == 0.77
|
|
|
|
|
|
def test_jump_and_stamp_actually_jump():
|
|
assert PRESETS["jump"]["travel_mode"] == "jump"
|
|
assert PRESETS["stamp"]["travel_mode"] == "jump"
|
|
assert PRESETS["stamp"]["shape_type"] != "none", "stamp exists to draw the mask"
|
|
|
|
|
|
def test_the_node_offers_exactly_the_presets_that_exist():
|
|
from snk.direct_shader_ksampler import DirectShaderNoiseKSampler
|
|
|
|
spec = DirectShaderNoiseKSampler.INPUT_TYPES()
|
|
offered = spec["optional"]["preset"][0]
|
|
assert set(offered) == set(PRESETS)
|
|
assert offered[0] == "custom", "the do-nothing option should read first"
|
|
|
|
modes = spec["optional"]["travel_mode"][0]
|
|
assert set(modes) == set(TRAVEL_MODES)
|
|
assert spec["optional"]["travel_mode"][1]["default"] == presets.DEFAULT_TRAVEL_MODE
|
|
|
|
|
|
def test_the_frontend_is_served_the_table_the_node_applies():
|
|
"""The panel writes widgets from this route; a second copy of the table would drift."""
|
|
import asyncio
|
|
import json
|
|
|
|
from snk.api_routes import get_presets
|
|
|
|
served = json.loads(asyncio.run(get_presets(None)).text)
|
|
assert served["presets"] == PRESETS
|
|
assert served["descriptions"] == presets.PRESET_DESCRIPTIONS
|
|
assert served["keys"] == list(presets.PRESET_KEYS)
|