hull_source: delete the nose-ward muzzle rule; gun facing is HullData's

The Blender scene cannot know which way a hardpoint points: HullData's
hardpoint rotation owns that (ruling aetheria-release:ruling:gun-facing-is-hulldata).
Deletes the forward list and its term in the muzzle check, the two facing
tests, and the smoke's nose-ward require. Pairing stays whole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
soul 2026-10-21 20:26:02 +02:00
commit ee78b85fd0
3 changed files with 5 additions and 21 deletions

View file

@ -344,13 +344,11 @@ def _checks(hull, own, judge):
by_id = {a.id: a for a in hull.anchors}
unmuzzled = [a.id for a in hull.anchors if a.role == "weapon-mount"
and not any(m.role == "weapon-muzzle" and m.parent == a.id for m in hull.anchors)]
forward = [m.id for m in hull.anchors if m.role == "weapon-muzzle" and m.parent in by_id
and by_id[m.parent].role == "weapon-mount" and m.position[1] >= by_id[m.parent].position[1]]
orphans = [m.id for m in hull.anchors if m.role == "weapon-muzzle"
and (m.parent not in by_id or by_id[m.parent].role != "weapon-mount")]
checks.append(Check("every weapon-mount has a muzzle nose-ward of it and no muzzle is orphaned",
not (unmuzzled or forward or orphans),
f"{len(unmuzzled)} without a muzzle, {len(forward)} not nose-ward, {len(orphans)} orphaned"))
checks.append(Check("every weapon-mount has a muzzle and no muzzle is orphaned",
not (unmuzzled or orphans),
f"{len(unmuzzled)} without a muzzle, {len(orphans)} orphaned"))
return checks

View file

@ -215,7 +215,6 @@ def one_hull(scene, number, fixture, ship_id, extent, length, rows, body, palett
for muzzle in roles["weapon-muzzle"]:
mount = first[muzzle].parent
require(mount.get("aetheria.role") == "weapon-mount" and first[muzzle].get("aetheria.order") == 1, "A muzzle is not ordered under its mount")
require(first[muzzle].matrix_world.translation.y < mount.matrix_world.translation.y, "A muzzle is not nose-ward of its mount")
require(layout_rows(scene) == rows_before, "Build changed the layout buffer")
check_poses(collection, root, runner)
if fixture == "zh-ammo-tender":

View file

@ -57,7 +57,7 @@ class Fixture:
def __init__(self):
self.library = {"zhestokost": language()}
self.hull_lang = None
# Body 6 wide, 10 long, 2 high (h/w 0.33, w/l 0.6); a bilateral plate on top; one gun with a muzzle nose-ward.
# Body 6 wide, 10 long, 2 high (h/w 0.33, w/l 0.6); a bilateral plate on top; one gun with a muzzle.
self.parts = {
"brief": ("", brief()),
"primary": (box(-3, 3, -5, 5, 0, 2), ""),
@ -221,20 +221,7 @@ class JudgementTests(unittest.TestCase):
fixture.parts["brief"] = ("", brief(length=length))
self.assertEqual(fixture.failed(), ["the length lies inside the brief's range"], length)
MUZZLE = "every weapon-mount has a muzzle nose-ward of it and no muzzle is orphaned"
def test_a_muzzle_aft_of_its_mount_is_refused(self):
fixture = Fixture()
fixture.parts["anchors"] = ("", echo("anchor", ["Gun.A", "weapon-mount", [0, 0, 3], 0, ""])
+ echo("anchor", ["Gun.A.muzzle", "weapon-muzzle", [0, 1, 3], 0, "Gun.A"])
+ echo("anchor", ["Th.A", "thruster-emitter", [1, 5, 1], 180, ""]))
self.assertEqual(fixture.failed(), [self.MUZZLE])
def test_a_muzzle_level_with_its_mount_is_not_nose_ward(self):
fixture = Fixture()
fixture.parts["anchors"] = ("", echo("anchor", ["Gun.A", "weapon-mount", [0, 0, 3], 0, ""])
+ echo("anchor", ["Gun.A.muzzle", "weapon-muzzle", [1, 0, 3], 0, "Gun.A"]))
self.assertEqual(fixture.failed(), [self.MUZZLE])
MUZZLE = "every weapon-mount has a muzzle and no muzzle is orphaned"
def test_a_mount_without_a_muzzle_and_a_muzzle_without_a_mount_are_refused(self):
fixture = Fixture()