From 28496f27dfd05ff4e56c2e369ef15c68f162a1e8 Mon Sep 17 00:00:00 2001 From: soul Date: Thu, 22 Oct 2026 01:07:54 +0200 Subject: [PATCH] Hull source smoke: HullType gate (refused as Ship, built as Station, Station refused on the grid) Co-Authored-By: Claude Opus 5.5 --- tools/blender/aetheria_ships/hull_source.py | 4 +- tools/blender/tests/smoke_hull_source.py | 65 ++++++++++++++++++++- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/tools/blender/aetheria_ships/hull_source.py b/tools/blender/aetheria_ships/hull_source.py index 300c926f1..ce5529897 100644 --- a/tools/blender/aetheria_ships/hull_source.py +++ b/tools/blender/aetheria_ships/hull_source.py @@ -289,8 +289,8 @@ def _language(library, faction): def build(runner, library, hull_type): """Reads the hull source through runner (see Runner) and judges it against the languages library(faction) answers with (the ECHO text of that faction's own language file). hull_type is the hull's HullData.HullType name, which the caller - reads; only a "Ship" is judged on the language's proportions. Raises HullSourceError when the source cannot be read at all; - otherwise returns a HullBuild whose failed() lists the checks it breaks.""" + reads; only a "Ship" is judged on the language's proportions. Raises HullSourceError when the source cannot be read at + all; otherwise returns a HullBuild whose failed() lists the checks it breaks.""" _, text = runner("brief") brief = read_brief(text) _, text = runner("lang") diff --git a/tools/blender/tests/smoke_hull_source.py b/tools/blender/tests/smoke_hull_source.py index 8bb9b9c48..06f2db715 100644 --- a/tools/blender/tests/smoke_hull_source.py +++ b/tools/blender/tests/smoke_hull_source.py @@ -38,6 +38,8 @@ the hull.scad into the package and runs the panel's operators: changes nothing in the scene. Her art needs no product. Build and Package refuse copies that share an aetheria.id, selecting the original and the copies, and delete nothing. Package moves no row and turns no mount: every layout row is as authored after it, and a hand-turned mount keeps its yaw. + The ship.cc HullData.HullType decides whether the language's proportions judge the hull: a body far wider than the + language's ships is refused as Ship and built as Station, and a Station wider than the layout grid is refused. """ import dataclasses @@ -698,6 +700,66 @@ def agent_guards(scene): return collection +def hull_type_gate(scene): + """The hull's HullData.HullType in ship.cc decides whether the language's proportions judge it; every hull type must + fit the layout grid. A body far wider than a Zhestokost ship is refused as Ship, built as Station, and a Station + wider than 32 cells is refused on the grid.""" + fixture, _, extent, length, rows, *_ = HULLS[0] + ship_id = "smoke.hull.role" + collection, box, package = make_ship(scene, fixture, ship_id, extent, length, rows) + ship_module = sys.modules["aetheria_ships.ship_cc"] + path = str(package / "ship.cc") + source = package / "hull.scad" + original = source.read_text() + slab = '\nif (part == "primary") translate([-%d, -2, 0]) cube([%d, 4, 1]);\n' + source.write_text(original + slab % (15, 30)) + judged = [] + real = hull_source.build + + def recorded(*args): + judged.append(real(*args)) + return judged[-1] + + sys.modules["aetheria_ships"].hull_source.build = recorded + try: + require(ship_module.read_hull_type(path, PACKAGES) == "Ship", "New Ship did not write a Ship hull") + select(box) + result, message = run(bpy.ops.aetheria.build_hull_source) + require(result == {"CANCELLED"} and "the body's proportions lie inside the language's ranges" in message, + f"A Ship far wider than its language allows was built: {message!r}") + print("PASS 5.1 a body wider than the language's ships is refused as Ship, on proportion") + + store = ship_module.read(path, PACKAGES).store + for envelope in store.pull_all(): + if envelope.type == ship_module.HULL_SCHEMA: + values = msgpack.unpackb(envelope.payload, raw=False) + values[ship_module.HULL_TYPE_SLOT] = 1 + store.push(dataclasses.replace(envelope, payload=msgpack.packb(values, use_bin_type=True))) + require(ship_module.read_hull_type(path, PACKAGES) == "Station", "The pushed HullType did not read back as Station") + select(box) + result, message = run(bpy.ops.aetheria.build_hull_source) + names = [c.name for c in judged[-1].checks] + require(result == {"FINISHED"} and judged[-1].hull_type == "Station" + and "the body's proportions lie inside the language's ranges" not in names + and "the top view fits the layout grid" in names, + f"The same body as Station was refused or judged on proportion: {message!r} {names}") + print("PASS 5.2 the same body as Station is built, with no proportion check and a grid check") + + built = set(generated(collection)) + source.write_text(original + slab % (35, 70)) + select(box) + result, message = run(bpy.ops.aetheria.build_hull_source) + require(result == {"CANCELLED"} and "the top view fits the layout grid" in message + and "the body's proportions lie inside the language's ranges" not in message + and set(generated(collection)) == built, + f"A Station 70 m wide was not refused on the layout grid, or the refusal changed the scene: {message!r}") + print("PASS 5.3 a Station 70 m wide is refused on the layout grid, not on proportion") + finally: + sys.modules["aetheria_ships"].hull_source.build = real + print("HULL_TYPE_GATE_OK", flush=True) + return collection + + def main(): require(bpy.app.version[:2] == (5, 2), f"This smoke is for Blender 5.2; this is {bpy.app.version_string}") bpy.data.objects.remove(bpy.data.objects["Cube"], do_unlink=True) @@ -733,6 +795,7 @@ def main(): previous = one_hull(scene, number, fixture, ship_id, extent, length, rows, body, palette, maker, other) clear(previous) clear(agent_guards(scene)) + clear(hull_type_gate(scene)) never_built(scene) @@ -750,7 +813,7 @@ else: print("SMOKE_HULL_SOURCE_OK", flush=True) code = 0 finally: - for _, ship_id, *_ in HULLS + ((None, "smoke.hull.unbuilt"), (None, "smoke.hull.guards")): + for _, ship_id, *_ in HULLS + ((None, "smoke.hull.unbuilt"), (None, "smoke.hull.guards"), (None, "smoke.hull.role")): shutil.rmtree(MODS / ship_id, ignore_errors=True) if created: shutil.rmtree(MODS, ignore_errors=True)