"""Guard the invariants that silently break the toolset (AGENTS.md §5, §2.3): python test_namesync.py # asserts; no nwn_gff / Blender needed 1. Every .itp Features leaf is backed by a matching .set [GROUP#] whose first tile is that leaf's model (Name + Tile0). 2. Paint tiles (fills + terrain transitions) stay OUT of every group/feature, so the brush pool is non-empty — an empty pool crashes the New Area wizard (the 2026-07-04 access violation). Regression guard for that crash. 3. The cobble/canal paint matrix covers every corner pattern the brush can request (an incomplete matrix = paint crash). 4. The .itp Terrain folder exposes exactly the paintable terrains + eraser. Runs on make_set.build() and make_itp.build() directly, so it fails fast if group_name / resref / is_paint / placements drift apart. """ import re import tiles import make_set import make_itp def _groups(setf): return {m.group(1): int(m.group(2)) for m in re.finditer(r"\[GROUP\d+\]\nName=(.+?)\n(?:.*\n)*?Tile0=(\d+)", setf)} def _tile_models(setf): return {int(m.group(1)): m.group(2) for m in re.finditer(r"\[TILE(\d+)\]\nModel=(\S+)", setf)} def _features(): return [l for c in make_itp.build()["MAIN"]["value"] if c["ID"]["value"] == 0 for l in c["LIST"]["value"]] def test_features_back_every_group(): setf = "\n".join(make_set.build()) groups, models = _groups(setf), _tile_models(setf) feat = _features() nonpaint = [s for s in tiles.TILES if not tiles.is_paint(s)] assert len(feat) == len(groups) == len(nonpaint), \ f"count mismatch: {len(feat)} features / {len(groups)} groups / {len(nonpaint)} non-paint" for lf in feat: name, resref = lf["NAME"]["value"], lf["RESREF"]["value"] assert name in groups, f".itp feature {name!r} has no matching .set [GROUP#]" tile0 = groups[name] assert models[tile0] == resref, \ f"{name}: group Tile0={tile0} model {models[tile0]!r} != leaf RESREF {resref!r}" def test_paint_tiles_stay_in_brush_pool(): paint = [s for s in tiles.TILES if tiles.is_paint(s)] assert paint, "no paint tiles -> empty brush pool -> New Area crash" grouped = set(_groups("\n".join(make_set.build()))) featured = {l["NAME"]["value"] for l in _features()} for s in paint: gn = tiles.group_name(s) assert gn not in grouped, f"paint tile {gn!r} is grouped -> dropped from brush pool" assert gn not in featured, f"paint tile {gn!r} is a feature -> dropped from brush pool" def test_canal_matrix_complete(): want, _covered, missing = tiles.canal_paint_matrix() assert not missing, f"paint matrix missing {len(missing)} pattern(s): {sorted(missing)}" assert want, "empty matrix?" def test_terrain_folder_is_paintables_plus_eraser(): terr = [l for c in make_itp.build()["MAIN"]["value"] if c["ID"]["value"] == 2 for l in c["LIST"]["value"]] names = {l["RESREF"]["value"] for l in terr} assert names == {"cobble", "canal", "eraser"}, names if __name__ == "__main__": test_features_back_every_group() test_paint_tiles_stay_in_brush_pool() test_canal_matrix_complete() test_terrain_folder_is_paintables_plus_eraser() print("namesync OK")