Merge remote-tracking branch 'origin/master' into eureka/aetheria-release-product-not-offered
This commit is contained in:
commit
25c2a18ab3
5 changed files with 19 additions and 20 deletions
|
|
@ -131,11 +131,11 @@ Cells not filled, plainly: whether a per-body multiplier on c or the core is eve
|
|||
| **Rock** (`Asteroid`) | (belt key, index) is the `ChunkId`. The index order is fixed at generation and never re-sorted, because wear (`ChunkWear`) is keyed by it. | Persisted (Distance, Phase, Size, RotationSpeed) about the belt's parent; wear and respawn live on Zone. | The generator draws a rock. Zone owns its wear and its pose (`AsteroidBelt.Pose`). |
|
||||
| **Period rule** | A property of the belt, not of the rock: the persisted flag `AsteroidBeltData.RidesRail` (MessagePack key 11, JSON `ridesRail`). False: each rock reads its own distance (`PeriodAround(parent, rock.Distance)`), an annulus. True: every rock takes its rail's period (`Orbits[rail].Period`), so a Trojan swarm keeps its shape (ruling `trojans-ride-host-period-with-stations`; on own-distance rails it shears within an hour, B60). | Persisted at generation. A belt saved before key 11 reads false, the annulus it was (CultCache defaults a missing slot, B61). | Zone's constructor alone writes `AsteroidBelt.Periods`, through PeriodAround or the rail's own Period; the flag only says which. Forbidden: a period formula in Pose, BeltTargets, DrawAhead, the renderer or the generator, and a second flag or shape enum read anywhere else. |
|
||||
| **Extent rule** | What a belt's rail adds to its parent's L_F: `AsteroidBelt.Extent(railDistance)`, the outermost rock for an annulus, the rail's own Distance for a swarm, whose rocks' periods never read their own distance. So a swarm moves no well (B61). An edge (Oort-like) belt must add nothing beyond the window, or the family's fade band narrows (B60). | Derived at zone build. | `AsteroidBelt.Extent`, read by Zone (the only WellNode builder in play) and by the station tests' rebuilt tree. The generator's clearance tree never sees a swarm, which is not a GeneratorPlanet. |
|
||||
| **Belt host** | A generator-time relation only. A Trojan host is a planet or gas giant (PlanetMass < mass <= SunMass) whose parent is a body, at a distance > 0. A ring member is never a host: its rail's pull adds its partners' (WellTree.PullAround), so a rail copied beside it would take another period and shear (B61). The swarm's rail copies the host's (Parent, Distance, Phase); that copy is all Zone needs, and nothing persists the host. A lane's host waits on laned-belts. | Lives for one GenerateZone call. | ZoneGenerator.GenerateZone's swarm pass. |
|
||||
| **Belt host** | A generator-time relation only. A Trojan host is a planet or gas giant (PlanetMass < mass <= SunMass) whose parent is a body, at a distance > 0. A ring member is never a host: its rail's pull adds its partners' (WellTree.PullAround), so a rail copied beside it would take another period and shear (B61). The swarm's rail copies the host's (Parent, Distance, Phase); that copy is all Zone needs, and nothing persists the host. A lane's host is a body that keeps its slot with room on both sides of the lane (cut laned-belts: half the eligible bodies, never a Trojan host, one shape per parent). | Lives for one GenerateZone call. | ZoneGenerator.GenerateZone's swarm pass. |
|
||||
| **Lagrange station orbit** | A bodyless OrbitData on a host's rail, 1/6 turn ahead of or behind it (CreateLagrangeOrbit). On a swarm host it sits inside the swarm, a mining outpost among the rocks (ruling `trojans-ride-host-period-with-stations`); rocks are on the floater layer below it, so nothing collides. | Created at generation and persisted. | ZoneGenerator's station placement, unchanged: candidacy reads the GeneratorPlanet tree, where no swarm exists, so no swarm can bar its host. |
|
||||
| **Belt generation settings** | `ZoneGenerationSettings` in Settings.asset (an LFS object): BeltProbability 0.25, BeltMassCeiling 500, AsteroidBeltWidth (a ceiling that never binds, B56), AsteroidCount = sqrt(mass x D) + 11. Trojans: TrojanProbability 0.15 per host, TrojanArc 0.07 turn per swarm, TrojanHalfWidth 0.06 D, TrojanCount = 0.04 D + 8 rocks round each of L4 and L5 (tuning defaults for a flight, B61). The code default of TrojanProbability is 0, so settings built in code draw no swarm. | Authored. | Settings.asset. Forbidden: a tunable shape's numbers as constants in code. The swarm's random stream salt is a ZoneGenerator constant, not a tunable. |
|
||||
|
||||
Cells not filled, plainly: the lane's host set and half-width (laned-belts' map proposes the visual margin, ruling `lanes-two-sided-visual-margin`); where an edge cloud's rocks sit between a root's window and the zone boundary (edge-material's map, rulings `edge-material-root-only` and `edge-material-in-the-dark`). The edge material's place in the potential is not a question: it stays out of the extent, because the law breaks otherwise (B60).
|
||||
Cells not filled, plainly: where an edge cloud's rocks sit between a root's window and the zone boundary (edge-material's map, rulings `edge-material-root-only` and `edge-material-in-the-dark`). The edge material's place in the potential is not a question: it stays out of the extent, because the law breaks otherwise (B60).
|
||||
|
||||
## Rationale
|
||||
|
||||
|
|
@ -180,5 +180,5 @@ Mapped and admitted (2026-10-14, base 11b2d7ad): `aetheria-gravity:cut_spec:cut-
|
|||
- **grid-noise-presentation**: the Sector Waves noise (after grid-lowering on layer 10, drawn only into the nebula patch height) and fine-grid detail move to non-slope presentation (grid-gravity-only-ruled); it also owns the Main Menu's own Surface Displacement brushes (B29, follow-up `menu-grid-brushes`). Waits on grid-lowering.
|
||||
- **top-speed-field**: top speed becomes a hull field; no drag below it, drag on the excess above it; the VelocityLimit clamp (Behaviors/VelocityLimit.cs) and always-on hull drag (Ship.cs:127-129) go; AI pilots brake actively; open: drag shape above the cap, consumables as modifiers, whether loot and wreckage come to rest (aetheria-gravity:ruling:no-drag-below-top-speed, target invariant orbits-do-not-decay). Waits on potential-owner and orbits-from-field.
|
||||
- **belt-shapes** (follow-up `belt-shapes`, ruling `belt-shapes-direction`; B60, B61; order by ruling `trojans-first`). One cut per shape. trojan-swarms is mapped and admitted (2026-10-18, base 67517f40): `aetheria-gravity:cut_spec:cut-trojan-swarms.r1`.
|
||||
- **laned-belts**: only a body with room on both sides of its lane keeps a laned belt, and the lane's half-width is its own visual margin, not the placement Roche margin (ruling `lanes-two-sided-visual-margin`; 468 eligible at the Roche width, B60). Folds in `belt-after-belt-edge-pin`, in the same Place loop.
|
||||
- **laned-belts** (mapped and admitted 2026-10-21, base d52b50f9: `aetheria-gravity:cut_spec:cut-laned-belts.r1`; lane half-width 5 body radii, a flight default): only a body with room on both sides of its lane keeps a laned belt, and the lane's half-width is its own visual margin, not the placement Roche margin (ruling `lanes-two-sided-visual-margin`; 468 eligible at the Roche width, B60). Folds in `belt-after-belt-edge-pin`, in the same Place loop.
|
||||
- **edge-material**: one sparse Oort-like cloud per root (per subzone root in a multi-root zone), from its family's window out toward the zone boundary, nothing at planets' rims (ruling `edge-material-root-only`); kept out of the extent (B60) and skipped by the populated radius, so its rocks sit in the dark fade (ruling `edge-material-in-the-dark`).
|
||||
|
|
|
|||
|
|
@ -11238,7 +11238,7 @@ PIDs are in `S\out\pids.log`, the first 3404 and the last 20348. It then writes
|
|||
- **AH-P1. Checks.** All pass (`S\out\<id>\checks.json`).
|
||||
- Tender: 3468 triangles. The bbox is 10.5 x 23.4 x 5.9 m, inside its 18-28 m size class. Body h/w is 0.42 and w/l
|
||||
is 0.61, inside Zhestokost's ranges [0.25, 0.6] and [0.35, 0.75]. Bilateral symmetry has a worst mirror miss of
|
||||
0.0000 m. It has five mount anchors, and every weapon has a muzzle nose-ward of its mount.
|
||||
0.0000 m. It has five mount anchors, and every weapon mount has its muzzle (gun facing is HullData's, ruling gun-facing-is-hulldata).
|
||||
- Fighter: 1466 triangles and 15 m long. The donor body's h/w is 0.20 and w/l 0.69, inside the R&D ranges. The
|
||||
asymmetry rule holds.
|
||||
- The vertex-based asymmetry figure (0.91 unmirrored) is too lenient, because tessellation alone breaks vertex
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
|
|
|
|||
|
|
@ -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,17 @@ 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"
|
||||
MUZZLE = "every weapon-mount has a muzzle and no muzzle is orphaned"
|
||||
|
||||
def test_a_muzzle_aft_of_its_mount_is_refused(self):
|
||||
def test_a_muzzle_facing_any_way_is_accepted(self):
|
||||
# Which way a gun points is HullData's hardpoint rotation; the hull source never judges it.
|
||||
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", ["Gun.B", "weapon-mount", [0, 0, 3], 0, ""])
|
||||
+ echo("anchor", ["Gun.B.muzzle", "weapon-muzzle", [1, 0, 3], 0, "Gun.B"])
|
||||
+ 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])
|
||||
self.assertEqual(fixture.failed(), [])
|
||||
|
||||
def test_a_mount_without_a_muzzle_and_a_muzzle_without_a_mount_are_refused(self):
|
||||
fixture = Fixture()
|
||||
|
|
@ -242,6 +239,11 @@ class JudgementTests(unittest.TestCase):
|
|||
self.assertEqual(fixture.failed(), [self.MUZZLE])
|
||||
fixture.parts["anchors"] = ("", echo("anchor", ["Gun.A.muzzle", "weapon-muzzle", [0, -3, 3], 0, "Gun.A"]))
|
||||
self.assertEqual(fixture.failed(), [self.MUZZLE])
|
||||
# Another gun's muzzle does not pair this one.
|
||||
fixture.parts["anchors"] = ("", echo("anchor", ["Gun.A", "weapon-mount", [0, 0, 3], 0, ""])
|
||||
+ echo("anchor", ["Gun.A.muzzle", "weapon-muzzle", [0, -3, 3], 0, "Gun.A"])
|
||||
+ echo("anchor", ["Gun.B", "weapon-mount", [2, 0, 3], 0, ""]))
|
||||
self.assertEqual(fixture.failed(), [self.MUZZLE])
|
||||
|
||||
def test_a_muzzle_whose_parent_is_not_a_weapon_mount_is_refused(self):
|
||||
fixture = Fixture()
|
||||
|
|
|
|||
Loading…
Reference in a new issue