Compare commits

...
Sign in to create a new pull request.
Author SHA1 Message Date
soul
a4be037a42 LFS lives on the Eureka forge: .lfsconfig points every clone at it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 03:12:31 +01:00
soul
152b9251a9 Research: release pipeline prior art (Unity CI, Personal licensing, Forgejo runners and releases, versioning tools)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 23:00:49 +01:00
soul
01bed8d447 AetheriaBuild: -aetheriaTarget win64|linux64, one entry point for both players
The target names the Unity player, its executable and the runtime AetherDb is published for.
Without the argument it builds win64 as before. Built 0.2.0 for both targets on Starfire
(Linux cross-built with Mono); both stop at the aetherdb step, because Aetheria compiles
CultMath from two CultLib pins and dotnet publish refuses the duplicated shader files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:51:42 +01:00
soul
3f3c59e27d Dithered Particles: a Deferred pass, so particles reach the depth texture the nebula fogs by
The shader dithered and wrote depth, but its one pass had no LightMode, so under the deferred
camera it drew in the forward pass, after the depth texture is taken from the G-buffer. The
nebula march read no depth there and fogged straight over every dithered particle (missile
trails, explosions, lasers, guns). Same cause and cure as Stardust (2e9d97661): black,
unreflective and emissive in the G-buffer. The colour pass is now ForwardBase only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 11:18:37 +01:00
soul
36d9d94e81 fly: unbind the ship you leave, so its death is no longer yours
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 10:29:13 +01:00
soul
26daac398a Console command "fly <hull>": a fitted ship beside the current one, and you in it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 10:20:44 +01:00
soul
f3927e24db Pin a docked ship to its parent before the inactive return; no flight while docked
Docking deactivates the ship, and Entity.Update returned before the parent pin, so a docked ship
kept integrating its own velocity and gravity (Ship.Update) for as long as it sat in the bay, and
undocked wherever it had fallen to at whatever speed it had reached. Drag used to bleed that
off; with no drag below top speed it flung the ship into the void.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 10:18:20 +01:00
soul
7a3c0c8fb8 Aim at infinity along the view's pitch; delete the terrain intersection
The horizon fault was the level XZ projection, not the distance (operator, 2026-10-29). The untargeted look point is 10000 along the aim again, so the camera no longer jumps where the view ray crosses a terrain discontinuity; group aim points and crosshairs lift each planar firing bearing by the aim's pitch. Reverts the surface march of aba02182b and the field-height crosshair of a9bbea509.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 08:45:37 +01:00
soul
ffdba39ac2 ThrustScale scales a thruster's push only; turning keeps authored strength
The 10x nerf reached yaw through Torque * thrust and made turning painfully slow (operator, 2026-10-29). Column and Execute apply the scale to the push; Thrust and NominalThrust read the authored stat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 08:39:18 +01:00
soul
845f6c3aad No drag below top speed: ships coast and orbits no longer decay; drag acts only on the excess over the hull's top speed
Carries out aetheria-gravity:ruling:no-drag-below-top-speed (2026-10-14), which the code never implemented (operator, 2026-10-29). The VelocityLimit clamp still caps speed; the soft cap is the ruling's remaining half.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 08:33:26 +01:00
soul
e0c1daa4ec Thrust is a tenth of authored, the ratio gravity fell by; ThrustScale and DragScale are global flight tuning in GameplaySettings
Most ships cannot climb straight out of a well and use orbits; an overspecced hull like the Longinus still can (operator direction, 2026-10-29). DragScale defaults to 1, unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 08:30:48 +01:00
soul
c6f7e0c8a4 Player turn input is banded by thruster: each band of equal yaw-to-translation ratio fills an equal share of the turn axis
The allocator already serves a turn by the best-ratio thrusters first; the axis was a fraction of total yaw, so a hull whose best-ratio pair holds little of it (the Longinus's front pair) saturated them in the first tenth of input and lurched on its drives. Ship.BandedTurn maps the player's axis so each band joins over an equal share of travel; full input is still full yaw. AI and the allocator are unchanged (operator request, 2026-10-29).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 08:22:04 +01:00
soul
633ae3353a Keyboard turning ramps down on release at the same rate it ramps up, so tapping at a rate holds a gentle turn; reversal still cuts to zero
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 08:09:29 +01:00
soul
5e2a9efc8a Keyboard turning ramps to full over KeyboardTurnRampTime (0.4 s) so a tap feathers the turn; analog turn passes straight through
Release or reversal starts the ramp again from zero (operator request, 2026-10-29: the Longinus lurches).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 08:07:30 +01:00
soul
562ba5c8eb Default weapon-group binding runs once per ship, on its first use; after that the action bar is the player's
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 08:01:29 +01:00
soul
c4911a4c96 Action bar binds weapon groups by default: mouse left/right/middle fire groups 1-3, number key N fires group N
Only slots the input settings define, only groups with weapons, and never over gear or consumables the player placed. Runs on binding to a ship and after a save's bindings restore. Replaces a lazy Zip that never ran (operator request, 2026-10-29).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 07:57:42 +01:00
soul
2e9d97661c Stardust draws into the G-buffer so the nebula fog attenuates it by depth
At BeforeImageEffectsOpaque the dust was drawn after the depth texture the nebula march reads, so the fog treated dust pixels as far background and wiped them out (operator report, 2026-10-29). A Deferred pass writes black, unreflective, emissive G-buffer texels and depth at AfterGBuffer. FlowFieldSceneVerify still expects the old event and needs updating.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 07:47:20 +01:00
soul
aba02182b7 Untargeted aim lands where the view ray meets the field, not 10000 out at the horizon; weapon-group aim points ride the field
With no target the look point sat 10000 along the camera direction, so crosshairs and shot visuals aimed at the horizon (operator report, 2026-10-29).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 07:44:56 +01:00
soul
a9bbea5091 Weapon crosshairs ride the gravity field: the aim point takes its own field height, not the ship's level plane
On a slope the level projection floated over downhill aim and sank into uphill aim (operator report, 2026-10-29).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 07:33:28 +01:00
soul
293e3a33fd Research: hyperbolic in-between space (H2 rendering, motion and precision, curved Kepler, HyperRogue scaffolding, getting lost, Aetheria's Euclidean gravity body)
Serves the operator's musing on hyperbolic inter-zone travel, 2026-10-28.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:22:25 +01:00
soul
257a34939e Delete Vault/: a stale 2025 copy of the lore; the live lore is GameCult/AetheriaLore
The copy had drifted far behind (no Megiddo or Wavecrafters pages) and an agent
edited it as if it were canon on 2026-10-28.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 06:36:54 +01:00
soul
6e5be44613 Revert lore merge 28c076b1e: it edited the stale Vault copy in this repo; the live lore is GameCult/AetheriaLore
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 06:35:25 +01:00
soul
28c076b1e6 Merge lore: cognition-anchored aether, ARI spectrum, Megiddo doctrine (operator direction 2026-10-28)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 06:29:16 +01:00
soul
c4a2ce36be Lore: the aether is anchored by its residents' cognition; ARIs are a spectrum; Megiddo's doctrine
Aether gains what the field answers to (consensus, load-bearing belief, a self not
compute, unopposed small effects, density, attention). Necrotech gains the ARI
spectrum from factory stamps to bound espers. Megiddo gets a page: right about the
mechanism, wrong that there is a way back. Pseudospace ties flatness to consensus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 06:29:08 +01:00
soul
8fa1fc21b2 Merge one exit per region: after boss placement each boss zone keeps only its exit-path link into the far side; distances recomputed before faction homes; saves load the borders they stored (aetheria-living-world). Soul held s1 and s2; Unity batch compile clean on Starfire at 2822a583e.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 21:33:40 +01:00
soul
2822a583ea Pin CrossingLinks far-zone ordering and a boss zone at the start of the exit path in hand-built graphs (cut one-exit, finding pure-helper-survivors)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:24:02 +01:00
soul
97aa371872 Provinces.ExtraExits throws when a boss zone has no exit-path link to keep instead of silently keeping every link (cut one-exit, finding silent-keep-all-fallback)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:23:47 +01:00
soul
9fd0fe96e7 One-exit tests require every sector seed to build; a generator exception fails instead of skipping the seed (cut one-exit, finding trybuild-masks-generator-crash)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:23:32 +01:00
soul
0182570200 DistancesAreOfTheFinalGraph builds its own galaxies so the pruning's distance recompute is under its coverage (cut one-exit)
Stryker's per-test coverage credited the shared galaxy cache's first builder
alone with Galaxy.OneExitPerRegion's CalculateDistanceMatrix call, so deleting
the call survived. Fixture fix, no rule change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 18:50:45 +01:00
soul
84acc9e563 Galaxy.OneExitPerRegion: sector generation prunes each boss zone's extra exits, then recomputes distances (cut one-exit)
Ruling one-exit-per-region: one exit to defend per region. Called once in
PlaceFactionsMain after the boss loop and before home placement; BuildProvinces
and the pruning share ZoneGraph. Saves and the prelude never prune. Tests pin the
rule on sector seeds 1-60, the pure helpers on hand-built graphs, and save/load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:28:09 +01:00
soul
5df9960343 Provinces.ExtraExits: the crossing links to prune so a boss zone keeps the one on the exit path (cut one-exit)
Pure. Reads CrossingLinks; nothing calls it yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:28:08 +01:00
soul
4f93c0de68 Provinces.CrossingLinks: one definition of a boss zone's crossing link, Regions reads it (cut one-exit)
Moves Regions' inline exit-side loop into a public CrossingLinks so the
pruning that follows reads the same definition. Behaviour unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:25:19 +01:00
soul
bef6a90f58 Merge ore geography: provinces inside boss-bounded regions, region decks deal province types, field kinds and yield read the province (aetheria-living-world). Unity batch compile clean on Starfire at the merge; Soul held.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:11:49 +01:00
soul
674baa95ae Merge eureka/aetheria-living-world-economy-model: living-world Body map, economy section
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 12:16:36 +01:00
soul
3e98238cf0 Living-world map: the economy's catalog records, owners and checks after recipe role inputs
Owner, inputs, outputs, derived state, forbidden writers, shared paths and
verification layer for RecipeData, Recipes.RoleInputsOf/Fillers and
EconomyChecks, read at 30030af6e. Notes the two stores of a recipe's role
slots behind one reader, filler choice at production time as unowned, and
stale docs: the target prose is not on master and target r2's L-recipe
wording predates intermediate role inputs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 12:16:07 +01:00
soul
30030af6e4 Merge eureka/aetheria-living-world-recipe-role-inputs: recipes take role inputs any filler satisfies; offered designs demand their roles
Soul s1 (aetheria-living-world:verdict:cut-recipe-role-inputs.s1) holds all five promises; its two Medium findings are pre-existing test races, deferred to their follow-ups. Suite 2867/0 at e93f53166; shipped report 146 faults (consumer demand: Ballistic Powder, Nitric Acid, Nitrocellulose, Potassium Nitrate). Unity compile not yet run; it rides with the ore-geography compile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 12:11:20 +01:00
soul
e93f531669 Economy tests: a null list beside RoleInputs does not drop them
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 11:44:30 +01:00
soul
1efd8343da Economy tests: pin the survivors of the diff (null RoleInputs keeps the rest, key-ordered recipe-less designs, unoffered recipe designs name roles, mixed takes line)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 11:30:14 +01:00
soul
ccd9792d2f Economy tests: role inputs persist, are read by every rule as any filler, offered recipe-less designs demand their roles
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:56:21 +01:00
soul
3fb5e0e0e6 Economy: RecipeData.RoleInputs, Recipes.RoleInputsOf as the one reader of role slots, checks read a role input as any filler and offered recipe-less designs as demanding their roles
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:56:01 +01:00
soul
1ed3579025 Merge eureka/aetheria-living-world-economy-checks: consumer demand from shared uses, a weapon's draw is a sink, upkeep walked, economy command tail
Soul s1 (aetheria-living-world:verdict:cut-economy-checks.s1) holds; s2 holds the fixes except one Low (dry-run test cannot see apply), filed as a follow-up. Suite 2848/0 at dc4838bd9; shipped report 144 faults, consumer demand nitric-acid only. Unity compile not run (no Unity-side change).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:29:05 +01:00
soul
dc4838bd9e Economy checks: upkeep walk test gets a control case and a baseline that is actually shared
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:07:40 +01:00
soul
3c3ecd40da AetherDb economy: pin Run's report and author dry-run branches on the shipped catalog
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:06:17 +01:00
soul
da067a34a2 Economy checks: the consumer walk follows upkeep edges; pin upkeep into a consumer good and a role filler
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:06:15 +01:00
soul
6ba77aafe7 OreGeographyTests: restore the file the previous commit replaced with a stale copy; the three sector-galaxy tests and their helpers on top of the guard test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:00:42 +01:00
soul
154b71160d OreGeographyTests: a multi-region sector galaxy's provinces survive save and continue and re-derive from an older save
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 09:59:24 +01:00
soul
4b7e5476c0 FieldKinds.Assign: a kind needs a positive kind weight and a positive province factor; two negatives no longer draw
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 09:59:15 +01:00
soul
8e6fd43d23 Economy checks: ConsumerDemand tests recipes, not keys; pin upkeep reached through a chain
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 09:32:13 +01:00
soul
8d23a378d6 Economy checks: gearRoles reads roles of non-commodity recipes; pin use order and batch list
Mutation testing found IsRoleRecipe redundant in the gear-role set (RolesOf is empty for a
design without roles) and the message's use order, join and the unknown-batch count unpinned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 09:19:00 +01:00
soul
1db250fb10 OreGeographyTests: the duplicate-listing fixture's yield-only kind is never drawn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 08:55:07 +01:00
soul
8774bd8a97 OreGeographyTests: pin seed order, tie-breaks, duplicate listings, deal key order, kept and saved first-load kind
Kills the survivors of the first Stryker run on FieldKinds and Provinces that were not equivalent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 08:50:01 +01:00
soul
c81a83ef2c OreGeographyTests: regions, partition, deal, yields, belts, save
Not yet run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 06:43:00 +01:00
soul
4848c5332f Ore geography: yields, provinces cut inside boss-zone regions, types dealt per region
FieldYield and FieldKindData.Yields (key 3), ProvinceData, FieldKinds.Pick/Assign/Ensure/Yield
with a province type, Provinces.Regions/Partition/Deal, Galaxy.Provinces built once by the
generation and save constructors, SavedGame key 15 and SavedZone key 6, ProvinceZones setting.
Not yet built or run; tests follow in the next commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 06:42:58 +01:00
soul
d92d77483f Retire BodyData.Resources key 4 and the commented ResourceValue
No reader or writer remains; FieldKinds.Yield (this cut) is the owner of a belt's yield.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 06:34:12 +01:00
soul
b375cfc8ec Research: ore geology and regional resources prior art (Dwarf Fortress, EVE, Elite/X4/Starsector, real asteroid taxonomy), for ruling ores-are-regional
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 05:11:18 +01:00
soul
26bea2cb14 AetherDb economy: unknown batch names are not echoed; held-cycle rise is relative
Run takes the output writer like Report and Author do. The unknown-batch refusal says how
many names matched no batch and lists the compiled batches. Introduced compares a held
cycle's radius relatively with RiseNoise = 1e-12, replacing the bare absolute 1e-9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 04:38:08 +01:00
soul
7df0a0943f Economy checks: consumer demand derived from shared uses; a weapon's draw is a sink
ConsumerDemand drops the two-or-three-step window (rulings consumer-demand-shared-only,
consumer-demand-skips-final-assemblies): a processed good owes a consumer use when it is
shared, derived from the recipe graph each check. A weapon's draw of a round is one use
(ruling draw-is-one-use). SourceSink counts a drawn commodity as sunk. Drawn is the one
reader of offered designs' WeaponData.AmmoType. Tests are unrun at this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 04:38:05 +01:00
soul
c6a49e5568 Merge eureka/aetheria-living-world-economy-content-b1: economy batch 1 (Zhestokost munitions) applied; one owner of drawable station hulls
Operator approved the batch (aetheria-living-world:ruling:batch-1-munitions-approved). Soul s2 holds; suite 2836/0 at the merged head; catalog LFS oid 68b37cb5 on GitHub and the Yggdrasil cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 19:09:35 +02:00
soul
dd1031ce6a StationKindTests: a maker-less or Price 0 station hull is unreachable by the draw; the stand-in check is exercised on Zhestokost
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 18:16:48 +02:00
soul
b481363b9b StationKinds.HullKindFor takes a stand-in's kind only when some maker offers it
The any-maker fallback in Choose could seat a faction on a kind it has a dead stand-in for; the hull draw then looked for the dead stand-in's kind and returned
null. A stand-in no maker offers is no longer followed, so a kind Choose places is a kind the hull draw can fill. FieldableKindsOf reads the same two helpers.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 18:04:24 +02:00
soul
e3824360aa StationKindTests: stand-ins judged from the catalog, zones test against an oracle, the partial and fallback-spread cases pinned
Deletes the name-keyed AwaitingFirstModel exemption: AssertStandInsLive derives liveness from the sold hulls (live exactly when some maker sells the
target kind; otherwise the kind leaves the pool and the any-maker fallback is observed in the log). GeneratedZonesPlaceStationsByKind checks against
a records-level oracle and asserts the fallback case. New: a kind is kept exactly when a hull of it can be drawn (Price 0, no maker), a faction with
one fieldable and one unfieldable kind places only the first, the fallback spreads over every offered kind.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 18:02:48 +02:00
soul
e403905e12 LoadoutGenerator.IsDrawable is the one definition of 'a maker offers this design'; StationKinds keeps a kind by it
AvailableProducts (Price > 0, a set Manufacturer, offered) and StationKinds.OfferedKinds (offered only) disagreed, so Choose could keep a kind
whose hull draw returns null. Both now ask IsDrawable; OfferedKinds and OfferedStationHulls read the same drawable station products.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 18:02:47 +02:00
soul
78b176a0ff Merge remote-tracking branch 'origin/master' into eureka/aetheria-living-world-economy-content-b1 2026-10-23 17:58:47 +02:00
soul
d5c0063b7e CommodityRecordsTests: the compound commodity count follows the batch (51 to 63)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 17:29:50 +02:00
soul
b1b5031e4b Economy batch 1 (munitions): apply Munitions to the shipped catalog; re-pin what the catalog stales
economy author Munitions apply: 49 added, 4 changed. The pins that name the shipped catalog's contents follow it: the kind list gains
munitions-works, the simple commodity count, the retirement check counts only its own stand-in, the stand-in liveness check exempts
a faction awaiting its first station model (Zhestokost), and generated stations draw from the fieldable kinds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 17:23:26 +02:00
soul
fce8c66284 StationKinds: FieldableKindsOf is the pool Choose draws from
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 17:23:24 +02:00
soul
fe7ea72c9b StationKinds.Choose: a kind the faction places but no maker can field leaves its pool and falls back to any maker
A stand-in whose hull kind no offered station hull carries (Zhestokost's depot, whose model has not landed) used to leave the
tutorial entrance with no station. The existing no-kinds fallback now covers it: one rule, no faction special case.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 17:20:04 +02:00
soul
37677c4893 Research: recipe-graph structure in factory games (shared vs single-use intermediates)
Prior art for aetheria-living-world❓consumer-demand-gear-specific-intermediates: Factorio 1.1 and Space Age, Dyson Sphere Program, Satisfactory, No Man's Sky.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:05:31 +02:00
soul
722e618cac Merge eureka/aetheria-arena-sim-rate h7: Combat releases every held weapon it no longer commands, beams and locks included
Soul pass 5 (aetheria-arena:verdict:cut-sim-rate.s5) holds all five promises. Unity compile and feel unproven.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 16:00:00 +02:00
soul
9b56bf0136 Digest pin comment names both causes of the h6 repin
The candidate-predicate change and the release sweep each move the digest alone (b559789a, d9493bbe); releasing beams and locks
moves nothing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 15:31:43 +02:00
soul
780e68be71 Combat lets go of every held weapon it no longer commands: beams and locks, not only AutoWeapons; its end releases a same-step pull
One sweep over what is held (Firing, or pulled this step): released when uncommanded, and on exit regardless of
command. Tests through Minion and Zone.Update for a beam, direct for a lock, the not-the-pick group, the cooling
candidate clause by gun kind, the same-step pull on exit, and PilotCommand.Commands.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 15:25:11 +02:00
soul
8ca5425fcc Merge eureka/aetheria-arena-sim-rate h6: the AI releases a held automatic gun it no longer commands
Soul pass 4 (aetheria-arena:verdict:cut-sim-rate.s4) holds all three promises; suite 2822/2822 on Yggdrasil. Unity compile and feel unproven.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 15:21:21 +02:00
soul
163bdf9b79 Test: Combat releases a held AutoWeapon whose target moves out of reach
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 13:46:37 +02:00
soul
3715c9edf0 Repin the two-AI digest once: Combat releases a held AutoWeapon it no longer commands
Measured 85e85c46 on linux-x64 (55ecd858 before), on two full runs.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 13:39:43 +02:00
soul
d4bec37c6a Combat's release sweep leaves a weapon it already commanded alone: PilotCommand.Commands
One command per weapon per step: the sweep released a gun the group loop had already released.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 13:34:18 +02:00
soul
d2c293b799 Test: the semi-auto floor is the 0.1 s constant, not a multiple of the step
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 13:27:54 +02:00
soul
65b0e00cd6 Test: a behaviour created from a consumable AutoWeaponData honours Automatic
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 13:27:53 +02:00
soul
848ff83cd7 Combat releases a held AutoWeapon it no longer pulls: no target, no track, other group, exit
The weapon holds a down trigger; Combat's early returns issued no release and had no exit hook, so a held
Slug Rifle, Autocannon or Flak Gun fired on at nothing. Combat is the one owner of the AI's trigger level:
every held AutoWeapon it does not pull this step is released. A held gun stays a group candidate between
rounds so cooling does not drop it. Tests run through Minion/Combat.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 13:27:52 +02:00
soul
5177154c17 Economy batch 1: Zhestokost munitions chain (unapplied, for operator review)
Dry run on the composed catalog: zero shape faults introduced; 49 records
added, 4 changed. Review page published separately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 12:45:20 +02:00
soul
bd7cf4b5d4 Merge eureka/aetheria-living-world-economy-authoring-command: AetherDb economy author and report; the batch gate refuses every shape fault a batch introduces
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 07:23:04 +02:00
soul
dd7f40b559 Merge origin/master into economy authoring command
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 07:16:44 +02:00
soul
6befccce74 Economy command: L-productive is one fault per unproductive component; a held-open catalog is a named refusal at the first read too
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 07:15:59 +02:00
soul
ae3e4a7c58 Economy command tests: a held cycle whose radius falls still applies
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:48:22 +02:00
soul
fe7c5226cc Economy command tests: a cycle that moves to a lower radius is still a changed membership
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:43:56 +02:00
soul
2da2afd468 Economy command: refuse a read-only catalog before the trial; the trial copy is writable
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:38:50 +02:00
soul
f403485dc0 Economy command tests: the summary line counts removals
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:36:54 +02:00
soul
e5099c0363 Economy command: escape the refusal line breaks
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:32:52 +02:00
soul
ade0b0fbf3 Merge eureka/aetheria-arena-sim-rate: the game ticks at 30 Hz; one owner of the rate; timers carry overshoot; guns fire every round their cooldown allows; plain guns are semi-auto
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 06:31:10 +02:00
soul
352a94fe21 Economy command: faults keyed by rule, record and message; L-productive by cycle members with a rising radius refused; removals listed, counted and applied; pending only for the recipe-less; swap failures named, read-only refused, file mode kept
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:28:20 +02:00
soul
a90ec3d450 Economy command tests: fault identity by rule/record/message, cycle members and radius, removals listed and counted, pending only for the recipe-less, swap failure and file mode (red)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:27:54 +02:00
soul
9036d39f30 Two-AI digest repinned once for arrival-order application (commits of a step precede its applies)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:22:01 +02:00
soul
62a641bf15 Catalog: Slug Rifle and Long Slug Rifle are AutoWeapons (auto-guns apply on master's catalog)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:12:57 +02:00
soul
1f7b971966 Fire mode is one property of a weapon's data (Automatic): creation and the semi-auto floor read it, the floor holds guided, mine, launcher and charged kinds and consumables; a step's rounds apply in arrival order, ties by shot id
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:12:55 +02:00
soul
e92c2140b4 SemiAutoCatalogTests: the shipped catalog holds no plain gun, only guns
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:10:07 +02:00
soul
d3da985290 Fire mode is the design: Slug Rifle and Long Slug Rifle are AutoWeapons; tap and hold tests for both kinds at 15, 30 and 60 Hz; the semi-auto cooldown check
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:10:06 +02:00
soul
5367c899a6 auto-guns: copy the plain gun's fields
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:53 +02:00
soul
35ef321ff3 A plain InstantWeapon is semi-automatic; AutoWeapon repeats while held; the shortest semi-auto cooldown is a named constant with a catalog check; auto-guns command
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:52 +02:00
soul
0d8457bc34 SignatureTests: only a gun is tapped; a beam burns while its trigger is held
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:46 +02:00
soul
1aceaa576d Two-AI digest repinned once for resolving a step's rounds in fire-time order (the trigger change alone moves nothing)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:45 +02:00
soul
43042177d6 Tests that press a trigger once release it: a held trigger keeps firing now
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:44 +02:00
soul
46912a20c8 A trigger is the weapon's own state: a tap fires one burst and a held trigger fires its cooldown's rounds at any tick; a step resolves its rounds in fire-time order; the idle-clamp test really idles
InstantWeapon repeats while its trigger is down (Activate to Deactivate), not when it was pulled this step; AutoWeapon's override of that is gone. FireControl keeps the shot queue in fire-time order (ties by shot id) and Step walks it oldest first.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:43 +02:00
soul
37a7bb76f7 Scenario tests step the arena at SimClock.GameStep, the rate the game runs
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:42 +02:00
soul
99f5820565 SimRateTests: a zero cooldown ends its step 2026-10-23 06:09:41 +02:00
soul
2b09e87733 SimRateTests: rounds leave from the shooter's pose at their moment; a zero cooldown fires at the shortest cycle 2026-10-23 06:09:40 +02:00
soul
aeac4e2958 SimRateEquivalence: survivor hull and first kill are held to the 95% interval of their difference, as the win rate is 2026-10-23 06:09:39 +02:00
soul
b679a38edc Two-AI digest repinned once for rounds-per-step; the step-cost test measures a round's birth with the shot queue grown 2026-10-23 06:09:36 +02:00
soul
7c9a970c81 Economy command: drop the untested second fingerprint check before the swap
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:35 +02:00
soul
c1f9c05c48 Entity.GetBehavior walks Equipment by index: the per-round targeting-system reads no longer allocate an enumerator 2026-10-23 06:09:35 +02:00
soul
b1380204a3 SimRateTests: split the step cost into a round's birth and its resolution 2026-10-23 06:09:34 +02:00
soul
d038ee393a SimRateTests: the fixture gun's round costs what the fixture reactor can pay at 0.01 s 2026-10-23 06:09:32 +02:00
soul
a1519c9b2b diag (temporary) 2026-10-23 06:09:31 +02:00
soul
c00be8b35c SimRateTests: probe the fast-gun capacitor on failure 2026-10-23 06:09:30 +02:00
soul
93f69ecb80 Rounds per step: a held weapon asks the bus for its step's whole inflow; mine-laying test cooldown stays inside one activation's two steps
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:29 +02:00
soul
1870e23bfd Rounds per step: a gun's capacitor holds a step's inflow so every round of the step is paid; the mine-laying test's cooldown no longer pins the one-lay-per-step cap
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:28 +02:00
soul
04128e2f81 InstantWeapon: a pull is held through its step 2026-10-23 06:09:26 +02:00
soul
3b466f8c9b SimRateTests: step-cost test compiles 2026-10-23 06:09:25 +02:00
soul
de9e1ba2f9 Projectile: a round is told of its shot by its manager's one subscription, and first seen where its barrel was when the sim fired it inside the step
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:24 +02:00
soul
95b7aeb7a2 Rounds per step: a held gun fires every round its cooldown allows in a step, each born at its own moment inside it; the shortest-timer pin is gone
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:22 +02:00
soul
3d4508d196 ChargedWeapon: a failed charge's cooldown restarts through InstantWeapon.StartCountdown and carries its overshoot
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:21 +02:00
soul
5adaef4697 Two-AI digest repinned once at the game step (step-exact rules, 30 Hz); shortest-timer test pins the four guns authored below two steps
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:20 +02:00
soul
ed5dd92283 Game tick lowered to 30 Hz (ruling tick-rate-stays-30); digest repin follows
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:19 +02:00
soul
48ba0a6204 SimRateTests: idle banking, magazine cadence, glowing passive target; equivalence and shortest-timer tests; ping-arm trace repinned for the exact decay
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:18 +02:00
soul
549437536a Cooldowns shorter than a step end in it; tests that pinned the old tick-quantised cadence and Euler decay follow the exact rules
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:17 +02:00
soul
b9e0e5b5eb SimRateTests: whole-step counts by MathF.Round
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:15 +02:00
soul
f0362025d9 Step-exact rules: timers keep their overshoot, passive sensing decays exactly (group B, 60 Hz, digest not repinned)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:14 +02:00
soul
e2f0fc9dc4 SimClock owns the game tick: TickRate, GameStep, ForGame(); the 60 default and ActionGameManager's literal step are gone
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:12 +02:00
soul
a19d0eb73b Arena: sim-rate map and target section 7 (sim rate cut, step part of A-pure)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:09:11 +02:00
soul
7f3b06222d Economy command: run the batches once on the trial, check that copy, replace Aetheria.cc with it atomically; refuse introduced shape faults by rule and record
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:03:44 +02:00
soul
dc69f5dc5b Economy command: run the batches once on the trial, check that copy, replace Aetheria.cc with it atomically; refuse introduced shape faults by rule and record
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:03:29 +02:00
soul
3092ce81cd Economy checks: null outside-market Rows or row and a null Fills entry are L-recipe faults; a missing recipe is marked pending
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:02:06 +02:00
soul
98aef507f1 Economy command tests: a later batch closing a cycle, in-place edit, one run, real-catalog write, throwing batch, null market rows and Fills (red)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 06:01:59 +02:00
soul
0394e33ed8 Economy command tests: pin the printed report, the written set and the trial cleanup (Stryker survivors)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 05:40:51 +02:00
soul
b31595add2 Economy command tests: an edit naming two items is refused (kills the ambiguity mutant)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 05:36:28 +02:00
soul
7367f21c8a AetherDb economy: report and author, dry-run gated by the catalog checks
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 05:28:42 +02:00
soul
b4510d5737 EconomyChecks: the key check reads the record key, not the cache's reference to a sound copy
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 05:27:50 +02:00
soul
94a14b8dec Economy command: contract and tests, skeleton throws (red)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 05:26:47 +02:00
soul
dccac5047d EconomyChecks: a recipe with a null list or entry is an L-recipe fault; every other rule reads it as empty
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 05:25:24 +02:00
soul
88a36fff4e Economy null guards: a recipe with a null list is an L-recipe fault, not a throw (red)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 05:25:13 +02:00
soul
a487f6c0a4 Merge eureka/aetheria-living-world-recipe-schema-and-checks: recipe and outside-market records; catalog checks for sources and sinks, productivity, recipes, lore and consumer demand
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 05:21:37 +02:00
soul
e2e01b2266 Merge eureka/aetheria-release-nakovalnya-boss-loadout: Zhestokost's demo boss is the Nakovalnya Boss loadout
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 05:15:41 +02:00
soul
084c77fb12 Boss loadout tests: shipped catalog now holds the demo boss; tests that wrote their own loadouts count only those
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 04:38:27 +02:00
soul
79caa8b32d Zhestokost boss: the Nakovalnya Boss Loadout, committed through loadout-commit --boss-of; RunGoal tests reach it
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 04:32:46 +02:00
soul
5584c0d7c8 Merge eureka/aetheria-release-loadout-commit: AetherDb loadout-commit with --boss-of; Loadouts.Commit accepts package designs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	tools/AetherDb/Program.cs
2026-10-23 04:27:12 +02:00
soul
6b89019c02 Economy checks: an undefined radius refuses; factor refusal says why 2026-10-23 04:24:32 +02:00
soul
c6c8b4465b Economy checks: pins from mutation testing; drop two redundant guards 2026-10-23 04:14:42 +02:00
soul
cdee1a27f8 LoadoutTests: a commit over a loadout written after the read writes nothing (kills the dropped-Expect mutant)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 03:43:55 +02:00
soul
3506d48b1f Spectral radius: converge on the vector, not on two equal maxima 2026-10-23 02:50:57 +02:00
soul
91f9b5553c Economy fixture: keyed writes through UpsertAsync 2026-10-23 02:46:19 +02:00
soul
263fa02a73 EconomyFixture is public for the theory data 2026-10-23 02:45:24 +02:00
soul
bacb56c613 Recipe schema and catalog checks: RecipeData, OutsideMarketData, role counts, EconomyChecks over fixture catalogs
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 02:43:35 +02:00
soul
d233194cb2 Merge eureka/aetheria-living-world-station-kind-datum: station kinds are one catalog list; stations and fields are placed by seed; bad station packages are excluded by name
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 02:14:33 +02:00
soul
0e50c1d101 RunGoalTests: a boss Loadout on a removed package's hull still boots and fails closed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 01:48:34 +02:00
soul
37de26291d AetherDb loadout-commit, with --boss-of, validated against the composed package stack
Ported and narrowed from 90f977bf (evidence only; no boss-hull code). Names resolve
exactly against ShipModCatalog.ResolveCatalog over GameData/Aetheria.cc and GameData/Mods,
composed into a scratch file, the catalog the game boots; validation is one
Loadouts.Materialize on a throwaway ItemManager; the commit goes into Aetheria.cc only,
through Loadouts.Commit, with --boss-of in the same batch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 01:48:33 +02:00
soul
48606e4253 Merge eureka/aetheria-release-scenario-picker: New Game is a two-pane scenario picker
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 01:44:07 +02:00
soul
f920cd7052 Revert the TextMesh Pro fallback font asset Unity re-serialised
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:43:04 +02:00
soul
2fb8e88fcf ScenarioPicker: find the list's ScrollRect while the panel is still inactive (play mode builds it before showing it); verify builds the real list inactive
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:43:03 +02:00
soul
9961a0df6d ScenarioPicker.Reveal: content moves against the row (the sign the first fix kept inverted)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:43:01 +02:00
soul
ca24a32028 ScenarioPicker: Reveal moves the content the right way; Launch is one-shot; the scrollbar takes no focus; the picker holds focus itself so the scene's background-click deselect is back on
ScenarioPickerVerify builds the real scenario list after a layout pass and arrows to every row, up and down, requiring each wholly visible; Launch by click, second click and Submit runs once.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:42:59 +02:00
soul
169679dbc4 ScenarioPicker: viewport span from the rect's own corners so FitViewport and Reveal see the real mask; Launch chrome stretched to its 220-wide button
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:42:58 +02:00
soul
585942259d Scenario picker attempt 2: body heading instead of a second giant title, small left group labels, chrome Launch in the accent, tight Seed/Ambient block, capped brief, visible scrollbar track
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:42:57 +02:00
soul
09de7e5eb2 ScenarioPicker: selected row in the menu accent, title is group over name, panes docked 128 apart; verify and capture run green in Unity; metas
The prefab row tint is nearly invisible on the row's graphic, the long scenario name overflowed a 480-wide title, and the list scrolls to keep the selection in view.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:42:56 +02:00
soul
79a3a62fc1 ScenarioPicker verify and capture: selection and key rules through real UI events, and a 1920x1080 render of the screen
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:42:55 +02:00
soul
3bfac25027 New Game is a two-pane scenario picker: names by group, selected brief/seed/ambient, one Launch, arrows and Enter
MainMenu.AddScenarios and the per-scenario launching buttons are deleted; the seven Clear+Title pairs collapse into Next. The main menu EventSystem no longer deselects on background click so the arrows keep working.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:42:54 +02:00
soul
80283d18a9 Loadouts.Commit takes bossOf: the Loadout and Faction.BossLoadout land in one batch
With bossOf set, the same conditional batch Expects the faction unchanged and Upserts it
with BossLoadout = KeyOf(Name), so both land or neither does. The batch lives in an
internal overload over a caller's cache; tests pin that a faction changed on disk after
the read refuses both, and that a missing boss faction writes nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 01:42:50 +02:00
soul
7f1337167d Loadouts.Commit stops refusing package designs (ruling catalog-is-a-package-stack)
The catalog is a stack of packages, the shipped content included, so a Loadout may
name any design the stack holds. The mod-design refusal (from 54917bb3, backed by no
ruling) is deleted, and its test now pins that a package design commits under its key.
The IsModKey comment no longer claims the shipped catalog never references a mod.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 01:42:49 +02:00
soul
bd628de3bf Merge eureka/aetheria-release-cultlib-2-0-adopt: Aetheria on CultLib 2.0.1 and CultMath 0.5.0; LockWeaponData is a BehaviorData arm; 1.0.x saves open
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 01:41:50 +02:00
soul
02d60691dc Same-seed belt test: fixed kind keys through a batch
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:37:40 +02:00
soul
b1ca2ead90 Same-seed tests: authored kind keys are fixed; the station sample spans eight zones
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:32:34 +02:00
soul
d9bc465355 Packages: lock regenerated by Unity at cultlib-unity-v2.0.1 (b8fc5eb0)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 01:26:14 +02:00
soul
4918fcd993 AetherDb is disposable: it owns the cache it opened
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:26:00 +02:00
soul
b878734158 station-kinds hull: the dry-run and applied lines are one line each
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:23:59 +02:00
soul
d2f4b2c347 A bad package station kind excludes the package; AetherDb opens a catalog with a kindless station hull, names it, and station-kinds hull sets it; the raw opener is gone
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:21:04 +02:00
soul
37a8110907 Station and belt kinds are seeded: the pick hashes the zone seed and the place in generation order, not a minted key
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:21:02 +02:00
soul
17e2da0092 AetherDb tests: a kindless station hull does not lock the tool out; station-kinds sets a hull's kind (red)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:18:21 +02:00
soul
d55ac30905 Station kinds tests: a bad package kind is excluded not fatal, same seed gives the same station and belt kinds, the stand-in check can fail (red)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:18:20 +02:00
soul
ea3e983aa2 StationKindsCatalog: OpenCatalog takes a path; tests pin Zenith's kind, the ids and the command's report 2026-10-23 01:16:22 +02:00
soul
0a468f24a8 Station kinds: tests pin the log text, a null faction, and one-hop stand-ins; kind strings default to null 2026-10-23 01:16:21 +02:00
soul
ac4b771f5e StationKindTests: worlds carry the run store zone generation writes orbits to 2026-10-23 01:16:20 +02:00
soul
353ca96b5e StationKindTests: the any-maker log is the hull draw's, not Choose's 2026-10-23 01:16:19 +02:00
soul
a7364ed13e GameData: 17 station kinds and Zenith as a trade-hub (station-kinds apply)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:16:17 +02:00
soul
fba7bd1b47 Station kinds: StationKindData catalog list, HullData.Kind, placed station Kind; GenerateStationLoadout takes a kind (the kindless draw is deleted)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:16:17 +02:00
soul
7886f0a231 ShipAuthoringStore.Load names the file when its store format is newer than this runtime reads
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 23:35:14 +02:00
soul
e045af344f Pins: cultlib-unity-v2.0.1 at b8fc5eb0 (1.0.x saves with ref lists open as compatible drift)
packages-lock.json follows from the Unity resolve. caching-unity stays at v2.0.0; cultmath at 7a8933b0.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 23:35:12 +02:00
soul
da5ba653cb Tests (red at 42dce361): a CultLib 1.0.60 run.cc opens; Load names a ship file of a newer store format
tests/vectors/cultlib-1.0.60/run.cc is the run store Soul wrote with the base build (cl2-old-run-save-refused).
Both new tests fail at the 2.0.0 pin; the first needs 2.0.1, the second needs ShipAuthoringStore.Load.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 23:35:11 +02:00
soul
f448957f3a Brush: name AnimatedSimplexBrush and RadialWaveBrush as arms; pull-failure test asserts the record key
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 23:35:09 +02:00
soul
e518a0b3d1 packages-lock: regenerated by Unity at the 2.0.0 / 0.5.0 tags
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 23:35:08 +02:00
soul
fbf88e85c5 ship_cc.read owns its unreadable-path refusal, naming no path
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 23:35:07 +02:00
soul
fb8f2ea2fb ShipAuthoring: Load follows the pull's InvalidDataException, RefuseLegacyEmbeddedHull the nil-stamp NotSupportedException
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 23:35:05 +02:00
soul
bd0293d683 BehaviorData: LockWeaponData is arm 52; union-closure and null-gear tests
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 23:35:04 +02:00
soul
1dce24a226 Pin CultLib 2.0.0 (42dce361) and CultMath 0.5.0 (7a8933b0): manifest tags and build revisions
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 23:35:03 +02:00
soul
8b0b50382a WeaponData: drop the dead [Union] list; BehaviorData's union owns behavior arms
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 23:35:01 +02:00
soul
00e60344ab Merge eureka/aetheria-release-cargo-store-owns-quantity: the cargo bay owns stack quantity; restore splits oversize stacks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 23:10:00 +02:00
soul
c986adcf20 Cargo restore tests: only the oversize stack's own bay is full, so the spill has another bay to go to
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 22:54:52 +02:00
soul
9958fe55ab Cargo purchase: a bay stack is one lot, so the lot loop is deleted; stock above MaxStack from outside a bay is refused up front
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 22:50:24 +02:00
soul
b027b36ab4 Cargo restore: a saved stack above MaxStack splits into MaxStack stacks, the same bay first, then the others; only what fits nowhere is dropped, logged
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 22:50:23 +02:00
soul
d66b2a2c3c Cargo restore tests: an oversize saved stack splits into MaxStack stacks and drops only what fits nowhere (red)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 22:49:22 +02:00
soul
fd50f02f1b Cargo purchase tests: a purchase of no units is refused, not bought
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 22:29:24 +02:00
soul
f7ba043b13 Cargo restore test: share the fixture's provenance ledger
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 22:21:08 +02:00
soul
fa5820f516 Cargo give: the bay no longer holds a stack below one, so the give's positive-stack filter and its zero-stack test go
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 22:15:41 +02:00
soul
ddae55759d Cargo bay owns stack quantity: TryStore refuses below 1 and above MaxStack, TryBuyLots owns the stock limit, restore logs dropped stacks
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 22:01:42 +02:00
soul
ffe43b41e8 Cargo store tests: the empty-stock purchase test reaches the charge (red)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 22:01:39 +02:00
soul
708fbeee59 Cargo store tests: the bay owns stack quantity, purchases charge units moved, restore drops bad stacks (red)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 21:59:06 +02:00
soul
689dbfa9d0 Merge eureka/aetheria-release-cargo-quantity-guards: cargo moves refuse quantities below 1; purchases charge the lot delivered; a give ends when its giver leaves
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 21:55:02 +02:00
soul
4d462c771c Give: Zone.Remove ends the leaving giver's give GiverGone
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 21:10:09 +02:00
soul
f0d7c12cbd Trade: the purchase loop moves to StationServices.TryBuyLots, charging each lot it delivers; the Buy Quantity field clamps to at least one
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 21:10:07 +02:00
soul
f9a09e0d0a Cargo: TryTransferItem and Remove refuse a quantity below one and change nothing
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 21:10:04 +02:00
soul
66671cf036 Cargo tests: quantities below one, give routing and ends, purchase lots (red); soul-tv probes P4-P6, P9 adopted
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 21:09:30 +02:00
soul
7e2fff1ff1 Merge eureka/aetheria-release-transfer-verb: give-only ship-to-ship transfer; partial cargo moves conserve units
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 21:03:37 +02:00
soul
c37fbdb3b9 Cargo transfer tests: drop the two-bay receiver test; the fixture hulls take one bay
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 20:11:38 +02:00
soul
b43b238d6a Merge readme-front-door: README opens with value, problem, entry points, live versus planned 2026-10-22 20:10:22 +02:00
soul
1691f47cc6 README: front door with simulation entry points, demo in progress, directions and package hulls
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 20:09:53 +02:00
soul
00d34e5ab5 Cargo transfer tests: the second receiver bay is a one-cell bin
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 20:05:33 +02:00
soul
3f265b6c73 Cargo transfer tests: fill the one-cell bay the way the other fixtures fill a shape
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 19:59:19 +02:00
soul
dadb6f327d Cargo transfer tests: long steps after the receiver leaves or strays move nothing; a full first bay hands on to the next
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 19:56:54 +02:00
soul
0267a191c4 Merge eureka/aetheria-arena-view-seam: one PilotView per side; brains reach other ships only through their side's sensors; unaffiliated ships are sides of one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:50:35 +02:00
soul
488b3fa434 PilotView enforces its own rules: members only, fire-control readouts only on the member's own track; constructor and View setter internal
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 18:26:07 +02:00
soul
961bdafade Determinism ledger: the TwoAiDigestIsPinned comment names both causes of the second repin
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 18:26:03 +02:00
soul
d38ee7cdd6 Approach track-loss test: warm up before reading the follower's aim (the 60 km anchor is lost by the sensors)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 18:25:58 +02:00
soul
70aa1ed037 Transfer verb: drop the redundant pre-check, let range, step length and quantity edges show in tests
TryStore already takes all of a part or none, so the pre-check in TryTransferItem changed nothing observable.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 18:22:43 +02:00
soul
c40a6e8229 View seam tests: per-shooter aim, view gates, sees-hostile, honest track loss (red)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 18:21:12 +02:00
soul
19901c1a92 Merge eureka/aetheria-living-world-catalog-subtraction: DemandProfile and the commodity categories go
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:01:45 +02:00
soul
ae52857a91 SimpleCommodityData: name key 10 as the removed stored Category (Soul s1 simple-key10-comment-misnamed)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:01:23 +02:00
soul
5b599f66bf Merge eureka/aetheria-release-kit-schematic: agent hulls get schematic lines from their kit; the Nakovalnya package lands
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 17:35:42 +02:00
soul
d3cd7813d7 Catalog subtraction: commodity records lose DemandProfile and both stored categories; the trade menu stops filtering by them
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 17:31:08 +02:00
soul
66c38d9506 Build asks for the marks part only when the kit drew lines (OpenSCAD refuses a part that draws nothing); hull_source documents marks
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 17:17:40 +02:00
soul
8d58bca27a Nakovalnya: casemate guns and plate seams emit their lines from the frame that draws them; the copied offsets are gone (geometry and lines unchanged)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 17:11:38 +02:00
soul
98b4f4f946 Build checks that every kit line is drawn in the frame of its geometry: lp_line's marks part meets its lines part
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 17:11:35 +02:00
soul
71b12d594b Kit lines tests: a line drawn outside its geometry's frame fails Build; side-facing fittings emit no line (red)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 17:09:04 +02:00
soul
dab415ad87 Package: her art's takeover leaves no retired hull lines in the .cc (state decides the source; the slot is cleared)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:55:50 +02:00
soul
87dc0f013f Capture Ship Lines frames the ship as Package does, so kit and Grease Pencil lines land in ship.glb's frame
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:54:13 +02:00
soul
bb38036e51 Cargo transfer tests: build the second stack with a second store
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:54:12 +02:00
soul
2b053b34c2 Smoke: a retired hull's lines are not hers, and Capture writes the kit's lines in Package's frame (red)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:52:17 +02:00
soul
bcfc776513 Cargo transfer tests: two stacks of one design come from a commodity that stacks two to a stack
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:50:35 +02:00
soul
4e559d822e Cargo transfer tests: the second bay goes where the hull has room
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:46:35 +02:00
soul
c1aab929b1 Give verb: a stop is a verb with no receiver, and the type pattern skipped it; tests: second bay at a free cell
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:42:42 +02:00
soul
7ac8f38901 Cargo transfer tests: the give verb's rate, ends, instance choice and press order
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:38:18 +02:00
soul
cada2fde3e Transfer verb: a pilot gives cargo to a ship alongside, committed through the conserving bay move
TryTransferItem(SimpleCommodity) checks the target can take the part and stores the part removed, not the stack
left behind (it duplicated and lost units on every partial move, the player's commodity buy among them).
TryTransferItems goes. CargoTransfer.Step owns a give's progress and end; PilotCommand.Give and StopGiving are applied
by PilotApplier; TransferRate is the new tunable, DockingDistance the range.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:38:17 +02:00
soul
3fa4768d1c Cargo transfer tests: a partial commodity move duplicates the stack left behind (red), also through the buy path
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:31:18 +02:00
soul
c1d55952eb Smoke: Build records the kit lines and retiring the hull source deletes them
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:31:11 +02:00
soul
4a6ddaa413 hull_source tests: a non-UTF-8 byte in the drawing, an unreadable number
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:17:00 +02:00
soul
970155a8ac Nakovalnya package rebuilt through the add-on: schematic lines from the kit (silhouette plus 102 kit lines)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:12:17 +02:00
soul
0cb78d2610 hull_source tests: pin style, echo parts, contour area, even-odd and edge tolerance, process refusals, check details
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:11:53 +02:00
soul
b064051efd Retarget test: east is staged knowing the third ship (ruling retarget-test-stages-the-track)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:11:03 +02:00
soul
0350255237 PilotSeamTests: drop the probe text
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:07:35 +02:00
soul
fd1188a797 hull_source: the silhouette is the zones' top view, what Rasterise reads
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:06:59 +02:00
soul
66c69cdc90 Smoke: sideways-facing kit parts draw no lines (octagon and line counts of the Nakovalnya)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:06:19 +02:00
soul
1a2a24ceb1 Staged knowledge: repin the two-AI digest for fighters staged knowing each other; LockOnSituation drops the no-track premise
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:05:39 +02:00
soul
5bb4ad5f0f Smoke: the silhouette may be several contours, all before the kit's lines
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:02:53 +02:00
soul
d5d94fb483 Staged Approach: the pilot starts knowing its anchor, through the sensors' own info state (ruling stage-states-pilot-knowledge)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 16:00:08 +02:00
soul
905074defb Add-on: schematic lines are hers (one Grease Pencil) else Build's from the kit; smoke and C# fact
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 15:59:56 +02:00
soul
b982fb9cd0 hull_source: kit lines echo, SVG silhouette projector, SchematicLine, three line checks
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 15:57:44 +02:00
soul
5c38029ec0 Research: crafting chains and non-gear demand, prior art (serves follow-up crafting-chains-and-demand)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:56:21 +02:00
soul
63e71a1f30 PilotView tests: sides, tracks, handles, designation, fire-control readouts
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 15:56:00 +02:00
soul
28ed24b28e Zhestokost kit and Nakovalnya emit schematic lines from their own frames (outline, step, seam, fitting)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 15:54:27 +02:00
soul
bd343b2a4e Kit frames: mates are lp_at placements that track the Ship Root frame; zone OFFs unchanged
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 15:53:20 +02:00
soul
a18d3b812f Merge Nakovalnya package head a67711b3 into kit-schematic
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 15:50:10 +02:00
soul
2ad52e7e9e PilotView: brains reach other ships only through their side's view, by opaque handles
One view per side over its members' sensors and books. Flight, Combat, Follow and Minion read contacts as ContactId
and fire-control readouts as view calls; FollowTask.Anchor is a handle. Own-ship reads stay direct.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 15:48:02 +02:00
soul
6252c29efa Determinism ledger: repin TwoAiDigestIsPinned for the side split (the fighters were one side and followed themselves)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 15:42:12 +02:00
soul
9b4b46e243 Zone: a side is a faction or one unaffiliated ship; each unaffiliated ship flies a flight of its own (brain-one-per-side-r2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 15:39:02 +02:00
soul
33103ce5da Seam fix tests: a neutral declaration drops the target, so a second press in that step finds none, as before the seam
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:44:28 +02:00
soul
8692cf8600 Command seam: the stance press resolves its target at the step, after the step's target verbs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:39:46 +02:00
soul
2f15c79176 Seam fix tests: the stance press resolves its target at the step, flips the belief, and the ping press pings once (red)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:39:15 +02:00
soul
cfbd044551 Command seam fixes: drop the ping observation, which this fixture's uncharged sensor cannot show
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 06:29:02 +02:00
soul
87f29cb0c8 Command seam fixes: the ping pin waits for the ping's cooldown and charge
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 06:18:32 +02:00
soul
a7477e679d Command seam fixes: the ping pin runs before any switch is thrown; a stance set is pinned as a set beside the flips
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 05:51:10 +02:00
soul
72ad1b8036 Command seam fixes: the shield switch is pinned on a shielded target; the Longinus has none
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 05:07:25 +02:00
soul
bc05417450 Command seam fixes: the player's switches, ping and stance flips are PilotCommand presses; triggers, switches and ping are internal
ActivatedBehavior replaces IActivatedBehavior so Activate/Deactivate can be internal under Unity's C# 9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 04:33:20 +02:00
soul
25750c225b Command seam fixes (red): stance flips and the player's switches and ping are presses on PilotCommand
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 04:32:17 +02:00
soul
30bcabf172 Command seam fixes: pin Combat's release of a pulled lock, its commanded-aim pricing, and the order and count of same-step presses
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 04:31:47 +02:00
soul
b180fd732d Command seam, player: the Previous and NearestEnemy pins start from no target, where no other verb gives their answer
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 02:47:57 +02:00
soul
d8a4e1567d Command seam: the trio's third ship sits in east's sensor cone; the lock pin holds the guns so the lock completes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 02:31:44 +02:00
soul
76e4e6f58f Command seam: the trio fixture waits for its contacts, and the lock pin for any launcher's lock
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 02:30:53 +02:00
soul
06435bdefa Command seam: the lock-pull pin carries the target past the launchers' minimum range
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 02:29:58 +02:00
soul
bee079b1c8 Command seam, player: the reticle verb's pin answers a target no other verb would
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 02:28:16 +02:00
soul
6f4dbff317 Command seam, player: the control setters and pilot methods are internal, so no Unity script can write them
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 02:27:30 +02:00
soul
394c13a099 Command seam, player: ActionGameManager and ActionBarSlot write only Entity.Command (the 18 direct writers)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 02:27:29 +02:00
soul
76143e50d1 Command seam, player: PilotCommand gains Tractor, the target Verb, CycleAim and Iff; HelmInput.Demand returns a TurnCommand resolved each step
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 02:27:28 +02:00
soul
9d9a8ed031 Command seam: the applier is the only writer from PilotCommand; the pilot reads back its own pending decisions (Soul finding only-reader-comment-false)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 02:27:08 +02:00
soul
7427a70971 Command seam: pin the Release path, Combat's lock pull and release guard, and the retarget step's pending reads (Soul findings release-path-untested, retarget-step-pending-reads-untested)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 02:26:28 +02:00
soul
b519fbd3d0 Merge remote-tracking branch 'origin/master' into eureka/aetheria-arena-command-seam-agents 2026-10-22 02:12:19 +02:00
soul
bbb1dc717e Merge remote-tracking branch 'origin/master' into eureka/aetheria-gravity-laned-belts 2026-10-22 02:03:21 +02:00
soul
67e2647753 Gravity: restore the swarm hosts' Empty guard; a standing binary's bodyless node keeps its mass and hosted swarms (finding swarm-on-bodyless-node)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 01:40:28 +02:00
soul
dc36b8400f Gravity tests: no swarm rides a binary's bodyless node; lane filters on a tuned sample; a lane splits rocks by band area (red)
NoSwarmRidesABinarysBodylessNode is red at 95903ab0 (finding swarm-on-bodyless-node). The tuned LanedBeltTests
sample reaches the swarm-host and one-shape-per-parent filters (lane-filters-unexercised); ALaneSplitsItsRocksByBandArea
pins LaneRocks' area share (lane-split-unpinned).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 01:40:15 +02:00
soul
a67711b3d3 aetheria.nakovalnya: side flak rows and radiators are 2x1 in the grid frame (drive.py rerun through the add-on)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 01:31:16 +02:00
soul
c7e85676fe ship_cc: replace_layout refuses on hull_grid.MAX_CELLS, the grid limit's one owner
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 01:28:07 +02:00
soul
da77380a04 Hull source: build refuses a hull type that is not a HullType name, before reading the source
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 01:28:06 +02:00
soul
efeadec265 Blender ships tests: build refuses a hull type that is not a HullType name; the grid limit is hull_grid's (red)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 01:27:38 +02:00
soul
d76563dd5a NakovalnyaPackageTests: every mount takes its gear through Takes; rows inside the shape and disjoint; no footprint-count pin (red)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 01:25:22 +02:00
soul
8bfb06ed64 Hull source tests: a Station 32 cells long fits the grid (height bound, not only width)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 01:15:11 +02:00
soul
28496f27df Hull source smoke: HullType gate (refused as Ship, built as Station, Station refused on the grid)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 01:07:54 +02:00
soul
2267d8beec Hull source: language proportions judge Ship hulls only; zones must fit the layout grid
build() takes the hull's HullData.HullType, with no default; the add-on reads
it from the bound ship.cc. A Station or Turret lists no proportion check; every
hull keeps symmetry, length, budget and anchor checks and gains the grid check,
judged over zone meshes through hull_grid.grid_size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 01:03:27 +02:00
soul
0a70e0cf93 Blender ships: hull_grid.grid_size; ship_cc reads HullData.HullType, pinned to C#
grid_size is rasterise's grid arithmetic without its MAX_CELLS refusal, so a
check can judge the bound; rasterise calls the same private filter and sizing.
ship_cc gains HULL_TYPE_SLOT (Key 25), HULL_TYPE_NAMES and read_hull_type;
ShipSchemaPinTests ties both to HullData.HullType and the HullType enum.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 01:00:06 +02:00
soul
321e34a0da PilotSeamTests: an agent's command lands before the next agent decides
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:46:10 +02:00
soul
95903ab00e Merge origin/master (arena cut 1: ItemManager takes a seed) into eureka/aetheria-gravity-laned-belts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:44:50 +02:00
soul
0e9b9c71e2 Gravity: lane half-width 1.5 body radii (ruling lane-narrow), code default and Settings.asset
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:44:36 +02:00
soul
74257926b1 Gravity: a parent's outermost child never hosts a lane (ruling lane-not-outermost); one host filter, an only child pinned
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:44:35 +02:00
soul
f7314b2661 ShipProductTests: stats --top-speed refuses a hull with two VelocityLimits and leaves it untouched
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:41:12 +02:00
soul
07bd57d9e1 ConsumablesFirstSetTests: the vent's smallest cloud holds every sold ship hull
Ruling vapour-cloud-sold-hulls; the radius is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:41:12 +02:00
soul
bc53361ff9 Census tests: sold hulls are LoadoutGenerator.SoldHulls; a hull's asset is its Prefab or its mod-ship Visual record
Ruling census-tests-read-the-owners.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:41:10 +02:00
soul
9ac8f23307 RunStartTests: the own-maker and factionless reach claims hold for every product LoadoutGenerator.IsOffered offers
Ruling census-tests-read-the-owners.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:41:09 +02:00
soul
487c45f2bd PilotSeamTests: price the commanded aim item after the guns come online
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:39:43 +02:00
soul
2f71975194 PilotSeamTests: the seam's rules (agent writes only its command, held and one-shot fields, apply order, lone-ship side, commanded aim item)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:36:15 +02:00
soul
3795bdb066 Tests: Flight and Steering fixtures drive pilots through the seam (apply after a direct update; scripted demands are commands)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:36:14 +02:00
soul
26a25fce3a Tests: drive agents through the seam (apply after a direct agent update; the duel's scripted player commands)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:32:12 +02:00
soul
9a2a9390e1 Command seam, agents: one PilotCommand per entity, applied once per step by PilotApplier in Zone.Update
Agent, Flight, Combat, MoveTo and Follow write only Ship.Command. Entities
with no agent apply first, then each agent applies right after it decides.
A pilot reads its own pending designation and aim item back from its command;
FireControl gains Refuses/AgentFires/HitProbability overloads that take them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:32:11 +02:00
soul
85e539af2b NakovalnyaPackageTests: import System.Collections.Generic
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:29:47 +02:00
soul
95f89a38a6 NakovalnyaPackageTests: the package composes, its Zhestokost product is never offered or drawn, and its layout is the brief's
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:28:59 +02:00
soul
b13aa7385a Command seam, agents: delete the 17 direct pilot writes in Agent, Flight, Combat, MoveTo, Follow
Each write is replaced by a /*DEL*/ marker; the next commit routes every
marked site through the entity's PilotCommand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:28:53 +02:00
soul
7cb6fb0c9d Mods: aetheria.nakovalnya, the Zhestokost boss hull package (hull.scad, ship.cc, ship.glb)
Built from hull-bench Nakovalnya B by the Starfire job: New Ship --like Djinni, ship-authoring stats (mass 60000, durability 8000, armor 40, drag 0.35, top speed 25), price 60000000, a not-offered Zhestokost product, Build, Rasterise (19x30), 27 layout rows, Package. The validator accepted it and compose excludes nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:28:31 +02:00
soul
0bccb395a4 ShipProductTests: compare the stats hull by its MessagePack bytes (Newtonsoft loops on Shape)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:22:44 +02:00
soul
2fd8cbe8e9 AetherDb: ship-authoring stats sets a package hull's mass, durability, armor, drag and top speed, judged before the file opens
Ruling ship-authoring-stats-command. --top-speed writes a fixed top speed (Min = Max) on the hull's one VelocityLimit and refuses a hull with none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:17:59 +02:00
soul
3efc8cddd7 Merge remote-tracking branch 'origin/master' into eureka/aetheria-arena-determinism-ledger 2026-10-22 00:01:30 +02:00
soul
ad05486388 Zhestokost kit: zh_z keys a donor's zones 'zhestokost:<zone>' when HULL_DONOR is zhestokost
zh_key in ewanhart.scad's form, not AU's either-name match: a Pirate hull's own zone named like a Zhestokost
zone no longer receives Zhestokost geometry. Closes the gap in follow-up zhestokost-kit-donor-routing.

Spec: aetheria-release:cut_spec:cut-zh-kit-landing.r1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 23:41:15 +02:00
soul
2fe4129777 Zhestokost kit: land the hull-bench kit; zh-ammo-tender fixture rebuilt from Oboz A with one defensive mount
factions/zhestokost.scad is the bench draft byte for byte (battered masses, typed mates, five zones with cream
marking), its lang comment naming the maker element. The fixture is bench Oboz A plus Gun.D on T[2]'s deck
centreline aft of its hatches; the smoke's palette, shadow colour and turned-mount pair follow.

Spec: aetheria-release:cut_spec:cut-zh-kit-landing.r1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 23:40:26 +02:00
soul
462adf2693 determinism-ledger: pin each scatter draw's ordinal and bounds, and the zero item-seed refusal
Three origin drifters, each placed at For(CombatSeed, ScatterStream, k) within half the radius;
a galaxy seed whose ContinueStep item seed wraps to zero is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 23:38:03 +02:00
soul
a46c0536cc release map: ProductWeight's line share counts only offered bins
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 23:25:26 +02:00
soul
25c2a18ab3 Merge remote-tracking branch 'origin/master' into eureka/aetheria-release-product-not-offered 2026-10-21 23:25:16 +02:00
soul
f102b082fd Gravity: laned belts, a belt on a copy of a light body's rail filling its slot less a visual lane
Place records every child's slot; GenerateEntities marks belt candidates. After the swarm pass, a lane pass on its own
stream gives a candidate that kept its body, has a band at least the lane's half-width on both sides, carries no swarm and
whose parent has no other belt shape, an annulus on a copy of its rail: rocks over [slot inner, D - E] and [D + E, slot
outer], E = LaneBodyRadii body radii. ZoneGenerationSettings gains LaneProbability (0 in code, 0.5 authored) and
LaneBodyRadii (5). LanedBeltTests pins it; EveryBeltIsKindedAtGeneration moves there and asserts lanes; BeltShapes.Lanes
lands with it; both TrojanSwarmTests fixtures draw no lane.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 22:42:49 +02:00
soul
f7cdb766cd LoadoutGenerator: IsOffered reads a bin's line, and BinShares counts only offered bins, so an off-market bin leaves its line the whole weight
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 22:01:41 +02:00
soul
f2cd036890 ProductChoiceTests: rig bins honour the not-offered list; an off-market bin leaves its line the whole weight, and an off-market line's bin is never drawn or stocked
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 22:00:55 +02:00
soul
749bdb09f5 ShipProductTests: the product grammar refuses a misnamed flag or a dangling description, and the on-market line is the off-market one without its clause
Kills the four Stryker survivors of the --not-offered parse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:58:09 +02:00
soul
d0fc8adb70 Gravity: a wider zone fingerprint, one copied-rail helper, belts kinded at generation, adjacent belts pinned
ZoneFingerprint digests rock spin and periods, orbital entities (type, position, rail), other entities by type, the
pack radius, a 9x9 height grid at two times and optionally PopulatedRadius (finding fingerprint-omits).
TrojanSwarmTests.BaseDigest is repinned with it on Yggdrasil at C1's generator, swarms off.

BeltShapes.CopiedRail replaces StationPlacementTests.SwarmRail and the RidesRail slot filters of
ChildrenWithinReachTests: a rail a belt rides that copies a body's rail is never a placed slot.

The Zones fixture counts the belts the generator writes and those with no field kind, before Zone's own
FieldKinds.Ensure; EveryBeltIsKindedAtGeneration pins it at zero (finding ensure-unpinned).
AdjacentBeltsKeepTheirSpacing places two belts side by side and pins the second's inner edge a sibling spacing
outside the first's outer edge (follow-up belt-after-belt-edge-pin).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:43:23 +02:00
soul
73d1cee9de Gravity: one belt extent rule, read by Zone, the generator's clearance tree and the station tests
AsteroidBelt.Extent(data, rail) is now static over the belt's data and the only spelling: the clearance tree's
'Distance + BeltHalfWidth' and the instance Extent are gone. The swarm host filter loses its dead '!p.Empty'
(no bodyless node has a parent). Findings extent-second-spelling and dead-empty-guard (trojan-swarms-residuals).

The clearance tree now reads the outermost rock, not the rail plus half-width, so 29 of the 1920 zones of seeds
1-30 place a station or turret differently. TrojanSwarmTests.BaseDigest is repinned for seeds 3-6, which hold such
zones; seeds 1 and 2 are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:38:43 +02:00
soul
51a3fd0bc9 Merge remote-tracking branch 'origin/master' into eureka/aetheria-release-delete-muzzle-facing 2026-10-21 20:38:06 +02:00
soul
aea0ce823d release map: AH-P1 checks line no longer lists the deleted nose-ward rule
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:37:59 +02:00
soul
0c6f359cb5 ProductLinesCatalogTests: FactionProductData's wire pin covers slot 8, and a record written before it is on the market
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:34:50 +02:00
soul
bd5d27c52b test_hull_source: a mount is not paired by another gun's muzzle
Kills the surviving and->or mutant in the kept pairing rule's unmuzzled list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:31:47 +02:00
soul
3d005ab559 LoadoutGenerator: IsAvailable's off-market comment names no boss term
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:30:04 +02:00
soul
9cedb4efe6 AetherDb ship-authoring product --not-offered writes the package product off the market
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:28:41 +02:00
soul
9c50b7499b FactionProductData.NotOffered: an off-market product is never drawn, stocked, chosen or a gear home
One reader, LoadoutGenerator.IsOffered, read by IsAvailable (ahead of the no-faction
shortcut), the AvailableProducts candidates (so the any-maker fallback cannot reach one)
and SoldHulls. A Loadout that pins the product still builds it. Rulings
off-market-is-unoffered-product and boss-hulls-exclusive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:28:40 +02:00
soul
51e82624d5 test_hull_source: pin that a muzzle facing any way passes the hull source
Muzzles aft of and beside their mounts pass every check, so a reintroduced
facing judgement fails a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:26:17 +02:00
soul
6f12283dc0 gravity map: laned-belts mapped and admitted; lane host and half-width cells filled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:26:15 +02:00
soul
ee78b85fd0 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>
2026-10-21 20:26:02 +02:00
soul
0524d3729d determinism-ledger: pin the two-AI digest captured on Yggdrasil (linux-x64)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 19:09:20 +02:00
soul
ced96568bc determinism-ledger: the ItemSeed test's expected values wrap as the formula does
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 19:06:49 +02:00
soul
cc5d498b7e determinism-ledger: tests for the seeds, the scatter, Scenario.Id and the two-AI fight
DeterminismLedgerTests (a RunStartTests partial): ItemManager refuses seed 0;
ItemSeed is the run seed and step; the unpack scatter ignores the item stream
and differs per zone; the SimulationDice streams are distinct; scenario Ids are
unique kebab; a two-AI fight is one history, and its digest is pinned for
linux-x64 (pin lands in the next commit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 19:04:51 +02:00
soul
9379712c78 determinism-ledger: Scenario.Id, a stable kebab identity for matches and replays
Every scenario, and the test Scripted scenario, names an Id; Name is
display only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 19:01:43 +02:00
soul
cc0c164574 determinism-ledger: every ItemManager states its seed; scatter on the zone's dice
- ItemManager(uint seed, ...) refuses seed 0 and seeds Random from it.
- RunStart.ItemSeed(galaxy, step) is public (formula unchanged), refuses a
  zero result; named steps ArenaStep, StageStep, ContinueStep and NoRunSeed.
- Continue seeds the item stream from the saved run's seed (ContinueStep);
  a scene with no galaxy uses NoRunSeed.
- Zone's unpack scatter draws SimulationDice.For(CombatSeed, ScatterStream,
  1-based pack ordinal), never the item stream.
- Loadout's scratch manager and AetherDb's catalog command take fixed seeds;
  every test and Editor construction gains 1u (mechanical rewrite).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 19:01:41 +02:00
soul
c13bb2368e determinism-ledger: delete the clock-seeded item stream and the item-stream scatter
ItemManager no longer seeds itself from DateTime.Now, and Zone's unpack no
longer scatters ships with _itemManager.Random. Deletes before adds: this
commit does not build (the scatter and every ItemManager seed return in the
next commit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 18:59:52 +02:00
soul
3df3a9549a aetheria-arena target: deterministic headless matches between swappable brains
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 18:48:50 +02:00
soul
d52b50f911 Research: strategic AI arena and living world prior art (persistent faction worlds, bot tournaments, believability, restricted play, fun proxies, off-screen combat models), for rulings arenas-explore-the-metagame and colosseum-feeds-matchup-model
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 18:06:49 +02:00
soul
2f4b617e50 Merge remote-tracking branch 'origin/master' into eureka/aetheria-release-package-agent-hull-guards 2026-10-19 14:25:09 +02:00
soul
a9207abba9 smoke_hull_source: pin H10, H6 all-edited, maker-less record, collider-alone and the unbuilt exit text
Soul s4 survivors: the zone test keyed on hull.scad, an edited-zones exclusion, the maker-None branch, a
collider counted as art, and the unbuilt refusal losing its exit text or becoming the agent state.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 13:45:09 +02:00
soul
2882907f66 smoke_hull_source: pin agent hulls with no zones and no art (1.9a), her art over an unbuilt hull.scad (3.1) and a plain hull (3.2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 13:11:10 +02:00
soul
82dab50ab5 Aetheria ships: _hull_state is the one owner of a hull's state; Package's guard and retirement read it
Deletes _art_takes_over and _refuse_wrong_maker's hull.scad branch. Her art over a hull.scad Build never ran now ships and retires the source (operator ruling art-over-unbuilt-hull-source-takes-over).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 13:11:09 +02:00
soul
96cb6d20f1 Research: prior art for agent-authored stand-in hulls (render-critique loops, part libraries, self-judging limits)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:01:22 +02:00
soul
1ecddc0e69 Merge remote-tracking branch 'origin/master' into eureka/aetheria-release-boss-loadout 2026-10-19 02:45:13 +02:00
soul
3ac185a84e aetheria-release map: hull collection states (HS) and the agent-built row
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:07:26 +02:00
soul
f121b46463 Package's art exemption keys on Build's maker record, not hull.scad; case-variant Manufacturer and attack maker keys pinned in smoke_hull_source
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 01:44:15 +02:00
soul
f3719d9943 Boss refusal tests: compare the serialized ledger, not the pack (pack bytes differ between identical runs)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 01:30:10 +02:00
soul
820ec73f76 Boss refusal tests: a mismatch prints both zones and the log
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 01:29:03 +02:00
soul
66510b003f Boss refusal tests: gear projection reads ships only (turrets are not ships)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 01:25:40 +02:00
soul
8ae052bb47 Materialize refuses an unbuildable Loadout before minting or drawing; delete the boss item-stream restore
Materialize now settles what the catalog alone decides, tries the cells on a
throwaway ItemManager (own ledger and stream, designs built unbranded), and only
then asks the chooser and mints lots. ZoneGenerator's copy-and-restore of
itemManager.Random is deleted, and the boss faction's LoadoutGenerator is created
on the first choice instead of as an argument. Resolve keeps its signature and is
Settle then Build.

Tests: an unbuildable boss zone equals the no-boss zone (serialized pack, ledger,
roster and gear, next item draw) for one failure, several failures and design-only
slots, across every faction as boss faction.

Not yet built or run when committed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 01:22:38 +02:00
soul
70c366b541 smoke_hull_source: update the view layer before comparing world positions around a refused Package
matrix_world is stale after a location edit until the depsgraph runs, so the
scene snapshot taken after moving Ship Root compared stale to fresh.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 01:15:26 +02:00
soul
8a1125f1fc ShipSchemaPinTests: remove a stray line left in the maker-key pin
The previous commit does not compile (a/2284391b: ShipSchemaPinTests.cs line 118).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 01:10:42 +02:00
soul
a2284391ba Pin the three language maker keys to the catalog; pin product_makers' exact comparison; fix a stale derivation line
ShipSchemaPinTests reads each factions/*.scad maker literal against its Faction
record key in GameData/Aetheria.cc. test_ship_cc pins that Manufacturer keys are
not case folded and that a bare string product body is refused, not read as a
maker. hardpoint-sizes-prior-art.md no longer describes the deleted derivation.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 01:09:12 +02:00
soul
399dc769df Package's maker guard keys on Build's record, not on hull.scad; smoke_hull_source fixes
_refuse_wrong_maker returned early when hull.scad was absent, so a built hull
packaged with any product once the file was deleted. It now keys on
aetheria.hull_maker; only art takeover (which retires both) and a package that
never held a built hull are exempt.

smoke_hull_source: ATTACKS lang literals gain the eighth (maker) element; new
refusal steps for hull.scad gone and for a right-maker plus a wrong-maker
product; a refused Package must leave the scene untouched (guard order).
Unrun at this commit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 01:09:11 +02:00
soul
8a6816ed3e test_ship_cc: a product body one short of the Manufacturer slot is refused, not an IndexError
Kills mutant short-body-reads-past-end.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 00:25:31 +02:00
soul
af993acf07 ZoneGenerator: an unbuildable boss Loadout restores itemManager.Random, so the zone's later draws equal the no-boss zone's
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 00:21:11 +02:00
soul
7f35c79ecc Package refuses an agent-built hull unless its product is by the language's maker
Language files carry their Faction record key (eighth element of the lang echo);
Build records the maker it judged (aetheria.hull_maker); Package compares it with
ship_cc.product_makers before writing anything (ruling hull-faction-is-product-maker,
finding hull-names-its-own-faction). _art_takes_over is shared with _retire_hull_source.
Unit tests untested at this commit until the Yggdrasil run; bpy smokes not run.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 00:20:00 +02:00
soul
adaa8b8865 Build and Package refuse objects that share an aetheria.id, by selection
One _refuse_shared_ids: a copy of the collider, an emitter, a radiator or a mount
selects the original and the copy and names a count, not an object
(follow-up hull-copy-refusals). smoke_hull_source covers it (not run: bpy smokes).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 00:17:18 +02:00
soul
eaf7b799d4 Package moves no row: delete the schematic-from-model derivation
Remove _plan_mounts/_apply_mounts, _derived_position, CENTRELINE, hull_grid.cell_at,
mounted_position/mounted_cell and their tests (ruling remove-package-schematic-derivation).
Add Weapon makes a default-Position row; Mirror Mount mirrors each space within itself.
smoke_mounts rewritten (not run: bpy smokes are held off Starfire).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 00:16:35 +02:00
soul
35c93dd6ab aetheria-release map: body facts and rationale for cut-package-agent-hull-guards
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 00:13:44 +02:00
soul
1950397630 RunGoalTests: Soul's s1 probes as behaviour tests (referenced Loadout wins on a shared hull, refusals fly no catalog Loadout, no maker fallback, boss path draws only what Materialize draws)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 00:04:45 +02:00
soul
5acbaa1b2f Merge eureka/aetheria-release-agent-hulls (head 3c4ea230)
Agent-built hulls, first cut: per-faction form-language files, per-hull sources, Build From Hull Source (rulings hull-art-communicates-form, zhestokost-language-lore-text). Reports cut-agent-hull-source.h1, h2; Soul s1, s2 hold on the fixes (open: question who-names-a-hulls-faction, follow-up hull-copy-refusals). The branch touches only tools/blender, which master did not change since base d28ce42d.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 22:58:46 +02:00
soul
3c4ea230bc test_hull_source: a fingerprint tells nested empty lists apart
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 22:36:54 +02:00
soul
d5491c1c57 test_hull_source: a genuine language echoed after an altered one, and a faction name that escapes the library
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 22:35:14 +02:00
soul
1e7678526a Agent hulls: the faction file owns its language; Build owns what it recorded, not what is marked
hull_source.build reads the faction's and donor's language from factions/<faction>.scad run alone and only compares the
hull's own lang echo with it; a hull that reassigns, re-echoes or shadows a language fails a check.

Build records what it made on the collection by name and fingerprint instead of marking objects. A copy (another name) or an
edit (another fingerprint) is the author's: Build refuses and deletes nothing. Package retires hull.scad when render meshes
that are not Build's remain and no zone mesh of Build's does, so keeping Build's anchors and collider no longer blocks
takeover.

Unit tests cover the runner (arguments, exit status, OPENSCADPATH), non-string names, the width/length bound, own-zone
colours and the language ownership; the smoke pins world poses of mounts, emitters and emitter meshes (a mount turned 90
degrees included), the language attacks with the real OpenSCAD, copy and edit, Build after Package and the takeover.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 22:34:41 +02:00
soul
5a050f0fca Merge eureka/aetheria-release-run-seed (head cb1c093e)
Reports cut-run-seed h1/h2; Soul verdicts s1, s2 hold.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 22:29:04 +02:00
soul
d83cacc6a7 Merge eureka/aetheria-release-product-tiers (head e6a17a5e)
Reports cut-product-tiers h1/h2; Soul verdicts s1, s2 hold.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 22:29:02 +02:00
soul
f9acdc1e9c smoke_hull_source: the collider is the language's body zone mesh (a hand mutant took the first zone and survived)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 22:08:53 +02:00
soul
2e597b0715 test_hull_source: the survivors of the first hand-mutant round get tests (vertex bound, exact bilateral bound, emitter triangles in the budget, non-mount muzzle parent, donor colours, missing donor language, empty silhouette)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 22:04:42 +02:00
soul
24f057d18f Build From Hull Source: zone meshes, mount anchors and a collider from the package's hull.scad; Package retires a hull.scad that operator art replaces
Build refuses a source that breaks its language or brief, and a collection holding render meshes it did not make;
it replaces only objects marked aetheria.hull_source and writes no layout row. Package deletes hull.scad, and says so,
once the collection holds art and none of Build's objects. OpenSCAD path is an add-on preference.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 22:00:15 +02:00
soul
763fa0987f hull_source: reads a hull.scad through OpenSCAD and judges it against its faction's form language
One language file per faction (Zhestokost, Rossum & Douglas, Pirate) owns the shape kit, proportions, symmetry rule and
palette; the Zhestokost palette follows the lore page (ruling zhestokost-language-lore-text). The two probe hulls are
ported as fixtures. Checks: zones, emitter and radiator meshes, donor, body proportions, top-view mirror overlap,
length, triangle budget, muzzles nose-ward. Fixture-text tests need no OpenSCAD.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 21:57:47 +02:00
soul
f17b04ec1a FlightTests: Faction's wire array ends at slot 22 (BossLoadout), so it is 23 long
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 21:31:57 +02:00
40db8291d8 Exit_open_only_in_the_exit_zone: the gate boss pins its hull's brand, so any faction builds it
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 21:25:21 +02:00
d8afb9d184 Exit_open_only_in_the_exit_zone: a failed boss build reports the log
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 21:24:46 +02:00
f235eea3d9 Exit_open_only_in_the_exit_zone: the faction with the boss Loadout is one that can get its hull's brand
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 21:22:05 +02:00
3ad3fcc505 RunGoalTests: using GameCult.Caching.MessagePack
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 21:19:14 +02:00
d2ec3a6678 RunGoalTests: using GameCult.Caching
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 21:18:49 +02:00
ad503059e5 RunGoalTests: LoadoutCells reads the fixture's cache
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 21:18:20 +02:00
e5485bb3e0 aetheria-release map: agent-built hulls replace the stand-in section (form languages, probe, first cut)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 21:17:42 +02:00
e2b1b9ca7b RunGoalTests: the boss flies its faction's boss Loadout; refusals, exact cells, brand by context, market rule, main-galaxy pick
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 21:17:05 +02:00
e6a17a5e01 ProductLinesCommandTests: the bins of a removed line go with it, as the catalog read now requires
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 21:14:14 +02:00
352e221453 Faction.BossLoadout (key 22): the boss is its faction's Loadout, resolved by Loadouts.BossOf and built by Materialize
BossOf refuses typed (BossRefusal Unset or Missing, Describe names the field and echoes no input).
ZoneGenerator builds the boss from the Loadout with ChooseProduct as the chooser; a refused or
unbuildable boss logs and spawns none. Galaxy picks boss factions through BossOf; AetherDb factions
prints the boss Loadout. Loadout vocabulary: no preset, no variant. Production builds; the tests
project does not until the next commit (RunGoalTests still sets BossHull).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 21:14:09 +02:00
900fa51e43 Delete Faction.BossHull (key 10 retired), GenerateBossLoadout and the three tests that pin its fallback
A boss is a Loadout the faction references, never drawn (rulings faction-boss-is-a-loadout-reference,
boss-gear-is-a-loadout, demo-boss-hull-invented). This commit does not build: ZoneGenerator, Galaxy,
AetherDb and the tests still read the deleted members; the next commit replaces them.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 21:12:58 +02:00
0769314ee6 aetheria-release map: boss loadout replaces boss hull derived (Faction.BossLoadout, off-market by product)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 21:11:01 +02:00
8d65e957f8 sensor-stat-set skips a bin, as the other role writers do; it takes an optional root for its test
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 21:08:11 +02:00
f73507ff2f Catalog read refuses a bin with a non-finite Floor, a missing Line or a Line that is a bin; a bin mints from exactly its named line (tested)
The runtime no longer trusts the authoring tool for a bin: a NaN Floor weighed NaN and nulled every
draw of its design. AetheriaStores.Open names the field and echoes no value.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 21:08:10 +02:00
129e5ff4dc ProductWeight is the one weight of every design-only draw; a line's bin shares are read once per pass
The all-weights-zero branch of ChooseProduct and the required-item fallback of RandomProducts weighed
every product 1, so a bin doubled its maker's presence there. Both now weigh by Share alone (the
preference drops out, the bins' shares do not). The sum of a line's bins' pass fractions is computed
once per weighting pass instead of scanning the product table per candidate.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 21:08:09 +02:00
d28ce42df9 Research: faction form language and role readability for agent-built hulls, for ruling hull-art-communicates-form
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:37:22 +02:00
a40072d9ab aetheria-release map: mark the stand-in section superseded by ruling hull-art-communicates-form
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:30:06 +02:00
096f833c24 Merge eureka/aetheria-gravity-trojan-swarms (head de3ecd9b)
Trojan swarms: belts riding their host's rail period at L4/L5 (rulings trojans-first, trojans-ride-host-period-with-stations). Report cut-trojan-swarms.h1; Soul s1 holds (four Lows deferred to follow-up trojan-swarms-residuals); GravityFieldParityVerify passes. Master's commits since base 67517f40 are docs only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:24:12 +02:00
cb1c093e84 ConsumableAiTests: open the run store so a scene can continue a run at a fixed seed
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 20:21:31 +02:00
0ed0605aa5 Pin the run seed's edges: AI dice stay on CombatSeed, full-width seed, refusal names its field, refusal test cannot hang
- ConsumableAiTests.ThePilotsDiceDoNotMoveWithTheRunSeed: sample offset and patrol circuit equal at different run seeds.
- RunSaveTests.TheRunSeedRidesTheSave uses a seed with the top bit set.
- RunStartTests.AGeneratedGalaxyRefusesSeedZero builds from authored settings (small ZoneCount) so a constructor that stops refusing fails by assertion, and pins ParamName and no ActualValue.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 20:13:03 +02:00
9457e4c8f9 ProductBinTests: the search's 64-draw bound and its stop at the first clearing draw, and the missing-line log, are pinned
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 19:51:40 +02:00
beb733f4b5 Census role gaps become RoleGaps, so a bin skipped by the census has a test; a bin authored on another line is refused
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 18:46:05 +02:00
de3ecd9bb0 Trojan swarm tests: host class, rock side parity, width and arc reach
Stryker survivors on the generator showed the host-mass gate, the side of each
rock and the width/arc scale were unpinned. Builds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 18:42:44 +02:00
112dd24536 aetheria-release map: boss hull derived r4 (fixed Loadouts), stand-in hulls (SI) probe and cut sequence
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 18:20:07 +02:00
fb7b95fb06 ProductChoiceTests: a bin takes nothing from another maker's product
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 18:19:00 +02:00
aec7b3c019 product-lines tiers lists every row, present or new, before it reports there is nothing to do
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 18:16:27 +02:00
9b7a1c00dc Catalog: the six tier bins (product-lines tiers apply); TierRowsAreShipped; the tier step pinned from both sides
Aetheria.cc written only by the command, on the pinned CultLib. Replay the command at merges; never merge the blob.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 18:11:11 +02:00
6768cdcb95 product-lines tiers: authors the six bins from a table of (line, bin, floor, text)
A bin gets its line's design and maker, Bin = { Line, Floor }, and no roles or assembly. Resolve-then-write
as the other lines do; a replay is a no-op. The catalog is not yet applied.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 18:08:56 +02:00
2806b443c9 Census, roles-migrate and roles-backfill skip a bin: it authors no roles, it reads its line's
RolesMigrate takes an optional root, as RolesBackfill does, so its bin skip has a test.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 18:06:07 +02:00
7f09b05725 ProductWeight: a bin takes its pass fraction of its maker's preference and its line the rest (ruling bin-share-by-floor)
Phi comes from CultMath's math.erf.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 18:04:08 +02:00
49853d3166 ProductBin: a binned product names its maker's line and a floor; the mint draws above that floor
FactionProductData slot 7 Bin (schema id unchanged, additive as slot 6). Mint derives a bin's
distributions from its line on every lot, truncated by NextGaussianAbove (64 tries, then the cutoff).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 17:58:51 +02:00
b19d6add89 aetheria-release map: product tiers follow ruling bin-share-by-floor (spec r2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:51:50 +02:00
47f834ea23 Pin all five pre-run-seed drops of the dice zone (the first pin kept three)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 17:39:52 +02:00
727c409659 Pin the dice of a zone without a run seed at their pre-run-seed values
A zone with no galaxy and a zone of a save written before run-seed (seed 0)
drop the items and scatter the mines they did at eb27ced1; values computed
at that base.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 17:33:11 +02:00
f23d3c6542 Drops roll on the run's seed: Zone.DropSeed feeds the loot and mine dice
DropSeed = CombatSeed ^ Mix(Galaxy.Seed), derived at construction. A zone with
no galaxy, or a restored seed of 0, keeps its dice bit for bit; CombatSeed
still roots shot, patrol and range-sample dice. RunSeedFixture builds a test
zone's galaxy through RunSave.Commit and the Continue constructor.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 17:30:50 +02:00
020dc58a81 The run's seed rides the save: SavedGame key 14, written by Capture, read by Continue
Galaxy.Seed on a continued run is the saved seed instead of zero. A save
written before run-seed decodes Seed 0.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 17:29:17 +02:00
8779304499 Galaxy generation refuses seed 0: delete the clock fallback and the seed defaults
GalaxyStage.SeedFrom is the one minter of a run's seed; both generation
constructors now take progressCallback and seed without defaults and throw
ArgumentOutOfRangeException on zero instead of reading the clock.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 17:28:36 +02:00
6d10331c05 aetheria-release map: boss hull derived, product tiers, run seed (body facts BH/PT/RS, cut sequence; InfluenceScale moves to SavedGame key 15)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:24:54 +02:00
ac835a8ab2 Research: agent-built hull art prior art (LLM 3D workflows, ship generators, low-poly style, internal fit), for ruling hull-programmer-art-stopgap
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 16:52:32 +02:00
9cdd885af0 Trojan swarms: tests for the period rule, stations, the unmoved zone and the save format
ZoneFingerprint pins a swarmless zone to digests computed at 67517f40 over seeds
1-6. Builds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 16:28:44 +02:00
92d6153c2e Trojan swarms: a belt on a copy of its host's rail whose rocks ride the rail's period
Rulings trojans-first, trojans-ride-host-period-with-stations. AsteroidBeltData
key 11 RidesRail; AsteroidBelt.Extent is the one belt extent rule (replaces
Zone's inline farthest-rock loop and the Math.Max at the well node); a swarm's
Periods are its rail's period. The generator draws swarms from a stream of its
own (TrojanStream), so no other draw in the zone moves. Tuning defaults are the
spec's proposals; the code default TrojanProbability 0 draws none. Existing
annulus tests skip swarm rails. Builds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 16:28:42 +02:00
eb27ced147 Research addendum: why BattleTech keeps BV apart from C-bills (operator's examples)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 16:25:25 +02:00
06a173a5b2 aetheria-gravity map: trojan swarms (body fact B61, belt-shapes cells, rationale, cut sequence)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 16:24:07 +02:00
0f7ae88e26 Research addendum: outfitting budgets are usually a value currency (BattleTech BV vs C-bills)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 16:05:36 +02:00
67517f407f Merge eureka/aetheria-release-quality-readers (head e95a3657)
Every reader of item quality reads Lot.EffectiveQuality (ruling effective-quality-mean-of-roles). Reports cut-quality-readers.h2, h3; Soul s1, s2 hold. Master's commits since base 70c7a497 are docs only, so the merged code equals the verified head.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 11:22:04 +02:00
e95a365719 ItemData role-validation comment: the fallback is the effective quality
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 11:06:46 +02:00
8cc7f1bc85 Pin GetTier: it hides an over-specced part, and its tolerance and upgrade step at the shipped thresholds
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 11:06:45 +02:00
7206c5fdf4 StatsReadTheLot: drop the upgrade count of the lot below the first tier
Its effective quality .15 sits under Common's .2, which GetTier counts as -1; not a rule this test is for. Builds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 10:28:11 +02:00
b6075b776f Tier and wear tests pin the tolerance, the upgrade steps and the direction of the quality term
Kills the Stryker survivors in the lines this cut touches that a quality-reading test can reach. Builds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 10:27:46 +02:00
d820e155ff GetPrice test: a lot of two parts, one above its assembly, prices as the mean of the capped parts
Kills the hand mutant that prices a lot at its best part (the single-fill sleeper could not tell max from mean). Builds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 07:34:13 +02:00
f3044718c1 Stage the drive lots with the ship deactivated: an active entity refuses unequip and equip
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 07:22:04 +02:00
349a226c72 MismatchedDrivesHoldHeading stages its two drive lots at .55 and .85
Heat now reads effective quality, which narrowed the Generated fit's drive spread below the test's 5 percent premise (probe: thermal exponents 1.227/.809 at base, 1.623/1.318 at head; NominalThrust ratio 1.0525 then 1.0436). The test fits the two Large Drives with lots staged at the low and high end of the makers' assembly range, chosen before any run, through the ship's own unequip and equip, and asserts the spread as a premise with its own message. Unverified until the Yggdrasil run.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 07:18:37 +02:00
174bdaded1 Every reader of an item's quality reads Lot.EffectiveQuality
Price, tier, equipped thermal and durability exponents, wear and the unequipped durability exponent now read the one effective quality; no reader keeps a path to the assembly alone. Tests pin each reader against a lot whose part sits below its assembly. Unverified until the Yggdrasil run.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 07:09:31 +02:00
96911ffc4b aetheria-gravity map: belt shapes (body fact B60, model sub-table, rationale, cut sequence)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 07:06:47 +02:00
db3043a5d2 Research: NPC outfitting selection and quality-for-cost mechanics, for chooseproduct-affordability and quality-paid-in-cost-or-throughput
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 07:06:23 +02:00
38198b6e00 Lot.EffectiveQuality: the item's one quality, the mean of min(part, assembly) over its fills
QualityForRole's unnamed, roleless and unfilled fallbacks read it. Builds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 07:04:15 +02:00
39aef82f47 aetheria-release map: quality readers at 70c7a497 (body facts QR1-QR5, model row, cut sequence)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 07:01:06 +02:00
70c7a49739 Merge eureka/aetheria-gravity-bodyless-node-places-children (head 49c6e3f8)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 05:52:45 +02:00
49c6e3f837 StationPlacementTests: the bodyless-node rule runs on a wide light sample of the production generator that holds standing binaries under bodies
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 04:22:31 +02:00
5a63094035 ZoneGenerator.Place: a folded family joins a bodyless target as a child and AtLeast reads its target through OneBody; direct-Place theories pin OneBody, the heavy member, the margin and the ringless settle
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 04:22:30 +02:00
55bda75ac3 Merge faction-curated-place-names (7da0d414), --no-ff
Code from eureka/aetheria-release-faction-curated-place-names. Conflicts kept both sides: Faction holds Assembly (key 19) and Places (key 21); AetherDb dispatches assembly-from-maker, place-names and doctrine-catalog, and its usage names both; FlightTests counts the Faction wire at 22 slots (19 is Assembly, 21 is Places).

The two LFS files are the merge's resolution, replayed on the post-assembly bytes, never merged as text or by side.

GameData/Aetheria.cc, per h1's two-step replay: (1) AetherDb place-names apply built at the branch's commit 1 (740e1ee7, merged onto the assembly merge so key 19 survives), run on the post-assembly catalog: purges the Markov NameFile records while their type exists and authors 15 place lists; (2) place-names apply built at this merged tree, run on step 1's output: authors Miss Terri's list (1 list). Each step's second apply said nothing to do and left the bytes identical; the whole chain run twice from the assembly catalog gave bytes equal except the record write timestamps (masked, both hash alike). Decoded independently with cultcache-py: 16 factions, key 19 Assembly filled on the 15 makers (Ewan Hart has none), key 21 Places 20 names on all 16, key 9 nil on all, no namefile type among the 321 records.

Assets/Resources/Settings.asset: the post-assembly file (Rarity values removed) with the four NameGeneratorSettings lines removed (NameGeneratorSettings, MinLength 5, MaxLength 10, Order 3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 02:16:57 +02:00
430eb307a3 Merge assembly-from-maker (a899432d), --no-ff
Code from eureka/aetheria-release-assembly-from-maker-r2. The two LFS files are not merged as text or by side; they are the merge's resolution, made as follows.

GameData/Aetheria.cc: master's catalog (0f5def17's, sha256 a4ff0eff...ebee9) is unchanged by master since, so the branch's blob was checked against the replay. AetherDb assembly-from-maker apply, built from this merged tree, run on master's catalog twice from fresh copies and once more on its own output: 15 factions written; the second pass said No changes and left the bytes identical. The replayed bytes equal the branch's blob (sha256 2b18ff7f...90e... ) except for the 333 record write timestamps, each of which a fresh write restamps (masked to a constant, both hash alike). The branch's blob, which h5's full suite tested, is taken.

Assets/Resources/Settings.asset: master's file (b004d902..., which dropped AsteroidVerticalOffset) with the branch's Rarity cleanup replayed: the five Rarity values (1, 0.5, 0.15, 0.05, 0.01) removed, nothing else touched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 02:13:23 +02:00
408b1c45bd ZoneGenerator.Place: a bodyless node is one body of its family's mass wherever a mass is read, and ChildrenWithinReachTests widens to seeds 1-30
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 02:01:09 +02:00
cacd926182 ZoneGenerator.Place: Settle recurses into each placed child; the queue, its guard and the settled set are deleted
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 01:59:06 +02:00
827f11c6df aetheria-gravity map: B59 bodyless binary members left at distance 0; why Settle recurses (spec bodyless-node-places-children.r1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 01:56:43 +02:00
6fcdb9cfea Merge eureka/aetheria-gravity-asteroid-floater-layer (head 01d3058d)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 01:26:03 +02:00
f30f15cd41 Merge eureka/aetheria-release-studio-underlay-fits-hull (head 1030e719)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 01:25:58 +02:00
b9e16872c7 Merge eureka/aetheria-gravity-ring-roche-final-heavy (head 390a5958)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 01:25:54 +02:00
a899432d46 Tests: the AI streams in both directions; sampling and admitting take nothing from the item stream, and each pilot's circuit is its own
Pins the survivors soul-ai-consumes-item-stream and soul-patrol-same-for-every-pilot of verdict s1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:54:01 +02:00
997172af17 Tests: the assembly derive rule at fifteen part means and across products, not at the fixtures' two
Pins the survivors soul-derive-quadratic-agrees-on-fixtures and soul-derive-floored-mean of verdict s1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:54:00 +02:00
05417f832e Tests: the role ceiling has no floor and no tolerance band; a product's own assembly replaces the maker's spread too
Pins the survivors soul-cap-assembly-floored, soul-cap-tolerance-band and soul-product-override-mean-only of verdict s1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:53:56 +02:00
14d3327c19 LockOn: the hostile is ordered to close on the player; the warning is measured against the poses it was stamped from
ScenarioStage.Approach(entity, target) states a piloted entity's task as a follow task on the target, passed through Zone.Admit to the flight as the member's baseline task; it replaces Patrol(entity, orbit) and the orbit index, and RunStart's post-admit circuit overwrite. LockOn stages at .6, the quality Crossing and the other RunStart scenarios use (its brief states none), not .8 picked by outcome.

The bearing check keeps well-law r4's intent (measure against the hostile's actual pose, not the staged line) but reads the poses of the tick before the paint was first seen, which is the tick it was stamped in, with a .05 deg limit for float error; the live-pose check at .5 deg spent its budget on the paint inbox's deliberate one-tick delay. LockOnHoldsUnderOnePercentOfEachStagedInput moves hostile x, y and quality by 1% each way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:51:33 +02:00
Meta Vens
01d3058d21 BeltFloaterLayerVerify: both belts the same length, so a draw list built from the other belt is measured as wrong sizes rather than an index fault
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:34:50 +02:00
Meta Vens
b320a8cf6d BeltFloaterLayerVerify: two belts loaded through ZoneRenderer with ARPG.unity's meshes; every drawn rock's reach and top measured from the instanced matrices and the mesh's vertices
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:29:04 +02:00
Meta Vens
72e2383ea4 DrawAhead.BeltDraw keeps the belt's meshes by the belt's key; empty candidates and a zero kept count are named refusals; ZoneRenderer draws through DrawBelts and one DrawInstanced seam
The unseeded Random.Range prune leaves ZoneRenderer: the kept set is a function of the candidates, the kept count and the belt key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:23:47 +02:00
Meta Vens
f5f0634b14 DrawnPoseTests: the renderer's remaining lines are pinned whole (the draw call, the pose, the scale, the slot)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 23:16:32 +02:00
Meta Vens
c5ad4676f7 DrawAhead.BeltDraw owns the whole per-rock draw list; ZoneRenderer iterates it with no mesh choice, sort or index arithmetic
Replaces RockMeshing/AssignRockMeshes and the renderer's slot bucketing (findings scale-lookup-unpinned,
renderer-index-wiring-unpinned). The build gate message names the mesh and the field, not the radii.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 23:12:33 +02:00
Meta Vens
390a5958c9 ChildrenWithinReachTests: sample seeds 1-9 so the ring Roche defect cannot hide behind six
Seeds 7, 11 and 12 reproduce the ring-inside-Roche-margin defect on the base
generator; seeds 1-6 do not. Seed 10 shows a separate defect (a bodyless
non-ring node whose children are never placed), so the range stops at 9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:18:34 +02:00
Meta Vens
d805c32092 Research: belt shapes prior art (embedded planetoids and gaps, Trojans, Oort-like edges), for ruling belt-shapes-direction
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:17:54 +02:00
Meta Vens
7da0d414cb PlaceNamesTests: assert on names, not zone graphs; pin invalid entries, homeless factions, runs of taken names, EAC- case and same-length restore
A failed Assert.Same or Assert.All over cyclic Galaxy objects overflowed xunit's formatter and crashed the host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:14:26 +02:00
Meta Vens
ac259c6259 Place names: one validity rule (FactionPlace.IsValidName) for NamePlaces and the place-names command
NamePlaces treats an invalid entry as absent; the command refuses a table holding one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:14:22 +02:00
Meta Vens
c576e0ac18 DrawnPoseTests: four rocks over two meshes split two and two (the mesh count decides the run length)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:07:57 +02:00
Meta Vens
eb752a49c9 LockOn stages its fit at .8: the lock then holds the test's bearing check; probe files dropped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:00:42 +02:00
Meta Vens
dbe4525a02 probe (scratch 5, reverted next) 2026-10-17 21:58:59 +02:00
Meta Vens
203093bcbd probe (scratch 4) 2026-10-17 21:57:33 +02:00
Meta Vens
2d61afa40f RockMeshRadiusBuildGate: a player build fails when an AsteroidMeshes radius is unfilled or stale (finding verify-not-gated)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:44:46 +02:00
Meta Vens
8412aa0dc3 DrawAhead.RockMeshing owns the mesh ranking and each rock's scale; a mesh with no radius is refused by name (finding renderer-wiring-unpinned)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:44:43 +02:00
Meta Vens
8ee159914b ZoneGenerator: a ring is judged on its members' settled masses
The Roche-margin check on a rosette's ring read heavy.Mass before any member had
settled; each member's own Settle later folds its overflowing children into it,
so a ring could stand with a member inside its Roche margin. Settle now places
and settles the ring's members first and judges the ring on ring.Max(Mass); the
queue skips members already settled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:40:01 +02:00
Meta Vens
f0cb87277b LockOn patrols orbit 3 by index; the probe file is dropped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:38:50 +02:00
Meta Vens
5f1fa7d63b probe (scratch 3) 2026-10-17 21:37:12 +02:00
Meta Vens
244e98e463 probe (scratch 2) 2026-10-17 21:35:54 +02:00
Meta Vens
77207ec73c aetheria-gravity map: B58 ring Roche margin read before members settle (spec ring-roche-final-heavy.r1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:35:22 +02:00
Meta Vens
6216b50d6f probe (scratch) 2026-10-17 21:33:33 +02:00
Meta Vens
f7c933c08e probe (scratch, to be removed) 2026-10-17 21:30:31 +02:00
Meta Vens
bbfd816851 LockOn states its hostile's patrol order and fit quality; dice streams, patrol circuit and projector range pinned
ScenarioStage.Patrol(entity, near) states a piloted entity's patrol order (the orbit nearest a point) and RunStart applies it over the drawn circuit. LockOn stages its fit at .6 and the hostile on the orbit by the player. Tests: every SimulationDice stream draws independently of every other (they already did: the stream is mixed into the hash), the pilot's sample offset is on its own stream's grid, a patrol circuit does not move with items minted first, Crossing.ProjectorRange is read against the live range.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:28:35 +02:00
Meta Vens
27be39284f Tests: the zone gives each admitted pilot its own id
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:56:28 +02:00
Meta Vens
5a16a2751c Crossing: ProjectorRange re-measured at the staged fit, 997 m (was 1216 at 0f5def17)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:55:50 +02:00
Meta Vens
ed6463dca0 DrawnPoseTests: expected mesh runs for five distinct rocks over three meshes (the last run takes the remainder)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:55:09 +02:00
Meta Vens
e894bde146 Agent.Id is a plain setter: netstandard2.1 has no init accessors
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:47:09 +02:00
Meta Vens
72d7e25df0 AI draws from its own dice: the range-sample offset and the patrol circuit leave ItemManager.Random
Combat.SampleDps and Zone.CreateAgent's patrol circuit drew from the item stream, so minting items moved
how a pilot fights and where it patrols. Both now draw SimulationDice.For(CombatSeed, stream, Agent.Id);
Agent.Id is the zone-scoped ordinal Zone.NextAgentId gives a pilot at admission (ruling
ai-sample-offset-off-item-stream).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:46:37 +02:00
Meta Vens
64d5e4534d ARPG.unity: measured rock mesh radii (RockMeshRadius.Fill), 30 meshes, 0.958 to 2.430
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:44:28 +02:00
Meta Vens
d592ca3223 Rocks take meshes by size and are scaled by gameplay radius over mesh radius (ruling rock-meshes-by-size)
DrawAhead.AssignRockMeshes ranks rocks by unworn size onto meshes in ascending radius; RockScale divides the
gameplay radius by the mesh's measured radius; ChunkAt is the one rock position for the renderer and the reticle
(ChunkHeight folded into it). InstancedMesh.Radius is filled and verified by RockMeshRadius. Tests pin the
assignment, the scale and that the renderer and reticle call DrawAhead (finding owner-not-pinned).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:44:13 +02:00
Meta Vens
26c44020a1 Catalog: place-names apply adds Miss Terri's list
Built from a5a81993 on the previous commit's catalog; a second apply printed
'nothing to do'. The merge pass replays the command twice: built at commit 1
(with the purge leg) on master's catalog, then built at the tip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:36:48 +02:00
Meta Vens
a5a8199321 Place names: Miss Terri's gets a draft list (ruling miss-terris-improvised-places)
Real Australian place names, Indigenous-derived and English, in the old
Australia file's spirit; a draft Emily may replace. The test that pinned her as
designation-only now clears her list in memory to prove the fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:35:37 +02:00
Meta Vens
2891b9a807 Tests: the drawn assembly clamp, Resolve at a stated quality, and the staged hull
Hand mutants of the previous head survived in three places: the clamp on a drawn assembly, Loadouts.Resolve ignoring a stated quality for a product or an unbranded design, and Materialize not passing it to the hull. Each now has a test through the production path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:18:09 +02:00
Meta Vens
1eb828d53a PlaceNamesTests: a name another faction used is skipped, whatever its case
Mutation triage: skipping used names, and comparing them case-sensitively,
both survived because the table refuses repeats before a galaxy sees them. A
list that did not come through the table (a package's factions) can still
repeat one, so the draw rule's skip now has a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:03:27 +02:00
Meta Vens
198bf147d2 Catalog: assembly-from-maker apply (15 factions)
AetherDb assembly-from-maker apply on master's 0f5def17 catalog, after every product and faction apply: 15 makers of roled products get Assembly = part mean + .1 (capped at 1) and the mean part deviation. Ewan Hart Inc has no roled product and stays unauthored. A second apply reports No changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:57:54 +02:00
Meta Vens
0367f9d1fd Fused Rounds stages its shooter; FlightTests counts the Faction wire slots at 20
FusedRounds.StagedQuality (.6) states the shooter's quality so the Cluster Cannon's damage in TheDirectWeaponKillsTheFragileHullWhileASmokeRoundIsStillInTheAir does not ride the item stream. The Faction wire now has 20 slots (Assembly at 19); the Doctrine-at-17 rule is unchanged. LockOn is not staged here: see the question raised on the spec.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:57:39 +02:00
Meta Vens
7bd11f882b FlightTests: Faction's wire array is 22 slots long, repinned from 18
Places rides at key 21, so the serialized Faction array grows from 18 slots to
22 (keys 18-20 empty). Slots 16 and 17 are unchanged. Base value 18, new value
22, reason: the new member; no zone content is involved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:35:05 +02:00
Meta Vens
9481e541ca PlaceNamesTests: a home zone shared by two factions takes its owner's capital
The prelude puts the quest faction's home on the buffer faction's, and a zone
has one owner (the nearest home in faction order), so the first full run showed
Adrasteia's home zone named Ishtar Studios, Lucent's capital. The rule stands;
the test said the wrong thing about the shared home.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:35:04 +02:00
Meta Vens
1d689adfa6 Delete the Markov name generator, its data and settings, NameFile and GeonameFile
The NameFile records went with commit 2's apply, so the type, Faction key 9
(left as a retired-key comment), NameGeneratorSettings and its parameter on
Galaxy and GalaxyStage, the Settings.asset block, the Name Tools editor
window, the Geonames resources, the purge leg of place-names and the
minor-power and Pirates commands' name-file lookups all go. The Pirates
missing-name-file test pins a retired rule and goes with it. Factions shows a
places count where the geonames column was. Unity compile owed at merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:26:15 +02:00
Meta Vens
0a6bbdde13 Catalog: place-names apply on master's catalog (15 place lists, Markov data purged)
Built from 740e1ee7 with AetherDb place-names apply on 0f5def17's catalog; a
second apply printed 'nothing to do'. The merge pass replays the command, not
this blob.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:25:47 +02:00
Meta Vens
740e1ee781 Place names: Faction.Places, Galaxy.NamePlaces and the place-names command
Each faction names the zones it owns from its own curated list (ruling
faction-curated-place-names): the home zone takes Places[0], the rest follow
in distance order, a spent or absent list falls back to unique catalogue
designations. Galaxy.NamePlaces replaces the Markov GenerateNames body. The
place-names command authors the 15-row table and, until commit 3, purges the
NameFile records and GeonameFile links while their type still exists.

The shipped catalog carries no Places yet (commit 2), so the tests that read
shipped lists fail at this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:10:17 +02:00
Meta Vens
5b49a2bbf6 AssemblyFromMakerCommand: restore the \n escapes in its two WriteLine strings
The previous commit (30597b0e) wrote real line breaks inside two interpolated strings, so tools/AetherDb did not compile at it. Aetheria.Shared and the test project do not reference the tool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:07:12 +02:00
Meta Vens
1030e7198a SchematicUnderlay tests: the bounds extremes sit in different lines
A mutant fitting to the first line only survived because every other point lay inside it. Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:04:44 +02:00
Meta Vens
7b2a6d064d Staged fits: a scenario states the quality of a fit its checks rest on
ScenarioStage.Fit(hull, quality, slots) mints every lot of the fit at the stated quality through Loadouts.Materialize/Resolve, so Crossing's fighter and EvasionTermTests' hand-fitted Longinus and Djinni do not ride the item stream. No seed changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:03:20 +02:00
Meta Vens
30597b0e45 Assembly is an AssemblyDistribution: Faction key 19, optional per product, stated-quality mint
Replaces h1's AssemblyMean/AssemblyDeviation floats with one Faction.Assembly (key 19, null = not authored) and adds FactionProductData.Assembly (key 6, null = its maker's). CreateLot(product) draws from product.Assembly, else the maker's, else AssemblyDistribution defaults; CreateLot(product, quality) mints the lot and every fill at a stated quality and draws nothing. assembly-from-maker now seeds part mean + .1 and writes only a null Assembly.

The shipped-catalog test is red until the catalog commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:03:12 +02:00
Meta Vens
f948992d94 aetheria-release map: faction curated place names at 0f5def17 (probes P1-P7, draw rule, model rows; spec faction-curated-place-names.r2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:00:04 +02:00
Meta Vens
57952dbfb7 Settings.asset: drop the five Rarity values; RarityTier is display bands only
The type lost its Rarity field with the roll; Unity would otherwise keep serializing orphan values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:55:53 +02:00
Meta Vens
d23038f53c Assembly is the maker's: re-land of h1 on 0f5def17 (net diff of ae300c6a..5187a59d)
Applies the net diff of eureka/aetheria-release-assembly-from-maker (5187a59d) without its catalog blob and RunStartTests residue, git apply -3, overlaps resolved on master's side. 066fb581's message ('git log --oneline -1') was a heredoc slip; the old branch stays as it is. Drops the Rarity = fixture member from the tests master added since ae300c6a. Faction keys 19-20 here are h1's; the next commit reshapes them to one Assembly at 19.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:55:16 +02:00
Meta Vens
ed40d26dd0 aetheria-release map: assembly-from-maker r2/r3 at 0f5def17 (probes AFM1-AFM5, model rows, quality-readers line)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:52:17 +02:00
Meta Vens
f69c997a85 Studio: HullUnderlay draws the texture or the hull's line art under both cell grids
The hull Shape grid and the footprint painter draw the underlay through HullUnderlay.Draw; the inline texture draw moves into it. Editor assembly: Unity compile not yet run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:47:52 +02:00
Meta Vens
59cbdd1f3d SchematicUnderlay.Fit: the line art stretched onto the hull Shape's Width x Height
Engine-free rule in ServerShared with five behaviour tests. Builds (ServerShared and tests only; Editor wiring follows in the next commit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:47:35 +02:00
Meta Vens
5283d4de57 aetheria-release map: Studio underlay fits the hull's Shape bounds (spec studio-underlay-fits-hull.r1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:39:19 +02:00
Meta Vens
8074d724a5 ChunkHeight comment names what the renderer does with pose.w
Comment only. Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:38:58 +02:00
Meta Vens
72ed5cc15f ChunkHeight puts a rock's top on the floater layer: its centre is a drawn radius below (ruling asteroid-tops-at-layer)
DrawAhead.ChunkHeight subtracts pose.w, the radius the renderer scales the rock by; reticle and renderer both read it. DrawnPoseTests pins top == layer at radii 2..9. Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:34:20 +02:00
Meta Vens
da2a7cc56a Merge origin/master into asteroid-floater-layer 2026-10-17 18:25:01 +02:00
Meta Vens
0f5def1794 Catalog: replay minor-power-factions, pirates-faction, product-lines minor-powers, product-lines gear on master's catalog
Regenerated through AetherDb on the merged tree, never a branch's blob (follow-ups catalog-blob-merge-order, catalog-merge-replay-order). Second pass of all four is a byte no-op. Blob sha256 a4ff0effc5b80bbd4edea4c9f3855555d622e6939edfb720d08c4603b61ebee9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 17:07:10 +02:00
Meta Vens
f123cdb735 Merge gear-lines (eureka/aetheria-release-gear-lines, a1e81613)
Code only. The branch's GameData/Aetheria.cc is not taken; the catalog is regenerated by replay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 17:02:03 +02:00
Meta Vens
c486b06d04 Merge minor-power-factions (eureka/aetheria-release-minor-power-factions, 7d1106a9)
Code only: Aya, Megiddo and Forge as ordinary factions, the Pirates allegiance top-up, ten minor-power products. Also carries the ItemData.ResolveStatField fix: the static stat-field cache is a ConcurrentDictionary (Soul judged ItemData the right owner), because parallel catalog opens raced on it. The branch's GameData/Aetheria.cc is not taken; the catalog is regenerated by replay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 17:01:16 +02:00
Meta Vens
7d1106a907 Stat-field resolution cache is concurrent: parallel catalog opens corrupted the shared Dictionary
Two test classes opening the catalog at once threw 'concurrent update' from
ValidateStatModifiers; the cache is static.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:25:49 +02:00
Meta Vens
cfba303afa Test: gear draw assertion back to Assert.Contains
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:23:48 +02:00
Meta Vens
16eef88455 Test: gear draw failure names the weights
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:22:40 +02:00
Meta Vens
51fc64de31 Tests: Pirates allegiance count is the other factions' count, not 12; gear draw seeds spread by a hash
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:22:01 +02:00
Meta Vens
a1e81613e6 Tests: the weapon and gear command tests share one xunit collection
Run side by side they raced on shared catalog state (concurrent-collection error in the mutation job's baseline at c8deb15b). Builds: yes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:18:41 +02:00
Meta Vens
c8deb15b38 Tests: null-safe role filter in the gear invariant; scratch roleless design drops its role-naming behaviors
The first full Yggdrasil run at 446c1325 failed these two tests (a design with null Roles; the store refusing Radiator's behaviors once its roles were cleared). Builds: yes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:14:06 +02:00
Meta Vens
1191aa1423 Test: a minor power's own allegiance names every faction but the Pirates
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:02:14 +02:00
Meta Vens
446c13252f Catalog: product-lines gear apply (22 gear products)
Output of 'product-lines gear apply' on master's catalog, run on Yggdrasil; second apply reports nothing to do. Builds: yes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:01:52 +02:00
Meta Vens
d91fa1a6e9 Catalog: minor-power-factions apply, pirates-faction apply, product-lines minor-powers apply (alone)
Three factions, 33 allegiance gains, the Pirates topped up to 1.0, ten products;
a second apply of all three writes nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:01:09 +02:00
Meta Vens
f54dada786 Tests: minor powers are ordinary factions, command idempotence and conflict, Pirates top-up, shipped minor-powers rows
These fail until the catalog commit lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:59:01 +02:00
Meta Vens
7e857cd6e5 Fix the Pirates top-up's two string literals (ac0b39f8 does not build)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:58:49 +02:00
Meta Vens
fbd8ab0915 product-lines minor-powers: ten products of Aya, Megiddo and Forge
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:54:34 +02:00
Meta Vens
ac0b39f84e minor-power-factions command, MakerProfiles rows, Pirates allegiance top-up (ported from 57083412)
MinorPowerFactionsCommand authors Aya Collective, Megiddo and Orbital Forge as
ordinary factions plus the gains on other factions' Allegiance. pirates-faction on
an existing record now tops up allegiance under its key. Unbuilt here; verified at
the head on Yggdrasil.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:54:31 +02:00
Meta Vens
3e5e794bcc Tests: gear line rows, two-maker gear invariant, per-line command tests, cargo exemption
Builds: not yet run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:54:23 +02:00
Meta Vens
064d19a1d3 AetherDb: product-lines gear (22 rows), cargo bays exempt from the no-roles refusal
Builds: not yet run (verified at head by Yggdrasil job).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:53:28 +02:00
Meta Vens
f66d921fec release map: gear-lines and minor-power-factions (PL10-PL13), replay order
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:50:56 +02:00
Meta Vens
7dddcc4727 Merge eureka/aetheria-gravity-floater-fixtures-wells (floater-fixtures-wells, head d6380ca8)
Origin branch lagged at c631e09f; merged the closed head d6380ca8 from the shared object store (branch hands/ag4-floater-fix, unpushed).

Spec aetheria-gravity:cut_spec:cut-floater-fixtures-wells.r1; report h1; Soul s1 holds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 15:03:43 +02:00
Meta Vens
e1ec1e89a2 Merge eureka/aetheria-release-ships-studio-footprint (ships-studio-footprint, head 13499535)
Spec aetheria-release:cut_spec:cut-ships-studio-footprint.r1; reports h1, h2; Soul s2 holds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 15:03:15 +02:00
Meta Vens
1349953500 Shape.SetLine writes through the indexer so it invalidates the caches; footprint tests pin refusal Position and a wide shape
Builds: yes (Shared-only; verified on Yggdrasil in the report).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 14:33:41 +02:00
Meta Vens
d6380ca8e0 DrawnPoseWiringVerify: the planet passes within about 8 u of both floaters; the guard names the heights it saw
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 14:28:27 +02:00
Meta Vens
417ac9c76a DrawnPoseWiringVerify: the planet passes within about 20 u of the pickup and mine, with a wider softening, so the field moves over a lead
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 14:23:17 +02:00
Meta Vens
cc243e7f7c DrawnPoseWiringVerify: bind the prefabs to the wells zone directly, no ZoneRenderer
A renderer loading a zone with a sun and a planet needs the planet prefab, the map and a GPU; the check pins the presenters' Place, so the prefabs are bound as LoadFloatingItem and LoadMine bind them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 14:14:05 +02:00
Meta Vens
b3eb6b0c37 Floater fixtures: planets placed where the field changes under the floaters; probe removed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:56:32 +02:00
Meta Vens
b708d51617 probe (temporary) 2026-10-17 13:54:38 +02:00
Meta Vens
be198593cc Floater fixtures: orbits and planets live in the run store, so the fixtures open one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:53:09 +02:00
Meta Vens
f37949f139 Studio: HardpointFootprintDrawer paints a hardpoint's footprint over its hull's cells; the hull drawer tint reads HullFootprint.Cells
Editor code; not yet compiled (Unity batch compile at the end).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:50:50 +02:00
Meta Vens
d76b27673a HullFootprint: the one footprint cell reading and edit rule (grow, trim, refuse last cell), with tests
Not yet built or run (verify job at the end).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:49:45 +02:00
Meta Vens
7a44e0c00c Shape: one dirty flag invalidates Coordinates, AllCoordinates and CenterOfMass; Resize marks dirty
Resize keeps its fill (new cells on): the hull drawer's width grow relies on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:48:35 +02:00
Meta Vens
71fa9411c6 Belt rocks and their reticle are drawn on the floater layer over the field by DrawAhead
DrawAhead.Chunk/ChunkHeight (ruling asteroids-on-floater-layer); ZoneRenderer and the
chunk reticle both read it; ChunkWorldPosition and the renderer's global are gone.
Fixes the reticle sitting off the rock by |field|. Test: AChunkIsDrawnOnTheFloaterLayerOverTheField.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:48:34 +02:00
Meta Vens
2055e18d3e Delete AsteroidVerticalOffset: setting, asset line, shader global, and the asteroid shader's field read
Rock height leaves the shader; DrawAhead owns it (next commit). Does not build alone:
ZoneRenderer and ActionGameManager still reference the deleted names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:48:29 +02:00
Meta Vens
e4f19df15e Shape caches: behavioural tests (CenterOfMass and AllCoordinates stale after an edit; Resize fill kept)
Builds; ShapeCacheTests.CenterOfMassFollowsACellCleared and ResizeRecountsEveryCoordinateRead fail at this commit by design (fixed in the next).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:48:17 +02:00
Meta Vens
c631e09fe8 Floater fixtures: the grab-point test and the wiring verify run over a moving well
PickupTests builds the zone over a sun and a planet on a rail, asserts the field differs between the ship's point and the item's and between start and end of the run, and checks each grab event at the zone time it was published. DrawnPoseWiringVerify gets a sun and a planet near the pickup and mine, requires the field under each to move with the drawn time, and checks the pickup's y as well as the mine's. Not compiled or run yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:47:52 +02:00
Meta Vens
333d95ec04 release map: ships-addon line at 1d4aa626 (footprint split out, underlay waits on question, livery and mesh-mounts holds)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:46:11 +02:00
Meta Vens
0c8b15f6c1 gravity map: B57 asteroid probe (reticle gap, rock size, thinner belts); floater-layer cut line
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:41:52 +02:00
Meta Vens
1d4aa626aa RigBrandTests: Belt is built in its own pinned arena, not eight seeds that may hold no belt
Merge fix: the children-within-reach generator leaves 37 of 90 arenas with no belt, so
Belt.Stage threw under seeds 7919*k. Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 12:18:26 +02:00
Meta Vens
6fe7f7cecb Merge eureka/aetheria-gravity-children-within-reach (children-within-reach, head cf242106)
Merged into master f937662d. No textual conflicts (auto-merged: ActionGameManager.cs,
Belt.cs, LockOn.cs, RunStartTests.cs, ScenarioTests.cs, AetherDb/Program.cs).
GameData/Aetheria.cc untouched by the merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:52:44 +02:00
Meta Vens
cf242106fa Tests: every turret on a read side clears the base body by its extent and reach
The 0.8 tail-extent mutant survived the stop checks alone: it places turrets inside the
distance, which only a placed turret's own clearance shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:30:03 +02:00
Meta Vens
a54707d1d8 Tests: drop the tail diagnostics
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:10:25 +02:00
Meta Vens
c345b303de Tests: tail test leaves out stations whose base family holds a belt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:09:03 +02:00
Meta Vens
f881a3bdfe Tests: tail test reads stations whose turrets do not wrap the ring
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:07:54 +02:00
Meta Vens
7db87cfa88 Tests: the entrance fallback reads its root's extent from the bodies alone; tail diagnostics
The zone's well tree counts the station's own rail in its root's family, so the extent is rebuilt
from the generated bodies. Carries a temporary DIAG line in ATurretTailStopsAtItsBaseBody.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:02:51 +02:00
Meta Vens
c39a73ca12 Tests: belts fill their slots, five placement rules pinned by forced fixtures
BeltsFillTheirSlots (midpoint, slot bound, no zero-width belt, width distribution printed),
ANarrowAuthoredBeltKeepsItsWidth, ASoleRootRingSitsAtThreeEighthsOfItsRadius,
ARingMembersChildrenStayWithinAFifthOfTheChord, ATurretTailStopsAtItsBaseBody and
ATutorialEntranceWithNoCandidateOrbitsItsBodyRootOutsideItsExtent. Not yet run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:00:21 +02:00
Meta Vens
b13aa67efd Place: a belt fills its slot between its neighbours' spacing, the Roche margin and the window
Replaces the symmetric half-width about the chain position (which collapsed a belt at the end of its
slot to a line, finding belts-collapse-to-a-line) with the slot interval: the belt sits at the slot's
midpoint and its half width is min(authored width, slot) / 2 (ruling belt-fills-its-slot). The chain
still places belts as massless points, so no fold count moves. Builds; tests follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:00:04 +02:00
Meta Vens
6214741ab5 Restore LiberationSans SDF - Fallback.asset to its 09a7f189 LFS pointer
Unity re-serialised the generated atlas into the working tree and it was committed
in c727141a; the file is back to the pointer (oid f1eead45, size 536050). Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 08:45:41 +02:00
Meta Vens
f937662d75 Merge eureka/aetheria-ew-ew-belief-allegiance (ew-belief-allegiance, head ac5cbcc5)
Spec aetheria-ew:cut_spec:cut-ew-belief-allegiance.r2; reports h1, h2; Soul s2 holds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 08:44:50 +02:00
Meta Vens
ed39d9460d gravity map: B56 belt fills its slot (r4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 08:41:39 +02:00
Meta Vens
ac5cbcc51b EW 3 fix: a contact is hostile on the observer's own permission, with tests for trespass through Zone.Update
Entity.IsHostileTo read other.PresencePermitted in its claimed-owner term where the spec and map S2
say the observer's own, so a trespasser never held the owner's patrol hostile. Tests: the trespasser
(player or not) holds the patrol hostile and its guns are not safed, a permitted neutral does not,
a ship tracked in open space turns hostile on entering the Critical area through ticks (pins the
per-tick stance refresh), and a third party does not treat a trespasser as hostile (pins the owner gate).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 08:10:29 +02:00
Meta Vens
cbb4526070 EW 3 tests: a live quiet subject's reveal stays in the book, so the belief ends by age, not by the book dropping
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 07:32:45 +02:00
Meta Vens
6460eed688 Research: quality combining rule meta (rule families simulated over the catalog), for question quality-combining-rule-meta
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 07:19:47 +02:00
Meta Vens
5a7ab7944b EW 3 tests: add the Forbid helper the previous commit referenced (8a779b3a does not build)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 07:10:54 +02:00
Meta Vens
1ddd1bef31 EW 3 tests: reciprocity test comment no longer names the deleted recursive branch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 07:05:45 +02:00
Meta Vens
8a779b3aa8 EW 3 tests: forbid by direct PresencePermitted (Update re-derives the security level); listed-once after a tick
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 07:04:46 +02:00
Meta Vens
49423b61fe EW 3: allegiance is the observer's belief; telemetry with the track, grudges from acts, one stance writer
Cuts WatchForGrudge, PerceivedStanceOf, the recursive IsHostileTo and the inline compare-and-write; adds _telemetry (ReceiveTelemetry at track entry), Provoke (Track paint, committed shot), RefreshStance, BelievesTrespassing. Not yet verified: unbuilt, tests unrun.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 06:45:51 +02:00
Meta Vens
3034721958 ew map: B2 corrected
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 06:35:03 +02:00
Meta Vens
afae0c6b97 ew map: cut 3 probes at ae300c6a (B1-B3, S1-S6); model page for allegiance belief at track entry
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 06:25:48 +02:00
Meta Vens
1f83d0743b Tests: folding takes the lightest family
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 06:08:02 +02:00
Meta Vens
4d6c307d0c StationPlacementTests: each generated galaxy has its own directory
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 06:07:09 +02:00
Meta Vens
b305c66c6c Tests: belts keep a spacing from their neighbours; story turrets at 3, 5 and 9; a station never sits on a binary's bodyless node
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:56:58 +02:00
Meta Vens
f5329e50a2 RestoredHullsTests: the entrance throw is reached by a single-root zone (several roots give the station a root to orbit)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:55:53 +02:00
Meta Vens
ae300c6a84 release map: maker-only faction r3 at aa12cbac
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:47:51 +02:00
Meta Vens
c727141a2f Scenarios: Belt and Lock On take the seeds that hold their conditions under the new placement
Belt: seed 86 (484 rocks, scanned over seeds 1-90). Lock On: seed 1 (the staged hostile settles below the detection threshold).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:40:34 +02:00
Meta Vens
3ef0ac21eb RunStartTests: the belt race takes the first arena that has a belt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:31:08 +02:00
Meta Vens
2111174adf Stations: a candidate rail clears each massive sibling's extent by the largest station or turret reach
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:27:47 +02:00
Meta Vens
513d0c4393 Place: ring merge decided on the heavy member's final mass; a bodyless node keeps a cusp as the one body it would become; tests follow
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:25:26 +02:00
Meta Vens
c3d7d077e6 Tests: compile fix
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:22:57 +02:00
Meta Vens
ed9f9930dd Place: a sole root's ring also folds when it cannot keep its heavy member outside the Roche margin; the entrance station falls back to the heaviest root of childless roots; tests follow
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:21:11 +02:00
Meta Vens
aa12cbac8b Catalog: replay verse-grammar apply on the merged catalog (faction-play-1 adds the follow verb)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:13:31 +02:00
Meta Vens
0dc7e4bae3 Tests: compile fixes (does not isolate a build until this commit)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:13:03 +02:00
Meta Vens
2409eed0dc Tests: children within reach, subzone isolation, station and turret clearance
ChildrenWithinReachTests reads the production generator's zones (seeds 1-6)
through the Zone and checks each Place rule; SubzonesAreIsolated prints its
pair and orbit counts. StationPlacementTests: a station's source is a body (the
tutorial root-node fallback excepted), turrets clear their base's extent, no
station or turret sits on an unrelated body over 64 instants. Not yet run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:12:17 +02:00
Meta Vens
e83f9a2ed1 Merge eureka/aetheria-release-minimap-asteroids-setting (cut-cut-minimap-asteroids-setting r1, h1) into master
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 04:58:22 +02:00
Meta Vens
84337de569 ZoneGenerator.Place owns every generated distance, fold and belt width
PlanetSafetyRadius, ChildDistanceMinimum/Maximum, ExpandSolar's distance series
and the window filters are deleted. Place (ported from replace.py, root window
from rootReach per root-window-floor) lays children inside-out, folds the
lightest family while the outermost overflows, stretches to the window, merges
rings that cannot keep their heavy member outside its Roche margin and sets
belt half widths. Stations pick well-bearing, sibling-clear off-ring bodies;
the tutorial entrance falls back to an orbit round a root node; turrets stop a
side when within the base body's extent. GenerateZone/GenerateArena take
PlanetSettings. Not yet built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 04:55:57 +02:00
Meta Vens
781607763e WellTree: SubzoneReach is one public static; the strengthless-orbit mean rule is deleted
The nearest-anchor rule (Reach x distance to the nearest other root) moves to
WellTree.SubzoneReach(anchors, root) so the generator spells it once; the
instance method returns max(it, floor). PullAround no longer gives a
strengthless orbit on a ring a period of its own (binaries-orbited-from-afar);
its test goes with it.

Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 04:55:48 +02:00
Meta Vens
d7f30ed165 Merge eureka/aetheria-release-faction-play-1 (cut-faction-play-1 r3, h6) into master
Catalog GameData/Aetheria.cc regenerated by replaying doctrine-catalog apply on the merged catalog, not taken from either branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 04:50:24 +02:00
Meta Vens
6179846249 Merge eureka/aetheria-release-product-lines-weapons (cut-product-lines-weapons r5, h4) into master
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 04:48:14 +02:00
Meta Vens
1bb17feabc OldPlayerSettingsLoad: write the old key 2 as 0.25f
The previous commit changed only the assertion; the hand-written array still
wrote the default 1f, which loads the same with key 2 dropped. Builds; not yet run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 04:40:48 +02:00
Meta Vens
dc0b1e2cfe OldPlayerSettingsLoad: old key 2 carries a non-default value
A default 1f would load the same with key 2 dropped, so the test could not
fail when the old array lost it. Builds; not yet run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 04:37:13 +02:00
Meta Vens
b70372963a Delete the minimap asteroids setting; minimap belts always draw
Ruling minimap-asteroids-setting-deleted. PlayerGraphicsSettings key 1 stays
retired so old saves load; OldPlayerSettingsLoad now writes the old 3-element
array by hand. MapPresenterSmoke asserts a belt draws with the map screen
closed. Not built or tested yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 04:36:13 +02:00
Meta Vens
4e620671dd gravity map: B55 root-window isolation measurement; pointers to children-within-reach r3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 04:32:28 +02:00
Meta Vens
0f2fc859de ConsumableAiTests: the following agent leaves combat before it is set to follow
The agent starts in combat; two zone ticks with nothing sensed return it to patrol, as the patrol test does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 04:03:08 +02:00
Meta Vens
199f1b1b94 ConsumableAiTests: the following agent clears the Ship's VisibleEnemies
The previous commit does not build: VisibleEnemies is on the Ship, not the Agent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 03:51:43 +02:00
Meta Vens
09a7f18985 Merge eureka/aetheria-gravity-well-law (cut well-law; Soul verdicts s1-s4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 03:49:59 +02:00
Meta Vens
843acb5341 faction-play-1: pin the vent in FollowState, pins to 10%, drop the dead LockAlarm
Combat.cs loses its unused LockAlarm constant. ConsumableAiTests gains a following agent that vents. FlightTests adds
follow-velocity, pull and compliance-entry cases a tenth either side of their thresholds and a hail member that is
nearest only with z halved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 03:49:37 +02:00
Meta Vens
7444f4a0c9 ProductChoiceTests: the nearer-maker bounds tight enough to fail a doubled distance penalty
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 03:30:50 +02:00
Meta Vens
617e602208 RigBrandTests: Arcs' turret is a fitted hull
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 02:53:34 +02:00
Meta Vens
d4cf8c8244 RigBrandTests: a ship's cargo bays count as fitted gear
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 02:51:08 +02:00
Meta Vens
86a498c566 RigBrandTests: a pin names its brand only; the design is the product's own
Not yet run on its own; the previous commit's table named designs by hand and Cargo Bay did not match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 02:48:47 +02:00
Meta Vens
82362da9d5 LockOnSituation: the warning is measured against the hostile's live bearing at the first lock, limit 0.5 deg
The staged line stopped being the hostile's line once consumable-ai r3 gave it a flanking approach. The message records the drift from the staged line. LockOn.cs's 5-degree staged-match claim is removed. Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 02:47:53 +02:00
Meta Vens
94570ca0be Tests: zone-weighted and zero-weight product draws pinned; every scenario brand pin guarded under eight seeds
ProductChoiceTests adopts Soul's probes: a nearer maker is favored (non-null zone), an allegiance weighs the draw, and an all-zero
weight draws uniformly. RigBrandTests becomes a theory over every scenario whose fits name products: each pinned design must come
out as exactly its brand on every fitted ship under eight stage seeds.

Not yet run: the commit is unverified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 02:41:44 +02:00
Meta Vens
8f20cd3664 Merge map-presenter (aetheria-release cut map-presenter r1; closing verdicts s1 and s2) 2026-10-17 01:17:41 +02:00
Meta Vens
39c7508e38 Merge boss-gate (aetheria-release cut boss-gate r2; closing verdict s3) 2026-10-17 01:17:38 +02:00
Meta Vens
5a96035563 ConsumableAiTests: the refit waits for the new Minion to reach combat
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 01:13:28 +02:00
Meta Vens
f11ec4f676 ConsumableAiTests: the refit tick count reaches combat from the new Minion's patrol start
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 01:11:15 +02:00
Meta Vens
4920b2c9c0 MapPresenterSmoke pins icon axes and heading; capture proves deep-well pixels; belt benchmarks at 1000/2250/4000
Smoke asserts each icon's x and z against the sim position (non-symmetric, ships ride at y 50 and -90) and the
yaw from the forward heading with a near-vertical rolled hull whose eulerAngles.y differs. MapPresenterCapture renders the
scene's Map Overlays and Map Tint cameras to pixels. MapBeltPerf measures base (MapBeltPerfBase, verbatim
AsteroidBeltUI) and head as median/IQR of 21 runs. All build.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 01:02:39 +02:00
Meta Vens
6985ece70b ConsumableAiTests: the refitted fixture agent starts on patrol and takes one more tick to reach combat
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 00:56:20 +02:00
Meta Vens
0e0afda03d Merge eureka/aetheria-release-ship-render-fixes-r3 (cut cut-ship-render-fixes, verdicts s2 and s3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 00:53:39 +02:00
Meta Vens
6f31375def Flight: a member's friendly override keeps it from following a flightmate's track; consumable-ai fixture orbit built on the zone
The union picture stays the flight's; the per-member override is read where the member chooses its bearing. A first
version dropped the track from the whole picture and silenced a hostile unaffiliated flightmate (SmokeRoundsStepped
AiFusedDiscipline).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 00:40:59 +02:00
Meta Vens
98337213a4 RigBrandTests: each rig is staged under eight seeds, so a pin is told from a lucky draw
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 00:28:00 +02:00
Meta Vens
ca050c9b05 WellLawTests: the belt fixture lists its outermost asteroid in the middle (10, 87, 43)
An extent read from the first or the last asteroid now fails the belt test (finding s3.belt-fixture-first-read). Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 00:26:47 +02:00
Meta Vens
b08f7d80ae FlightTests: the friendly-override pin builds in both orders; compile fixes for the s4 pins
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 00:23:01 +02:00
Meta Vens
2bb08a91ee Flight: a track any member made friendly is out of the picture; pins for the s4 findings
The flight builds the picture, so it is where per-member friendly overrides meet; the track is dropped there. New
pins: a tuned doctrine survives doctrine-catalog apply, the follower's velocity match and pull at scale, compliance on
a diagonal, the hail speaker and engaged pick off-axis. Not built or run yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 00:21:43 +02:00
Meta Vens
0e40a66592 Vent on a Track paint in every agent state: Agent.Update owns the vent, CombatState keeps the overdrive
Ruling vent-in-any-state. The vent decision (own Track paint above .5, one of a kind through TryActivateConsumable)
moves out of CombatState into Agent.Update, which every state runs; CombatState's overdrive asks Agent.TrackAlarm so a
venting tick still does not overdrive. ConsumableAiTests: AVentDoesNotNeedAnyEnemyInSight is master's premise again
(empty VisibleEnemies), a patrolling agent has its own test, and the fixture zone has one orbit so an unengaged pilot
patrols it. Not built or run yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 00:21:40 +02:00
Meta Vens
d135c77e0e Merge origin/master into faction-play-1 2026-10-17 00:13:54 +02:00
Meta Vens
66aee3cf0e ProductChoiceTests: an outfitter that reaches no maker gets no product
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 00:04:35 +02:00
Meta Vens
4694366d98 Map presenter: ShowAsteroidsInMinimap stays (head's belt cost misses 0.1 ms); belt upload via SubUpdates; settings key restored
The setting gates belts on the minimap again, through MapPresenter.BeltsOnMinimap; the map screen always draws them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:54:45 +02:00
Meta Vens
07f0037148 MapBeltPerf: belt cost per frame in CPU ms and managed bytes; script metas
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:41:39 +02:00
Meta Vens
350c40236b MapPresenterSmoke: deep planet, yaw-only tilt, one draw per kind, one belt draw, fade; capability smoke binds an icon
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:40:15 +02:00
Meta Vens
35406653b6 GravityField: the band potential's promise is the float32 bound measured, 1.8e-6; the sweep covers the taus above the seam
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:30:31 +02:00
Meta Vens
281745394b Map presenter: one owner places every map icon in 2D, belts become one instanced draw (not yet compiled)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:26:26 +02:00
Meta Vens
538333a4b4 WellLawTests: pin the Gamma half of the fade start and the zone rim radius; the belt fixture is unsorted; SubzoneReach says what superposed-inside needs
Not yet run: pushed once the filtered suite passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:22:16 +02:00
Meta Vens
75df9a8421 RemovePackage: drop the dot-name clause the trailing-dot rule already covers
'.', '..' and '...' all end in a dot; one rule decides, not two.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:16:05 +02:00
Meta Vens
e57eedea71 RemovePackage test: the missing-name case must not create its directory
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:59:36 +02:00
Meta Vens
9315a0019d FireControlPerWeaponTests: the turret threshold is pinned half a hundredth either side of the price
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:57:44 +02:00
Meta Vens
afc570affd ConsumableAiTests: the refitted agent senses the prey again
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:52:29 +02:00
Meta Vens
b76e212d60 RemovePackage: containment under Mods and RemovedMods, name round-trip, no links; Remove dialog keeps other buttons
Refuses names the filesystem rewrites (dot names, trailing dot or space, device names, stream colon), anything that does not
resolve to a strict child of the mods folder under the same name, and link packages. A fault with no known package names
(all mods), so no Remove button. A removal drops only its own button and the dialog returns to the rest.
Unity compile not yet run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:49:44 +02:00
Meta Vens
0f8bb6d9da Release map: map presenter (owner audit, 2D icons, instanced belts, the asteroid setting's cost)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:48:18 +02:00
Meta Vens
ff34648371 ConsumableAiTests: the refit re-admits the agent as a pilot (Zone.Remove drops the agent on this branch)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:42:46 +02:00
Meta Vens
00956ce329 Tests: a loadout may reference products; the unavailable-product test fails one design; distribution tests spread their seeds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:34:34 +02:00
Meta Vens
1519aa8f44 Tests: consumable-ai fixtures let the flight target the prey (the flight is the only Target writer); standoff range, presence and wire-slot test fixes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:24:33 +02:00
Meta Vens
94ab2d3f50 ProductChoiceTests: the draw follows the outfitter's preference
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:17:35 +02:00
Meta Vens
4609f148a2 Tests: product choice (outfitter, seed, pin, key shuffle, capture, wrong-design chooser, wire keys); rigs keep brands; key-order test removed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:16:15 +02:00
Meta Vens
7bbaa26675 ScenarioStage: names are designs or products, one Resolve path with the generator's chooser; rigs pin today's brands; Crossing back to 1216
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:10:58 +02:00
Meta Vens
3a081160b4 LoadoutGenerator: ChooseProduct (weighted seeded draw, name-ordered) and ProductWeight; generation shares the weight
Builds with commit 1 only once the callers move (commit 3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:10:13 +02:00
Meta Vens
40e2ada37e Loadout: slots and hull may name a product; one Resolve; Capture records the brand; ProductsInKeyOrder deleted
Does not build alone: Materialize's callers move in later commits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:10:03 +02:00
Meta Vens
b5c4fd0b8e Release map: PL7-PL9 product choice in loadouts (pins, ChooseProduct owner, key order retired)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:08:19 +02:00
Meta Vens
7518c0f3e8 DoctrineCatalog: fix the nothing-to-author line (previous commit does not build)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:07:49 +02:00
Meta Vens
9376e5fcc0 WellLawTests: the three-subzone fixture puts the big sun off the origin and pins each subzone's reach
The first fixture hid the big sun's weight in the barycentre (it stood at the origin) and tested reach with two roots only, where nearest and farthest coincide.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:07:11 +02:00
Meta Vens
46a2296ce8 Tests: rig fixture gives map and collider nodes a mesh; fix two test slips
Bind now refuses a meshless map-icon or hull-collider, so ShipRigTests' GLB
needs the meshes real packages carry. Not yet re-run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:05:53 +02:00
Meta Vens
c30764e33e faction-play-1 fixes: doctrine-catalog apply writes nothing on rerun; tests pin the unseen rule off-origin, the hail speaker in every build order, wire slot 17, exact-name match, scaled standoff, compliance axes, threshold boundaries, follow velocity
Does not build-verified yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:05:02 +02:00
Meta Vens
2ff48d0b45 Merge origin/master (da6fe033) into faction-play-1
Combat.cs: master's lazy optimum-range owner kept, the OptimumRange accessor beside it. FireControlPerWeaponTests: the exponent test runs the first Update before reading the optimum (master derives it there).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:01:56 +02:00
Meta Vens
794ff00541 PotentialOwnerTests: the two-sun height oracle is held to Gamma x reach, where a well is its Plummer pull whole
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:51:20 +02:00
Meta Vens
d35730dc9d ShipModPlaySmoke: exclusion at compose, ImportFault at preload, removal by RemovePackage
Phase 1 expects the map-icon sibling excluded by Compose; phase 2 installs a
sibling whose GLB lost its BIN chunk and requires ImportFault naming exactly
it; phase 3 removes it and composes again. Editor code; not yet run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:50:12 +02:00
Meta Vens
f0ec95e640 RemovePackage test: spell the backslash name as a backslash
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:49:18 +02:00
Meta Vens
65a6ce8705 MainMenu: a surfaced broken mod gets a Remove button that moves it aside
RefuseMods names every faulted or excluded package with one Remove button per
package, all through ShipModCatalog.RemovePackage; the catalog stays as this
boot opened it and the dialog says to restart. ModsPath becomes public.
Unity-only code; not built until the Starfire smoke at the end of the cut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:48:57 +02:00
Meta Vens
249beaa03f Preload faults Loading with ImportFault instead of skipping a composed hull it cannot build
The skip-and-log is deleted. A composed hull that fails to import is a fault
naming its package, not a silent drop. Unity-only code; not built until the
Starfire smoke at the end of the cut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:47:54 +02:00
Meta Vens
e04e247820 ShipModCatalog.RemovePackage: the one uninstall moves a mod aside to GameData/RemovedMods
Presence under Mods is the only enablement; removal is a move, never a delete,
and moving a package back reinstalls it. Refuses names that are not a package
directory directly under the mods root. Not yet built or run; verify runs at
the end of the cut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:47:38 +02:00
Meta Vens
f9ece91638 Boss gate test: the main galaxy places no boss in the shipped catalog, so give it one off the exit
Fixes the previous commit's Exit_open_only_in_the_exit_zone, which asserted bosses the main galaxy does not have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:39:05 +02:00
Meta Vens
3f29d803eb Bind refuses a mod Assemble would throw on: meshless map-icon and hull-collider, non-positive shield scale
Compose is the one owner of whether a mod hull is available. Bind now reads
the node scale and matrix in ReadNodeIds and refuses what Assemble refuses.
Not yet built or run; the verify job runs at the end of the cut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:37:51 +02:00
Meta Vens
6c53dac3c5 PotentialOwnerTests: the gradient check allows the height's float32 resolution on flat ground between subzones
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:34:34 +02:00
Meta Vens
65aa61811e Well-law tests follow the subzone law: two suns 600 apart are two subzones, not a superposition
WellsSuperpose is removed (its rule, roots superpose everywhere, is the one multi-root-zone-shape decides); HeightIsThePotentialOverOneK and FlowFieldTests probe inside each well's reach. ZoneWorlds gains Belt and AsteroidSize; fade and ring tolerances are relative to the Plummer pull. Not yet run on dotnet at this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:32:30 +02:00
Meta Vens
dca660e448 Boss gate: TryWin is the one way to win; equal-gap tie-break and two exit pins tested
Win() becomes TryWin(), which checks RunGoal.ExitOpen itself; the call site no longer reads it.
Pins: first-of-equal-gaps, exit-zone-only, no ship hull on offer. Not yet built or run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:18:52 +02:00
Meta Vens
250b61be2c Well law fix batch: zone node for several massive roots, belt extent, series from tau 0.4, Unity parity on the multi-root fixture
One zone node at the massive roots' centre of mass owns the only global term (SubzoneReach is the one place the subzone shape is decided: isolated-subzones). WellNode gains Extent and Anchor; a belt orbit's extent is its outermost asteroid. GravityField.SeriesFrom = 0.4 is emitted into the shader template and Gravity.cginc. Tests: the spec's WellLawTests; the old seam test is replaced by TheBandPotentialIsExactInSinglePrecision. GravityFieldParityVerify runs the multi-root fixture (second sun, moon inside the planet's Roche limit, far window): PASS, largest drawn tau 1.228, max error 0.01028 u. The dotnet suite has not been run at this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:14:58 +02:00
Meta Vens
807533d26b FlightTests: the combat-exit test removes every listing of the engaged track
The grudge lists the track in VisibleEnemies a second time (the hostility refresh adds it again), so a single Remove left it visible.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:58:26 +02:00
Meta Vens
c387b137d1 FlightTests: combat exit diagnostics
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:56:05 +02:00
Meta Vens
45ab0b9f23 Aetheria.cc: master's catalog with doctrine-catalog and verse-grammar re-applied
doctrine-catalog apply wrote four doctrines (Pirates included); verse-grammar apply added the follow verb (follow-up catalog-blob-merge-order).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:55:22 +02:00
Meta Vens
4fc225b1d5 VerseGrammarTests: the shipped catalog holds eight starter verbs; combat exit asserts the Target cleared
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:54:47 +02:00
Meta Vens
ae290654aa GameData/Aetheria.cc: product-lines weapons apply onto master's catalog (df154ffb), replayed
Master's blob (oid ed0500b4) with the twenty products and nothing else: applied once, then again with no change
(second apply: nothing to do, bytes identical). Replaces the blob whose five hulls a probe had re-encoded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:54:41 +02:00
Meta Vens
da6fe0334e Merge origin/master into suns-never-green + consumable-ai merge (docs only)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:53:39 +02:00
Meta Vens
ceb1a08edb product-lines apply lands on a catalog with older-schema records; refusal axes pinned; one maker-profile table
ExpectUnchanged compares the file's schema ids with the loaded ones, so master's blob (hulls written before
HullData lost GridOffset) made apply throw and write nothing. The batch now expects only that the keys it authors
are absent and lands onto the file as it is. Tests: older-schema replay, the design and maker match, case-insensitive
name conflict, a row name that is a design's, the dispatch word, and the sold-weapon floor replaced by "only Smoke
designs are unsold". LaunchSuppressorCatalogTests reads Program.MakerProfiles instead of spelling six profiles.

Not built or run yet; the catalog blob is regenerated in the next commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:50:47 +02:00
Meta Vens
c1c8d8d7de Gravity map: B52-B54 multi-root zone node, belt extent, series threshold 0.4; rationale for well-law r3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:48:59 +02:00
Meta Vens
e5d35352ee Merge consumable-ai r3 (cut consumable-ai; verdicts s2, s3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:36:18 +02:00
Meta Vens
2d96c3241f Merge suns-never-green (cut suns-never-green; verdicts s1, s2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:36:15 +02:00
Meta Vens
04516c72cf Faction play 1: s1/s2 fix batch (Engaged-first anchor, doctrine pins, follow verb)
Flight anchors a blind member on an Engaged track before a nearer Hailing one and keeps its anchor only while nothing outranks it. Doctrine.cs states the trespass rule as the zone owner's permission. DoctrineCatalog matches the faction by exact name and its comment names the landed Pirates record. VerseGrammarCommand gains the follow verb. Tests pin default and authored standoff, Grace against ComplySpeed, nearest hail, the Identified boundary, off-origin direction and anchor choice and switch, the unseen-member rule across geometry, combat from follow, the doctrine-catalog dry run and the turret's global hit threshold.

Not yet run: this commit's tests have not been built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:31:41 +02:00
Meta Vens
86c3a94880 Merge origin/master into faction-play-1
Conflicts: GameData/Aetheria.cc takes master's blob (doctrine-catalog and verse-grammar re-applied in a later commit, follow-up catalog-blob-merge-order); IffAndCombatTests NewShip admits through zone.Admit(ship, piloted); Program.cs keeps master's cases plus doctrine-catalog; FireControlPerWeaponTests keeps master's asserts with the branch's Minion constructor. Master-only compile sites adapted: Minion(ship, doctrine) in FireControlPerWeaponTests, LayoutAimTests, ScannerTests; AgentFires threshold in SolutionTests; Zone.Remove and VerseGrammar in FlightTests.

Not verified to build until the verify run at the end of the pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:18:54 +02:00
Meta Vens
c23390c76e CatalogRoleTests: the mine launcher's renamed brand is found by name among its sellers
Tripwire is a second seller of the Mine Launcher; the test asserted the design had a single product.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:01:52 +02:00
Meta Vens
df154ffbdd Gravity map: B50 pockets probe (drawn extent and basin share, C0/C1/C4), B51 binary period shear and C4 merge counts; rosette-equilibria line
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:52:30 +02:00
Meta Vens
e5962d960d Crossing: the Lightning Projector's settled Range follows the product a design's first key gives
The staged fit names designs, and the first available product of the design by key builds it. With the Static Shock line in the catalog that product is NiteLife's, whose settled Range is 948.8 m where Lucent's DragOnBreath gave 1216.4; the waiting Djinni sits at 80% of it. Compiles; CrossingConditions passes with the catalog commit before this one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:30:55 +02:00
Meta Vens
1dd3627500 GameData/Aetheria.cc: apply AetherDb product-lines weapons again, with name-derived record keys
Regenerated from master's catalog (cee8cd6e) in the ygg-verify container; a second apply says nothing to do. Replay the command at merge; do not merge this blob. The five HullData records still on the schema before orbital-layering deleted GridOffset were re-encoded first (no ExpectUnchanged commit lands on a catalog with a stale record schema), so this blob carries those five hulls re-encoded plus the twenty products.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:29:13 +02:00
Meta Vens
1e9a89e401 CombatState: drop the StatsKnown gate; a stale optimum is re-derived at the next agent Update
Agents update before the entities, so a weapon that came online this tick has evaluated by the next Update;
the Weapon.StatsKnown flag and the gate on it were dead machinery and are removed. The add handler no longer
marks stale itself (a new item's Online subscription does). The refit test fits and removes a dominating gun.

Not yet verified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:28:29 +02:00
Meta Vens
05b262f139 Product lines: records get keys derived from the product name; two catalog tests stop hard-coding the old seller sets
The command's records now land with a key derived from the product name, so a replay at a merge reproduces the same records. CatalogRoleTests.RenamedDesignsKeepTheirKeysAndProducts asserts the renamed products are among a design's sellers, not all of them; RolesBackfillIsAuthored reads maker profiles from Program.MakerProfiles (plus the Pirates) instead of its own copy, which lacked AU. Catalog commit before this one is stale: the next regenerates it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:28:00 +02:00
Meta Vens
4930ecd06a Suns: secondary hue stays on the allowed arc with the primary; ramp never crosses the band
The secondary is placed along the allowed arc (reflected at its ends, distance capped at half
the arc) so the RGB blend between the two stops stays outside the band for any Settings band.
Tests sample the production ramp and pin draw uniformity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:14:15 +02:00
Meta Vens
7e92c7da93 ConsumableAiTests: refit the gun the way a docked ship does
Not yet verified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:08:59 +02:00
Meta Vens
58b78f81de GameData/Aetheria.cc: apply AetherDb product-lines weapons (twenty products)
Output of 'dotnet run --project tools/AetherDb -- product-lines weapons apply' on master's catalog, run in the ygg-verify container; a second apply says nothing to do. Replay the command at merge; do not merge this blob.

The catalog's five HullData records were still on the schema before orbital-layering deleted HullData.GridOffset, so no ExpectUnchanged commit could land on it. This blob also carries those five records re-encoded under today's HullData (plain upsert of the five, nothing else changed in bytes), then the twenty products on top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:08:08 +02:00
Meta Vens
99333cf1a2 ConsumableAiTests: the admitted agent has no patrol task (the test zone has no orbits)
Not yet verified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:04:58 +02:00
Meta Vens
bf32a82e32 CombatState derives its optimum range once the weapons' stats are known, and re-derives on loadout change
A zone-spawned ship's CombatState sampled in its constructor, before any weapon had evaluated its stats,
so the optimum range read 0 and never moved. It now marks the optimum stale on construction, on any
equipment add/remove and on a weapon's online change, and samples at the next Update once every active
weapon reports StatsKnown. ConsumableAiTests build the agent through Zone.Admit(piloted: true) and read
the optimum without re-sampling; new rows pin the hold scope, hull-heading source, mixed paints, sight
independence and the optimum across two guns and a refit.

Not yet verified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:03:50 +02:00
Meta Vens
3eb9d4df6c Merge stardust-before-fog: Stardust draws before nebula composite (closes aetheria-gravity verdicts cut-stardust-before-fog.s1 and .s2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:01:40 +02:00
Meta Vens
df0f51be5f Merge audio-1: simulation names no sound; dead Wwise path deleted (closes aetheria-release:verdict:cut-audio-1.s1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:01:36 +02:00
Meta Vens
4fb5cb9466 GravityFieldParityVerify: the family zone moves along its tree; tolerance restated from the run
Batchmode on Starfire, Unity 6000.3.24f1: PASS, max error 0.01077 u (tolerance 0.03), gradient 0.07 degrees. The previous
frames teleported the planetoid away from its planet, which a compact tree rightly does not draw. WellTree.cs.meta added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:51:20 +02:00
Meta Vens
3fc59887d5 Scene verifier measures dust at partial fog and reads Stardust's own event
The under-fog check lays a floor under the dust so the march has a finite ray (pixels with no depth are fogged whole), disables the zone noise, and uses a dim uniform fill: the correct event dims the dust to 0.58 of fog-free, and dust drawn after the composite reads 0.99. The check also fails when the dust is erased (below 0.1) or missing with the fog off. The attach check reads Stardust.DrawEvent by reflection and requires no Stardust buffer at any other CameraEvent.

Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:48:11 +02:00
Meta Vens
6667b4c6d3 Tests for the weapon product lines and the product-lines command
Catalog tests read the shipped records against the command's own table; command tests run it on a scratch catalog (dry run, apply, replay, untouched bytes, partial line, name conflict, tier accepted). AetherDb shows its internals to the test project. These tests fail until the catalog commit that follows; this commit builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:47:30 +02:00
Meta Vens
fa3c2ab4d3 AetherDb: product-lines command, with the weapons table
One command file owns the twenty second-maker weapon products; roles and quality come from Program.MakerProfiles, now internal. A maker already selling the design is not refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:46:12 +02:00
Meta Vens
d267a535d2 Delete NoMakerSellsTwoProductsOfOneDesign (ruling maker-design-tiers-allowed)
Its justification, Brand finding a product by (maker, design), went with lot-product. A maker may sell several products of one design at different performance.

All commits in this pass build unless noted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:46:09 +02:00
Meta Vens
cee8cd6e40 Release map: ship-render-fixes r3 (removing a broken mod), run-seed / mining-yield / product-tiers lines; mining refresh: overflow floats
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:40:32 +02:00
Meta Vens
7eaa431240 WellLawTests: a rosette's members feel their partners projected toward the node; the tau-1 branch seam is held to the float32 floor
Full suite at the previous head: 2003 passed, 1 failed (the seam test, whose 3e-6 was tighter than the closed form's
single-precision floor, 1e-5 measured, 1.5e-5 in the research).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:34:45 +02:00
Meta Vens
9d5620ddac WellLawTests: the term's slope and band, the family laws, reach and support; ZoneWorlds shared by the tree tests
Hand-built zones move from OrbitsFromFieldTests into ZoneWorlds so WellLawTests reuses them. Not yet run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:17:59 +02:00
Meta Vens
1068eca331 Gravity.cginc regenerated from the band template; orbit and fixture tests read the new law
Gravity.cginc is GravityShader.Source() output. OrbitsFromFieldTests spell periods from GravityField.Pull composed as the law
(binary members feel the partner's pull at the chord, the outer body feels c(total mass)). Fixtures whose bodies carried
mass 0 under constant curves (WellFixture, the mining scenes) now carry a mass: the tree conserves mass. DrawnPoseTests'
rails use the Plummer pull. Builds; not yet run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:15:05 +02:00
Meta Vens
ebce853aba Stardust draws at BeforeImageEffectsOpaque, before the nebula composite; scene verifier proves dust under fog is attenuated
The nebula is an [ImageEffectOpaque] OnRenderImage; a command buffer at BeforeForwardAlpha runs after its composite, so the
dust landed on top of the fog (B49). BeforeImageEffectsOpaque runs before it on the deferred camera. FlowFieldSceneVerify
now requires one Stardust buffer at the new event and none at BeforeForwardAlpha, and scene-stardust-under-fog compares the
dust's contribution with the cloud renderer off and on in an HDR target, with a fog control.

All commits build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:11:04 +02:00
Meta Vens
131895740d WellTree: float2 alias
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 17:58:17 +02:00
Meta Vens
eeeb6db290 Well law: the band term, WellTree owns every term; the old law, per-body sums and Gauss ring law deleted
GravityField is now the band term U(eps, inner, outer) (C4 smoothstep fade, closed form and a series for tau > 1).
WellTree is built once per Zone from the orbit tree: node terms, reach, support, the pruned Force/Potential walk and
PullAround (rails). Zone.GetHeight/GetForce/PeriodAround delegate to it; WellReference, _wellOn, _strengths,
Strength() and DrawAhead.Well are gone; GravityLowering and the shader template take the flat term list;
HeightExaggeration halves to 2.0491803.

Does not pass yet: Gravity.cginc is not regenerated and PotentialOwnerTests/OrbitsFromFieldTests still spell the old law.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 17:56:25 +02:00
Meta Vens
a8586d083f Gravity map: B49 stardust order against the opaque fog pass (camera-event probe on the ARPG camera)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 17:55:56 +02:00
Meta Vens
86d833b0f6 Release map: audio-1 split (audio-present), products order and PL1-PL6, faction pair at 6b6e7c7c; stale "blocked" lines corrected
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 17:49:11 +02:00
Meta Vens
5b1bfe6cec ColorMath.RgbToHcv: read green-dominant colours with the right channel order
The else branch swapped y and z, so the hue read back from many colours was
wrong (values above 1). Nothing else consumed it; the sun-hue test reads hues
back from RGB. Builds; first run of SunsNeverGreenTests failed on this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 17:38:37 +02:00
Meta Vens
2f1f392ecf ConsumableAiTests: stock the hold after the optimum is sampled 2026-10-16 17:30:32 +02:00
Meta Vens
9b1ac48027 ConsumableAiTests: assert the fixture carries what it stored 2026-10-16 17:29:20 +02:00
Meta Vens
f39ce7850e ConsumableAiTests: alias the CultMath vector types the static math import shadows 2026-10-16 17:26:14 +02:00
Meta Vens
c5c9086ece Fauna map: trophic gaps mapped to CultLib cuts; fauna-trophic-habitat waits on expr-trophic-ops
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 17:23:37 +02:00
Meta Vens
fcf1b46880 AI pilots vent on a Track paint past half and overdrive to close (consumable-ai r3)
Re-land on master of the h1 cut with the s1 fixes: the vent rule reads one Track
paint in the ship's own IncomingPaints, TryActivateConsumable refuses on an
inactive ship, the AI holds while a consumable of the kind is active, and the
top of the lock strength range is pinned. Not yet built or run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 17:22:51 +02:00
Meta Vens
0f26c4bb35 Suns never take a green hue: draw sun hues from the circle minus a Settings band
Primary hue maps through ColorMath.HueOutsideBand; the secondary takes the
out-of-band side; fog tint and light derive from primary. Band edges are
SunHueExcludedStart/End (75 and 165 degrees by default).

Not yet verified on Yggdrasil at this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 17:20:52 +02:00
Meta Vens
0fa5c56821 Audio 1: delete the unread SoundbanksInfo.json Wwise export and its staging mentions
Header comment, the stray staging fixture (now Stray.json) and the
build-delivery doc no longer name it. Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 17:18:13 +02:00
Meta Vens
2102affb09 Audio 1: the simulation names no sound; delete the dead Wwise path on EquippedItem
Removes EquippedItem's SoundBank, AudioEvents/AudioParameters, every
FireAudioEvent/PlaySound/StopSound/SetAudioParameter overload and Update's
AudioStats loop, the four audio enums, WwiseMetadata and WwiseSoundBinding,
the weapon/reactor/thruster call sites, and the Wwise Sounds counters in
FireControlPerWeaponTests (Announced and Starts keep the refused-round pins).
Catalog keys 21, 22 and 13-15 stay, unread.

Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 17:18:11 +02:00
Meta Vens
6b6e7c7c26 Release map: consumable-ai r3 body facts CA1-CA7
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 16:44:07 +02:00
Meta Vens
533b301e49 Gravity map: B44-B48 (layers, turrets, stardust, ambient edge, law and generator r3); tail-taming line
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 07:36:51 +02:00
Meta Vens
4abec829ad Merge gravity well-law research r3 (law and generator co-designed, minimap grading)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 07:36:22 +02:00
Meta Vens
fbe3156556 Research: well law and generator co-designed, graded as nested basins (r3), with minimap renders
Generator rules read from the band law's reach (Roche margin, cusp bound, window ending
before the parent's fade, sibling spacing, capacity folding into the parent), applied as a
re-placement of today's zones; basin metrics for ruling wells-read-as-nested-basins (cusp,
blob, nesting, saddle curvature, dead ground, steep share, lanes) and a numpy port of
IsolinesUI.shader rendering four zones per candidate. Stable series branch for the band
potential when the softening is comparable to the band (tau > 1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 07:32:58 +02:00
Meta Vens
6865af813f Merge remote-tracking branch 'origin/eureka/aetheria-gravity-nebula-tint' into eureka/aetheria-gravity-merge-ag3 2026-10-16 07:21:34 +02:00
Meta Vens
e2d78b984c PopulatedRadiusTests: the belt fixture carries the asteroid curves a zone with a belt reads
The belt fixture's PlanetSettings lacked AsteroidSize, AsteroidHitpoints and AsteroidRespawnTime, so building the zone
threw before the bound was read. Builds; the previous commit's belt test fails without this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 06:25:40 +02:00
Meta Vens
fdecde02d0 Merge eureka/aetheria-gravity-stardust-bounds (closing verdicts cut-stardust-bounds.s1, cut-stardust-bounds.s2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 06:25:33 +02:00
Meta Vens
74e51586c2 The ambient quad reaches where Fog Tint's format still holds the gaussian; AmbientEdgeVerify renders into that format
Fog Tint is B10G11R11_UFloatPack32. A quad sized to 1e-5 of the centre drew a plateau near 3.2e-5 from 1.75 to 2.1 P and
then zero: a step in the tail the RFloat verify could not see. AmbientFloor is now 2^-15 absolute and the quad ends where
the brightest channel reaches it (1.78 P), so the one step to dark is the format's last value, at most 1.5 floors. The
verify builds its target from the production render texture's descriptor, asserts the format, holds the profile to the
gaussian within the format's 2^-5 step where it is at least 8 floors, and bounds the rim and the step to dark by 2^-14.
Builds; AmbientEdgeVerify passes on Starfire.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 06:24:36 +02:00
Meta Vens
13691ac1fa Zone.PopulatedRadius covers belts; the tests pin the largest body and the belt
A belt's asteroids circle its orbit's parent, so a belt reaches the chain's reach to that orbit plus its Radius
(AsteroidBelt.Radius, the largest asteroid Distance); the chain walk is one function for bodies and belts. The fixture
inserts a smaller body after the outermost so a last-wins bound fails, and a belt beyond the outermost body pins the
belt term. Builds; Unity-side files are in the next commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 06:11:49 +02:00
Meta Vens
1973b6d220 Stardust: draw at BeforeForwardAlpha (deferred cameras never run AfterForwardOpaque); probe checks the event, culling mask 1 and a near control; Main Menu drops the stale GravityCamera
Both target cameras are deferred, so the AfterForwardOpaque buffer never drew. The render half of scene-stardust-uncullable now attaches-checks BeforeForwardAlpha, renders with cullingMask 1 (DrawProcedural is layer 0), and requires a near control (dust 100 below, size 5) to draw before the far probe is read, so a miss at 10000 can only be culling.
Builds: not compiled by this commit alone; verified in the Unity batchmode run reported with the cut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 05:46:50 +02:00
Meta Vens
8439e39434 FlowFieldSceneVerify: stardust probe sizes its particles to cover pixels at 10000
At size 5 the material's distance^.33 scaling made every quad sub-pixel, so the probe saw no dust with or without the cull. Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 05:24:33 +02:00
Meta Vens
949fa005d6 Merge cut orbital-layering (closing verdicts cut-orbital-layering.s1, cut-orbital-layering.s2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 05:08:26 +02:00
Meta Vens
f50c4cb6c9 Merge cut turret-placement (closing verdicts cut-turret-placement.s1, cut-turret-placement.s2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 05:08:20 +02:00
Meta Vens
9b05ced11f The ambient tint is a gaussian over the populated radius, not a quad that ends at Pack.Radius
Ambient Fog Light leaves Zone Brushes (root object, same pose) and Sector Tint.mat moves from
PowerBrush to Brushes/Ambient Brush: brightness falls as exp(-x^2 ln(1/_EdgeBrightness)), x in
units of Zone.PopulatedRadius. ZoneRenderer.FitAmbientLight, called by LoadZone, is the one
writer of the quad's size and _Reach (the radius where it is 1e-5 of the centre). AmbientEdgeVerify
renders the real scene object through that call. Builds in Unity 6000.3.24f1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 05:08:16 +02:00
Meta Vens
042839fe58 Merge cut dead-fog-marcher (closing verdict cut-dead-fog-marcher.s1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 05:08:13 +02:00
Meta Vens
71bf2a1bff Stardust: draw by a command buffer on the target camera, never frustum culled
Deletes the bounded Graphics.DrawProcedural, its hand-inflated Bounds, the GravityCamera field (and its scene reference) and the dead OnRenderObject block. The camera replays the buffer at AfterForwardOpaque; it is attached on enable, detached on disable and destroy, and released. Unity compile not yet run for this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 04:48:41 +02:00
Meta Vens
03e91aede2 FlowFieldSceneVerify: stardust probe renders without the camera's image effects
With them on, the effects wrote their own frame and the probe could not tell dust from noise. Builds; the check fails on the bounded draw (buffer count 0, no dust pixels, run against 5a788833 plus this probe).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 04:37:27 +02:00
Meta Vens
cbdecfd578 Layering fixes: mine floater layer checked in the wiring verify; contact fixture orbitals carry Station/Turret hull types; grab point rides the floater layer
Unity compile is verified before push; not yet built when committed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 04:24:43 +02:00
Meta Vens
ecb165624d Zone.PopulatedRadius: the extent of the populated area, owned by the zone
The largest over bodies of the root orbit's offset from the origin, every orbit Distance
along the chain, and the body's Roche limit: a static bound on where any body can be,
computed once at construction and independent of Pack.Radius. Presenters read it.
Builds; the Unity side follows in the next commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 04:13:43 +02:00
Meta Vens
ee18829a04 FlowFieldSceneVerify: scene-stardust-uncullable renders the dust 10000 below the camera
Fails on the bounded Graphics.DrawProcedural (this commit builds; the check fails until the cut lands).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 04:09:08 +02:00
Meta Vens
7274f6d623 Delete the fog marcher nothing draws
VolumeRender and VolumeRenderCustom shaders, the four Fog Marching
materials, ZoneRenderer's FogMaterial/FogFarFadeFraction and its
_DepthCeiling/_DepthBlend writes, the ARPG scene values, and the
Volumetric.cginc _TintExponent, texFlow and tintGradient (with its
commented-out caller). The live nebula is VolumeCloudRenderer + CloudShader.
Unity compile not yet run on this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 04:06:01 +02:00
Meta Vens
2bc09f832f StationPlacementTests: the narrative station belongs to a faction, as authored stories do
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 03:56:52 +02:00
Meta Vens
89a481fd16 Turret placement: turrets clear their inner neighbour by their own reach; tests for the tail, the story site, off-ring and no-turret zones
Each side of a station grows outward from it: a turret sits innerReach + 2 turretReach beyond the body inside it,
so turrets of differing reach clear each other (same arcs as before for one turret hull at k = -1, +1).
StationPlacementTests: shared flank check (no off-ring turret escapes it, 1e-4 relative arc), a narrative
station with five authored turrets and a second, larger turret hull, a zone with no turret hull, and a sweep
that no station or turret reaches another orbital. Not yet run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 03:54:24 +02:00
Meta Vens
d15052afaf LayerTests: provenance lots for the fixture hulls (a0a85920 fails EachKindRidesItsLayer)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 03:32:19 +02:00
Meta Vens
a0a85920cd LayerTests: using static CultMath.math (fixes the compile error in be7f2b07; be7f2b07 does not build)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 03:17:42 +02:00
Meta Vens
55c61676ce StationPlacementTests: the station's source orbit may be a bodyless node
The generator picks from every non-rosette planet orbit, empty nodes
included, so the source is the one ring orbit no orbital entity rides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 02:42:25 +02:00
Meta Vens
be7f2b0787 Layers: one owner puts orbitals above the ship plane and floaters below; per-hull GridOffset is deleted
GameplaySettings carries ShipLayerHeight 3, OrbitalLayerHeight 15, FloaterLayerHeight 0 (authored in Settings.asset).
Entity.LayerHeight (Ship, OrbitalEntity) is the one offset RideHeight adds; DrawAhead.FloaterHeight is the floater
height ItemPickup and MineInstance draw at. HullData.GridOffset (Key 26) and the pickup/mine/prefab GridOffset fields
are gone with every reader. Fixtures (DrawnPose, FireControl*, ImpactFeedback) take heights from the layer owner;
LayerTests pin each kind's layer. FloatingItemsDrawnSmoke follows. ItemPickup/MineInstance.Bind take the settings from
ZoneRenderer. Builds as one commit with its tests; not compile-verified until the cut's head run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 02:35:07 +02:00
Meta Vens
089c3ecad1 Turret placement: stations at L4/L5 in turns, turrets flank by hull reach
CreateLagrangeOrbit adds one sixth of a turn (was PI / 3 on a turns field).
PlaceTurret spaces turrets by k * (stationReach + 2 * turretReach) arcs,
k = -1, +1, -2, +2 (was t / 2 integer division putting two turrets on the
station, and radians-per-arc arithmetic on turns). Contacts.Radius moves to
HullData.Reach. Not yet verified on Yggdrasil.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 02:29:57 +02:00
Meta Vens
624d5420e9 Contacts: hull contact is between ships only; orbitals take no part
Step pairs OfType<Ship>; stations and turrets sit on the orbital layer above the ship plane
(ruling aetheria-gravity:orbitals-above-floaters-below). Tests: a ship flies through a station and a turret;
two ships still bounce beside an orbital. Not yet compile-verified (verified once at the cut's head).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 02:27:58 +02:00
Meta Vens
5a788833f0 Merge gravity-drawn-pose-at-drawn-time
Operator flew 39ba73d5 on 2026-10-16: no jitter coasting beside bodies. Soul s1-s3; residuals in follow-up drawn-pose-residuals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 02:09:42 +02:00
Meta Vens
39ba73d57d DrawnPoseWiringVerify.cs.meta
Builds: yes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 01:06:39 +02:00
Meta Vens
215ba46569 DrawnPoseWiringVerify: batchmode proof that ItemPickup, MineInstance and Projectile draw at Clock.Lead
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 00:50:44 +02:00
Meta Vens
e5eb87050e Drawn pose: ship indicators and map centre read DrawAhead; Round takes one time; RailAt logs a missing parent
- ActionGameManager: the visible hostile/friendly indicators and MenuMap.Position read DrawAhead.Position at Clock.Lead.
- DrawAhead.Round drops its lead parameter: the target's lead is the drawn time less its zone's step time, derived inside,
  so a caller cannot pass two times that disagree. Projectile and BallisticFlightTests updated; the hit tests derive their
  lead from the drawn time; two assertions that only varied the now-absent lead are removed.
- Zone.RailAt logs "Requested orbit is not part of this zone!" for a missing parent again (DrawnPoseTests.AMissingParentOrbitIsLoggedAtADrawnTime).

Builds: not yet compiled at this commit (verified by the run that follows).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 00:50:29 +02:00
Meta Vens
eb0c12e459 DrawnPoseTests: compare record keys with Equals (2cb1e833's tests do not compile)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 23:43:56 +02:00
Meta Vens
2cb1e83353 Presenters read the drawn pose: HUD markers, wells, rounds, ping, shield hit, compass, loot view, guided aim
HUD target, lock, incoming-lock, bearing and sweep markers anchor on DrawAhead.Position / AtBearing at Clock.Lead.
GravityLowering's record fill is DrawAhead.Well (pure, pinned at non-zero lead). Rounds take DrawAhead.Time
instead of float Zone.Time + lead. Sensor ping, shield hit, compass icons, wormhole bearing, loot ViewOrigin and
the guided-projectile aim read the drawn pose. Not built in Unity yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 23:41:49 +02:00
Meta Vens
6c865b2cc9 Zone: the memo fill takes its position from RailAt, so the chain rule exists once
GetOrbitPosition's step-time fill and RailAt each decided the rail chain (root at
FixedPosition, child at parent plus offset). The fill is now RailAt(id, _time).
DrawnPoseTests builds the sun on an off-centre FixedPosition (the ZoneGenerator
cluster-root shape) and adds a root test at step and drawn time, and an unset-orbit
OrbitalEntity fact for the DrawAhead guard.

All commits build, unverified until the job in the report.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:39:06 +02:00
Meta Vens
b71dfc219b Gravity map: B40-B43 conserving compact well law; tail-taming cut line
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:27:12 +02:00
Meta Vens
db31d96f8a Merge gravity well-law research
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:27:08 +02:00
Meta Vens
b2f6122dd2 Research: the conserving compact well law (band terms on the orbit tree), for ruling tame-tails-parent-only-rails
Each node pulls exactly as its body inside its outermost child, exactly as one body
of its family's mass beyond its own support, and nothing beyond its reach; the root
has no reach, so the zone's far field is exactly c(M_zone) however the generator
splits it. C4 on every band edge, closed-form potential (grid = K phi), Plummer core.
Probe against today, a2, c2, b1 and the tapered draft: ratio, funnel, 12-18 u/s,
far field, split invariance, whole-zone depth, and evaluation cost.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:22:10 +02:00
Meta Vens
fd00ad90ef GravityLowering: build the well Vector2 from the drawn orbit explicitly
Unity presenter file; not compiled by this pass (no Unity run).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:59:41 +02:00
Meta Vens
63124add6b DrawnPoseTests: coasting probe reads world-axis swing; SimClockTests rides the field at the drawn time
The co-moving probe projected a world-fixed offset onto the rotating motion
direction; it now compares the planet and ship in world x and z. SimClockTests'
zero-lead sibling at a lead read the step-time ride height; with moving wells the
drawn ride is the field at T.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:45:10 +02:00
Meta Vens
8bc5ef1837 Merge gravity smooth-wells research
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:44:31 +02:00
Meta Vens
e395b5b091 Research: smooth (C-infinity) gravity wells, seam jumps, conserving halo and split-deepening probe
Eyes pass eyes-ag3-smooth-wells (aetheria-gravity, self-2026-10-15-ag3). Extends
gravity-tail-taming: smoothness class per law, measured A&S K0/K1/E1 seam jumps,
log-ratio, screened Plummer, Lamb-Oseen (with and without halo), Plummer log and
conserving-halo families on the same probe, plus zone far-field against merged mass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:43:03 +02:00
Meta Vens
b57207045d Draw every moving body at the drawn time T = ExactTime + Lead
Planets, belts, wells, loot, mines, stations, the chunk reticle and the flow
clock are evaluated through DrawAhead (Time, Orbit, Height, Position) at one
drawn time; the rail law lives once in Zone.Offset; OrbitPositionAt, GetHeight,
ChunkPose, EvaluateBelt and RideHeight take a time. Fixes the planet jumping
against a co-moving ship at display rates above the step rate.

Not yet built or run (Unity presenter files are verified by compile only on
Starfire).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:26:36 +02:00
Meta Vens
39ebf7f6af Merge gravity tail-taming research
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:22:03 +02:00
Meta Vens
ca37b45687 Research: taming summed gravity tails (screened K0, caps, force switch, disc subtraction, exponent, hyperbolic kernel), for ruling tame-tails-parent-only-rails
Eyes pass eyes-ag3-tail-taming, session self-2026-10-15-ag3. Facts file plus
numpy probe over the orbit-probe bodies: enclosed/parent speed ratio by child
class, mid-zone inward pull, planets round a median sun, HLSL cost per well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:19:49 +02:00
Meta Vens
46af8d4f09 Gravity map: B39 planets drawn at the step; drawn-pose and tail-taming cut lines
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:12:35 +02:00
Meta Vens
c981537904 Research: rosettes as relative equilibria under the 2D log law, for follow-up rosette-generation-r3
Eyes facts for aetheria-gravity: regular and twisted alternating polygon
equilibria (log closed form, Newtonian sources), linear stability
(Muscas-Pasca-Stoica log thresholds reproduced, Klemperer, Thomson),
the soft core's reach, PeriodAround's ring rule against the twisted
equilibrium spin, and rotating-frame stationary points against the drawn
potential. Solver scripts beside the note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:01:13 +02:00
Meta Vens
09ec04a8d5 Merge gravity-orbits-from-field
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:52:57 +02:00
Meta Vens
49e8ea58e1 Merge gravity-fog-precision-and-jitter
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:52:55 +02:00
Meta Vens
55cc7eda41 Merge gravity-grid-lowering
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:52:48 +02:00
Meta Vens
1babed4bf1 Ring law: a strengthless ring member takes its bodied partners' period; the ring window and a bodyless node's closed field are pinned
PeriodAround's ring term is one rule: self feels the mean, over the members whose pull it takes (itself if bodied, else every
bodied ring member), of half the rest of the ring. A station on a binary's ring no longer stands still. The 0.1 u window is a
named constant, pinned just inside and just outside it; a bodyless node's children are pinned never to read the node's parent's well.
Both commits build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:05:03 +02:00
Meta Vens
4bac2623bf AI aim and turret aim ride Entity.RideHeight; tests pin the hull GridOffset on a slope and at both aim sites
Combat and TurretController spelled GetHeight + GridOffset inline; they call target.RideHeight now. The drawn-ship test authors a hull with GridOffset 2.5 on the well's slope. Two new tests aim at a target hull raised 40 u.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 18:39:28 +02:00
Meta Vens
fe5d03d04b Menu and test scene patch cameras stop culling the gravity layer; the parity verify checks every scene's patch camera
cloudDensity adds the R32 gravity height to the patch texture, so Main Menu and TestScene patch cameras (mask 5632) counted layer 9 twice. Mask 5120, and GravityFieldParityVerify now asserts each of ARPG, Main Menu and TestScene excludes layer 9 from its Fog Patch Displacement camera.

All commits build as far as C# goes; verify runs after the second commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 18:39:27 +02:00
Meta Vens
160abd8b44 OrbitsFromFieldTests: the facts of a rail's period under the field
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 18:11:37 +02:00
Meta Vens
a4977cbe48 Every rail's period is derived from the field it orbits in; OrbitPeriod is deleted
Zone.PeriodAround is the one owner: round a body, the pull its well gives at the rail's distance
(GravityField.Acceleration); round a bodyless node, the ring law (strength of the orbits inside, plus
half of what shares the ring). Orbit.Period and AsteroidBelt.Periods are written once at zone build,
before placement; BeltTargets copies the belt's periods. A parentless orbit has no field and stands at
its phase. Fixtures lose OrbitPeriod and gain wells where their rails must move. Builds; the new
OrbitsFromFieldTests follow in the next commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 18:11:27 +02:00
Meta Vens
d7f0e8f30f Aetheria gravity map: orbits-from-field mapped (B35-B38: field speeds, bodyless parents, hierarchical premise, readers)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 18:00:22 +02:00
Meta Vens
27fd8687a3 Test that a child is drawn where its parent is drawn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 17:48:09 +02:00
Meta Vens
3b039d069b The nebula patch reads gravity from the R32 surface height, not a half-float copy
Patch Displacement no longer culls layer 9; cloudDensity adds the surface displacement to the patch-only displacement.
GravityFieldParityVerify wiring checks the camera. Neither commit has been built when this is written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 17:45:57 +02:00
Meta Vens
105f0270eb A drawn ship rides the field at its drawn xz
DrawAhead moved xz ahead but carried the last step's y, so on a slope the ship's height stepped once per sim tick.
Entity.RideHeight is the one owner of 'an unparented entity sits on the field'; the step and DrawAhead both call it.
A child is drawn where its parent is drawn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 17:45:56 +02:00
Meta Vens
5ec9363ac4 Aetheria gravity map: B32-B34 (slope jitter from stepped ride height, half-float patch gravity copy, fog snapping) and fog-precision-and-jitter in the cut sequence
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 17:41:45 +02:00
Meta Vens
37e0cb113d Aetheria gravity map: B31 core at the Roche limit and K = 4.098 (supersedes core and K numbers in B22, B25, B28)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 17:24:46 +02:00
Meta Vens
8f491fd3c9 Core radius is the Roche curve; the global exaggeration divides by the core's growth
core-at-roche-and-exaggeration-up: PlanetSettings.CoreRadius and Planet.CoreRadius are
deleted; the well's softened core reads the RocheLimit curve (one curve of mass). The
core < Roche guard cannot fail and is replaced by a Roche-limit-positive-and-finite
check. Settings.asset: CoreRadius removed, HeightExaggeration 10 -> 4.0983607
(10 / (7.32 / 3)). Tests pin the new curves and the peak drawn slope / 2.44x steeper tail.
Not yet built or run at this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:46:53 +02:00
Meta Vens
0d7fc2ee3a Merge origin/eureka/aetheria-gravity-potential-owner (cc3249b8) into grid-lowering
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:44:10 +02:00
Meta Vens
cc3249b8c9 Crossing sweep: rename the shifted-pass lists (a name clash broke the previous two commits' build)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 15:50:20 +02:00
Meta Vens
aa3af5ecc0 Crossing sweep: the lateral shift moves the pass by at least 3 m (the crosser meets the player's hull below a 40 m start)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 15:48:05 +02:00
Meta Vens
a033c01c56 Crossing sweep: assert the shifts moved the pass, so a sweep that ignores them fails
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 15:47:12 +02:00
Meta Vens
ced6600261 Crossing: start window centred at (-128, 45); relative speed and angular rate bounds from the sweep
The crosser is unpiloted and launched at 50 u/s; hull drag slows it to about 25 u/s relative at the bow line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 15:28:56 +02:00
Meta Vens
61dc8c6d0e Crossing test: InArc takes a planar offset as float3 (WIP, bounds still unfitted)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 15:23:38 +02:00
Meta Vens
bf69f489a9 Crossing: unpiloted crosser coasting at 50 u/s; the start-window sweep (bounds to be fitted)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 15:22:14 +02:00
Meta Vens
9b1b27d2d6 GravityShader: verbatim string, not a raw string literal (Unity's C# 9)
Commit 13c7a087 and the merge a1b54bea do not build: error CS8773, raw string literals need C# 11.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:47:01 +02:00
Meta Vens
a1b54bea7a Merge origin/eureka/aetheria-gravity-potential-owner (1c5326dd) into grid-lowering
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:44:31 +02:00
Meta Vens
13c7a08707 Gravity.cginc is generated: GravityShader.Source() is its one author, a test pins the committed file to it byte for byte
The mirror test compared a normalised reading of two spellings and was beaten twice (a statement before the return, gravityHeight's sum). GravityShader writes the whole file (potential, zero-strength guard, reference term, height sum) from GravityField's constant (TailOffset); GravityShaderTests fails on any difference. GravityShaderMirrorTests deleted. GravityFieldParityVerify opens Main Menu.unity: no ZoneRenderer there, its Fog Surface Displacement camera is the menu's own brush presentation (layers 9 and 10) by spec.

Builds not yet verified on this commit; verified at the final merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:44:11 +02:00
Meta Vens
1c5326dd9d TheCoreIsTheBowlUpToItsEdge: sample 0.996R so a join at 0.995R fails
The mutation table showed a join at 0.995R surviving: no sample lay at or past it. At 0.996R the log form
differs from the bowl by 1.6e-5 c, over the test's tolerance.

Not built or run locally; verified in the Yggdrasil job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:17:10 +02:00
Meta Vens
e6d1546b44 Gravity grid-lowering fixes: formula mirror test, verify across moved off-axis bodies and a zero-strength body, map camera wiring, RTS Planet's Gravity Well deleted
GravityShaderMirrorTests compares the expression of GravityField.Potential with gravityPotential in Gravity.cginc.
GravityFieldParityVerify now builds four off-axis bodies (one of mass 0), lowers through three frames with bodies moved
between them, and checks the map screen's gravity camera mask. RTS/Planet.prefab loses the Gravity Well object bound to
the Gravity Field material. Builds: yes (verify passes on Starfire; the new test is unrun until the Yggdrasil job).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 07:59:15 +02:00
Meta Vens
4568e217f3 Merge remote-tracking branch 'origin/eureka/aetheria-gravity-potential-owner' into eureka/aetheria-gravity-grid-lowering 2026-10-15 07:55:31 +02:00
Meta Vens
268065ca67 Merge eureka/aetheria-release-mount-cycle-and-test-hygiene
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 07:49:00 +02:00
Meta Vens
d7bdbc4a77 GravityLowering: the record comment no longer spells the deleted GravityWell name
Comment only; verified code is 3a5b3b0b. Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 07:29:21 +02:00
Meta Vens
45d19f3a8e PotentialOwnerTests: authored curves read from Settings.asset; zero pinned from just below; the core bowl sampled to 0.99R
TheAuthoredCurvesBuildAtEveryMass loads PlanetSettings from the asset the way RunStartTests does. The unphysical-strength
theory adds -1e-6 and -float.Epsilon and a new test builds zero and negative zero. TheCoreIsTheBowlUpToItsEdge compares
the drop from a lone body's centre with c r^2/2R^2 at 0.5R to 0.99R, tightly enough that a join at 0.95R fails.

Not built or run locally; verified in the Yggdrasil job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 07:23:26 +02:00
Meta Vens
a54e2f53df Pin the mount aim's resolved reads, the record's isolation and its Deactivate clear; delete two dead guards
Forecast and Fire drop a mount aim whose gate closed (destroyed, unidentified); a record for one Bay never hides another's mount; Deactivate clears the record. Deleted: the located-or-booked guard in RecordSeenDestroyed and the Entity == subject check in ResolvedAim (the record is read only for layout mounts; a Target change already clears the slot). Comments name AimedAt. Not built or run yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 07:14:35 +02:00
Meta Vens
3a5b3b0bab Gravity grid-lowering: the drawn height is one Gravity Field pass over the zone's wells; gravity brushes and brush inputs deleted
Zone.WellReference owns each body's reference zero (GetHeight uses it). Gravity.cginc mirrors GravityField.Potential;
Brushes/Gravity Field draws depth = -h on layer 9 from a StructuredBuffer that GravityLowering.Lower fills from the
zone each frame; GravityLowering.CreateField makes the one quad. GravityFieldParityVerify renders it and compares every
texel with -Zone.GetHeight (max error 0.0059 u), the gradient with GetForce (0.03 degrees) and the wiring.

Deleted: the Gravity Well children of Planet, Gas Giant, Sun and PlanetOutpost prefabs, Wormhole 1's three gravity
children, their overrides in Main Menu and TestScene, five wormhole/wave materials and two brush shaders,
Planet.GravityWellDepth/Radius, BodyData keys 6-7, PlanetSettings.GravityDepth/GravityRadius (and the Settings.asset
keys). Surface Displacement culls layer 9 only. Headless build and tests not yet run at this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 07:11:25 +02:00
Meta Vens
c4dfd905ff Gravity: the Planet guard refuses an unphysical well; tests pin the Roche rule, per-body multipliers, the core band and Ship's order
A WellStrength that evaluates negative, NaN or infinite now throws at construction (it built an
antimass well with NaN height). New tests: the core-inside-Roche guard judged at every mass on
curves that cross in the generated range; per-body multipliers move neither strength, core nor
Roche limit; potential and force consistency inside the cores' outer bands (0.5 R to R); a
moving Ship feels the field where it has moved to.

Not built or run locally; verified in the Yggdrasil job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 06:36:02 +02:00
Meta Vens
8013c2deec Crossing and Lock On: restaged for the ruled field
Under the potential-owner field the zone pulls everywhere (about 1.4 u/s^2 even in
its weakest places), so the staged runs bend. Crossing's player sat on the star at the
arena's origin and fell in; its crosser, braked by its pilot, never reached the bow
line from 150 m to port. The crosser now starts 96.5 m to port and 70 m ahead of a
player staged 250 m or more from every body, which keeps the pass at 3.2 s and 40 m
(the test's 3.0-4.2 s and 30-60 m unchanged). Lock On's hostile started on the side
where the zone bends its run by 7 degrees; the lock warning must point within 5 degrees
of where the hostile was staged. It now starts ahead and to port, where the bend is under
one degree and the lock comes at about 14 s. No assertion, limit or gravity is changed.

Not built or run locally; verified in the Yggdrasil job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 06:36:01 +02:00
Meta Vens
f5caef9fd0 Aetheria gravity map: grid-lowering mapped (B27-B30: GPU readers, precision, one-quad lowering, parity verify)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 06:08:27 +02:00
Meta Vens
dd06f39dbc FloatingBodyTests: the tilt expectation is spelled from force and K, not read back from GetNormal
Two hand mutants of GetNormal's K (ignored; times 3) survived because the test compared the ship's
tilt with the same GetNormal it was testing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 05:40:10 +02:00
Meta Vens
43536ccf5f Pin that a book entry is not a perception of its item's destruction
Mutant d (record written without the identification gate) survived: the located path is
already gated by LayoutOf. The gate matters for a destroyed item held only by its act.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 05:10:02 +02:00
Meta Vens
41e90d0e40 GravityField: a well of no strength digs nothing (c = 0, R = 0 gave NaN height)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 05:02:48 +02:00
Meta Vens
22e1bc32cc RecordSeenDestroyed allocates nothing in the common path
Sense runs for every pair of ships every tick: iterate Equipment by index and read
the believed layout only for an item actually perceived destroyed.
SenseAllocatesNothingOnceWarm failed on the previous commit (1257600 bytes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 04:57:13 +02:00
Meta Vens
fde8062931 Gravity potential-owner tests: fixtures keep the brush-input curves Planet still reads
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 04:51:20 +02:00
Meta Vens
a554d5ed08 Fix stray line in IsRevealed's comment; drop the seen-destroyed record of a departed subject with no book
The previous commit does not build (a leftover line in FireControl.cs). EndReveals
only runs for subjects the book holds, so a record of a quiet departed ship was held
for the sitting; Sense's prune now ends it too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 04:44:24 +02:00
Meta Vens
f6c0d24dba One aim slot holds an item or a mount; the cycle covers every located mount
Entity.AimedAt (AimTarget: a revealed item or a believed mount) replaces
TargetItem. Two writers, TrySelectTargetItem (IsRevealed) and TrySelectMount
(in LayoutOf, not seen destroyed); ResolvedAim re-validates both on every read.
AimCycle is SchematicRows' enemy rows minus mounts in the observer's
seen-destroyed record, written only in Sense through PerceivesDestroyed.
SchematicRows drops its covering filter. IsLocated and AimsAtMount are deleted;
PendingShot.AimedMount and Silhouette's HardpointData mount replace the bool.
The cycle key calls Entity.CycleAim; the schematic marks read ResolvedAim.
Entity.ToString names an entity (follow-up entity-formats-safely).

The slot is AimedAt, not Aim: Entity.Aim is the pilot's pointing direction.
Not yet built or run; ActionGameManager and SchematicDisplay are uncompiled
without Unity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 04:42:38 +02:00
Meta Vens
6957180450 FloatingItemsDrawnSmoke waits on game time and a real-time budget
Both label loops wait on Time.time against the pickup's own persist and fade
durations plus a second, not 1200 frames; Step's guard is a 300 s real-time
budget, not 3000 frames. Batch frames can be far shorter than a game second,
so a frame count could expire before the label faded (follow-up
floating-items-smoke-label-fade-flake). Not yet run in Unity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 04:42:28 +02:00
Meta Vens
021c4cbcbf Gravity: one potential per zone owns acceleration and height
GravityField is the law of one well (logarithmic tail, softened core).
Zone.GetForce and GetHeight sum it over the bodies; GetNormal derives from
the force for ship tilt; Flow and Ship read GetForce. Well strength, core
radius and Roche limit are PlanetSettings curves of mass, height
exaggeration K one global. Deletes GravityAcceleration, GravityReferenceRate,
GravityStrength, the PowerPulse well, GetNormal's finite differences and
BodyData.GravityDepthExponent. Settings.asset carries the calibrated curves.
Tests: PotentialOwnerTests, the moon's moon placement, retired keys over every
BodyData type, a body-derived giant sweep. Not yet built or run (verify job
follows); this commit may not build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 04:36:28 +02:00
Meta Vens
54e2f88702 aetheria-release map: mount cycle at 3f5cef68 (MC1-MC10, rationale, cut sequence)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 04:32:03 +02:00
Meta Vens
ff63362f45 Aetheria gravity map: potential-owner r3 (Roche curve, core inside it, moon windows B25, grid-lowering stacked B26)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 04:29:14 +02:00
Meta Vens
3f5cef68e7 Merge eureka/aetheria-release-layout-aim
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 02:30:59 +02:00
Meta Vens
61e66dac35 Layout-aim tests: AddWing picks the guns by design name (the hull is equipment too)
Fixes 3098ff69, whose two Wing tests did not build their fixture. Test-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 01:52:44 +02:00
Meta Vens
3098ff69a5 Layout-aim tests: lookalike-hull lie, asymmetric two-mount Wing hull, Entity assertions without formatting, docked stance through IsHostileTo
UnderALookalikeLieNoGearIsLocated: claimed and true hulls of one type and size with a reactor mount of equal type, position and shape. AMountAimReadsThePositionAxesInOrder and TwoMountsOfOneTypeAreTwoRows use a Wing hull off the diagonal with two Sensors mounts. Assert.Same and DoesNotContain over Entities become ReferenceEquals asserts. The docked-stance test reads IsHostileTo on a hostile-by-default subject instead of _iffOverrides. Test-only; no production change. Not yet built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 01:39:37 +02:00
Meta Vens
1b0a67069e Layout-aim fixture: reactor mount off the origin so a mount-cell offset term is observable; schematic keys and below-identify rows pinned
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:31:57 +02:00
Meta Vens
1c041cb67b SchematicRow: assign every field in both constructors (the struct language level has no auto-default)
Fixes the build of the layout-aim sim commit (095efacd).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:19:09 +02:00
Meta Vens
2a69c40bfa Tests: layout-aim behaviour, retarget scan progress, Inspection Scanner role map, Entity-free fixture assertion
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:17:21 +02:00
Meta Vens
eb2dc1e501 Scanner progress reads 0 on another target; Program.RoleMapOf exposes roles-backfill's map
Scanner.Progress is bound to the target the scan was built on, so a retarget with the scanner switched off no longer
draws the old scan on the new target (scan-progress-shown-on-retarget). RoleMapOf is the one accessor HasRoleMap now
delegates to. Not yet built or tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:17:17 +02:00
Meta Vens
095efacd79 Layout-aim: an identified hull shows where its mounts are; a located mount can be aimed at
TrySelectTargetItem and ResolvedTargetItem gate on FireControl.IsLocated (an item seen to act, or one on a mount of the
believed hull's layout) instead of IsRevealed; Combat keeps IsRevealed. Entity.LayoutOf derives the layout from the
Contact profile; SchematicRows appends a mount row per unclaimed hardpoint. A mount aim centres on the mount's cells and
is frozen per shot (PendingShot.AimAtMount). The cycle runs over located items, the schematic renders mount rows and
marks through SchematicRow.Marks. Not yet built or tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:17:14 +02:00
Meta Vens
f8e7abf460 Merge eureka/aetheria-release-scanner-catalog
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 23:50:57 +02:00
Meta Vens
22534aebb7 Aetheria gravity map: potential-owner mapped (B19-B24, constants, funnel and moon-window findings)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 23:48:41 +02:00
Meta Vens
5dd1f6b71f Merge eureka/aetheria-gravity-gravity-subtract
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 23:11:47 +02:00
Meta Vens
2aad0fceca Gravity subtract tests: sweep the giant's well densely; pin gas-giant keys 14-17 as retired
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:09:48 +02:00
Meta Vens
7ae77e41a7 Scenes: drop the Sun prefab-instance overrides that targeted the deleted Gravity Waves object
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:08:18 +02:00
Meta Vens
3b9d7e0770 ZonePlacedAtBuildTests: drop the Wave curves the merged settings no longer have
Builds after the merge of orbits-placed-at-build; the merge commit itself does not build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:07:22 +02:00
Meta Vens
570f22083b Merge orbits-placed-at-build into gravity-subtract
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:07:09 +02:00
Meta Vens
3314663826 ClaimTests: a weapon claimed as a non-weapon is a gear row of the claimed class
The enemy schematic now lists every non-weapon claim as a gear row (cut scanner-catalog), so the old "no row" expectation became "one row, the claimed class, no range". Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:37:32 +02:00
Meta Vens
9b28daf7eb Tests: the gear schematic fixture reveals the hull as the claim tests do; drop the formatting probe
The Format probe showed a Reveal formats as "Reveal" (no graph walk), so no ToString and no test is added (follow-up bearing-markers-test-residuals (2)). Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:23:47 +02:00
Meta Vens
5659bcfdd0 ClaimTests: SchematicRow.Range is nullable
Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:20:25 +02:00
Meta Vens
283d1365c4 Tests: inspection scanner catalog, command, schematic gear rows; scanner/emission/stance residuals
InspectionScannerCatalogTests and InspectionScannerCommandTests (new); AFinishedScanListsTheGearOnTheSchematic; AScanReadsNoHostility pins pace, emit and a target-side sensor; AScanTakesTheSamePathForAnyPilot uses a Minion in CombatState; an emission heard within Range over the lot's alpha (ping and scan); the docked-subject test is a Theory over true and false; a ReveEal formatting probe. Not yet run: may not build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:19:14 +02:00
Meta Vens
205098ef93 Zone.Update takes every previous position before placing any orbit
GetOrbitPosition places a parent as a side effect of placing its child, so with a child listed first the parent's previous position was already its new one and its Velocity came out zero. The constructor's unread PreviousPosition write is dropped; the first Update takes it from the placed Position. The velocity test now runs moon-first and planet-first packs over varying dt. Builds not verified yet (verify follows).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:19:09 +02:00
Meta Vens
dbace27ee7 Catalog: Inspection Scanner design and three products (Inkling, Assay, Full Disclosure)
GameData/Aetheria.cc after `AetherDb inspection-scanner apply` on Yggdrasil (sha256 ed0500b4...): one GearData and three FactionProductData by Finch, AU and Lucent; a second apply wrote nothing. Builds: yes (data only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:13:28 +02:00
Meta Vens
b14d609cae Schematic lists revealed gear; scan progress indicators at both ends
Entity.RevealedGear and SchematicRows' enemy branch (revealed weapons then revealed gear); SchematicRow.Range is float? (null for a non-weapon claim); SchematicDisplay shows ShowGear rows for non-weapon claims and hides the range label when null; SchematicListElement.ShowGear. ActionGameManager: one progress-indicator list (lock weapons hostile-gated, scanners ungated) and the incoming indicator follows Track or Scan paints. Not Unity-compiled yet; Shared tests unaffected by the UI files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:11:37 +02:00
Meta Vens
70b7829bb0 AetherDb: inspection-scanner command (design, three products, role map)
Adds the inspection-scanner [apply] command beside launch-suppressor, in its pattern: dry run, all-or-nothing idempotence, partial refuses, ExpectUnchanged batch, root parameter for tests. One Inspection Scanner design (Tool 1x1, one ScannerData) and three products from Finch, AU and Lucent; ToolRolesByDesign gains the design. The catalog itself lands in the next commit. Builds: not yet verified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:11:17 +02:00
Meta Vens
cc398ff2ad aetheria-release map: visibility refresh at a17dab36 (VA1-VA12, scanner-catalog r2 and layout-aim r3 order)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:04:37 +02:00
Meta Vens
a17dab3677 Merge eureka/aetheria-release-bearing-markers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:32:03 +02:00
Meta Vens
36d2dd45f2 Gravity subtract tests: pin the well fixture to the bowl it replaced
The five bowl fixtures compare to the zone's own normal, so nothing pinned
the shared well's depth or radius to the bowl (a depth-scaled fixture
mutant survived). TheWellFixtureReproducesTheFormerBowl checks heights
against the bowl's own definition. This commit's test has not run yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:30:28 +02:00
Meta Vens
abe0c848be Bearing-marker tests: assertions over entities report booleans, and the docked and inbox clauses are pinned
A failing Assert.DoesNotContain/Contains over a collection of Entities made xunit format the Entity graph and overflow the stack, aborting the host; the five such assertions now assert a bool. Adds a docked-subject stance test and a pending-paint-on-leaving test. Builds: yes (test-only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:55:10 +02:00
Meta Vens
9fb5aad454 EvSettle returns the ship to where it stood
A ship stepped alone drifts under the zone's wells while it idles. Orbits are
now placed at build, so those wells are where the bodies are and the drift is
real; the scenarios (Crossing, FusedRounds) staged geometry the wrong wells
happened to leave undisturbed. Settling is about stats and power, not place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:50:08 +02:00
Meta Vens
55d5566743 Gravity subtract tests: open the fixtures' caches with a run store
OrbitData and BodyData are run-store types; a cache opened with a catalog
only has no home for the well body. The six fixtures that dig a well now
open with a run path. The previous commit does not pass its tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:47:32 +02:00
Meta Vens
68c97166ab Aetheria gravity map: cut-sequence line for the top-speed field (no drag below top speed)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:46:05 +02:00
Meta Vens
a8876d7a33 ZonePlacedAtBuildTests: open the catalog and run stores as the other zone fixtures do
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:34:21 +02:00
Meta Vens
7d3fb29ed2 Gravity subtract: delete the zone bowl and the gas-giant and sun ripples
GetHeight sums body wells only. Removes RadialWaves, GasGiant wave fields,
GasGiantData keys 14-17, PlanetSettings Zone*/Wave* fields, the minimap
gravity range, the Boundary brush and Gravity Waves objects from the scene
and both prefabs. The five bowl fixtures become one WellFixture well at the
origin with unchanged expected numbers. Not yet built or tested: this commit
has not been through the Yggdrasil job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:34:20 +02:00
Meta Vens
aefd0c0a92 Zone places every orbit when it is built; delete AddOrbit and CreateOrbit
Orbit.Position and PreviousPosition are set from GetOrbitPosition at the pack's
time in the constructor, so wells, lights and poses are valid before the first
Update. AddOrbit and CreateOrbit had no caller. Tests not yet run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:31:03 +02:00
Meta Vens
1ad00c045a Aetheria gravity map: body facts B13-B18, rationale and cut sequence for the subtraction and placement cuts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:27:00 +02:00
Meta Vens
11b2d7adbc Aetheria gravity target: rulings of 2026-10-14; Ink pillars leave the flatspace law
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:16:26 +02:00
Meta Vens
818eee2781 Memory-window edge test runs with the subject staying and departing
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:09:15 +02:00
Meta Vens
10aaa8a6ad Pin the book's edge for a departed subject and the stance of one still in the zone
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:02:39 +02:00
Meta Vens
0b86888f79 Aetheria gravity: model page and body facts (map), prior-art research for the eight gravity forks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:19:15 +02:00
Meta Vens
c651938381 Aetheria gravity: target document (honest wells, one potential, height = potential x exaggeration)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:02:37 +02:00
Meta Vens
2ba41373bd Bearing markers read the observer's live stance; a ship out of the zone holds no warnings
The hostility stamp (Reveal.Hostile, Contact.Hostile) is deleted: the observer knows its own
stance live. The real fault was the IFF override being dropped when its subject departs while
the book still remembers it; the override now goes when the book drops the subject (ReleaseStance),
so RememberedHostiles reads IsHostileTo. A ship removed from the zone clears its paint inbox and
incoming warnings at the owner, so the Parent == null gate in ActionGameManager is deleted.
Tests pin both stance directions after the act, the departed override hostile, the exclusive
memory window at the equality, and the empty inbox. Unbuilt until the verify job; Unity side not compiled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:53:58 +02:00
Meta Vens
2ea329e6f1 Remember a hostile's stance with its sighting; pin the lapse, claim and distance rules
RememberedHostiles read the stance the freshest reveal was made under (Reveal.Hostile, surfaced as Contact.Hostile) instead of IsHostileTo now, so a hostile held by the observer's override keeps its marker and the music after it dies or warps, until the bearing lapses. The departure test no longer re-sets the override. New tests: the Detected clause for an in-zone lapsed hostile, position independence for far hostiles on three sides, a within-category lie in ClaimedClasses, and the stale warning of a docked ship. The HUD no longer collects sweeps for a docked ship, and a sweep flash resets the pooled lock indicator's motion.

Untested here: the Unity-side changes (ActionGameManager) have no headless test and no compile has run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:12:44 +02:00
Meta Vens
2b726a998b Pin the claimed-class order: reveals made in reverse name order list by name
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:11:56 +02:00
Meta Vens
0debeab1b3 Bearing-marker tests: restate the stance after a departure, bring the claimer into hearing
A departure drops the observer's IFF override (it lasts the sitting), so the departed-hostile case re-sets it to stand for faction-derived hostility; the gun reveal in the claim fixture needs the target within the Eye's hearing. The previous commit's two new tests fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:08:06 +02:00
Meta Vens
7b12fbe417 Bearing markers for remembered hostile contacts, labelled with their claimed classes, and a sweep flash per ping
Entity.RememberedHostiles and ClaimedClasses derive from the book; music and the new markers share RememberedHostiles. Markers sit on the remembered bearing, never the subject's position; Search warnings are collected per tick into sweep flashes. Unity code is not compiled in this commit's verification (dotnet tests only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:54:43 +02:00
Meta Vens
1ff0e185c5 aetheria-release map: bearing-markers refresh at 3300e16e (body facts BM1-BM10, rationale, cut order)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:50:29 +02:00
Meta Vens
eb5083dd44 Merge eureka/aetheria-fauna-curl-probe (docs only)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:37:13 +02:00
Meta Vens
b285f45c9f Merge eureka/aetheria-fauna-target-r3 (docs only)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:37:06 +02:00
Meta Vens
3300e16e25 Rerun range-falloff apply on master's catalog; RangeFalloffTests reads the generic missile rack name
Catalog (GameData/Aetheria.cc, LFS): master's blob 00c59a64 re-saved by `AetherDb range-falloff apply` -> sha256 7c9cf240ab5e64973603fe3fe5cadaddfeb620ecb75b0ffae9cc2d84ad72bc9f.
Dry run: 27 weapon records to rewrite. A second apply left the bytes unchanged. Decoded against master's: 273 keys equal, schemas
equal, exactly 27 weapon records' payload differs, nothing else.
The test fix renames the design literal SRMM72 to Short-Range Missile Rack (generic-design-names table).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:15:15 +02:00
Meta Vens
97b7566ee9 Merge origin/master into damage-falloff
Conflicts: FusedRounds scenario brief and fixture comment, SmokeRoundsSteppedTests, and AetherDb's usage line keep both sides, with
DeathCluster renamed Cluster Cannon. GameData/Aetheria.cc is master's blob; range-falloff is rerun on it in the next commit.
This commit's catalog does not carry the falloff stats, so CatalogRole/falloff catalog tests do not pass here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:54:44 +02:00
Meta Vens
aff6086c01 Mines are flat by data: refuse falloff on a mine layer, Lay reads no factor; clamp test
A mine layer carries no EffectiveRange or DamageAtMaxRange (StatValidation.ValidateMineLayerFlat, run wherever the other catalog rules run), so RangeFactor is 1 inside its gate for the mine, the AI and the panel without any reader branching on type; Lay's damage is the weapon's damage again. Adds a test that fails without Deliver's min(reach, MaxRange) clamp. Not built or tested yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:06:20 +02:00
Meta Vens
0c5d1f36d2 Test: a targeted fused round reads the falloff at its planned burst point, not the target's distance now
The fire-reads-fire-range mutant survived the first table: no test separated BurstReach from FireRange for a round Fire delivers.
Both commits build (verified on Yggdrasil, Aetheria.Shared.Tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 07:05:11 +02:00
Meta Vens
b713c42503 Read the range factor where the delivery point is decided; mines and unauthored stats go through the one function
A no-lock fused round reads RangeFactor in CommitBurst at the distance it travelled to the hull it stops on; every round whose point
Solve plans reads it once at Fire (PendingShot.DeliveredAtCommit says which). Lay reads it at its own distance 0. PerformanceStat.Evaluate
no longer interpolates a single-value span, so an unauthored +infinity stat evaluates unequipped. Tests: the round that stops short, the
Fused Rounds hull ahead, a mine layer's authored ranges, the group pick at the target's real distance, unequipped SRMM72/Mine Launcher.
Not yet built (verify job follows).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 06:49:56 +02:00
Meta Vens
05a445418c Fauna r4: body-Voronoi probe (Skein drawn as a diagram over the bodies, per metric); map B37-B42
Imagination probe of the operator's sketch: the bodies as Voronoi sites, the distance metric varied
(power, additive, multiplicative, equal potential, flow-stretched, Minkowski), the lattice web pulled
toward the edges. Images, logs and scripts in docs/research/2026-10-14-body-voronoi-probe/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 06:46:26 +02:00
Meta Vens
196edced82 Research: curl flow probe, Nebula Span Lab stretch as a flow (fixed axis; contour axis fails as built), labs noise facts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 06:17:42 +02:00
Meta Vens
55e92b9d44 Research: curl flow probe, candidates to replace FlowField's Tri3D (curl of CultMath simplex, fbm, warped, with slope terms)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 06:12:43 +02:00
Meta Vens
4354ef2c36 Fauna target r4: placement is a trophic DAG over CultMath nodes; map r4 and the trophic DAG probe
Applies rulings habitat-trophic-dag and voronoi-primitive and the operator's
correction that CultMath owns shader/C# parity. The target's Placement law
becomes a layered, donor-controlled DAG (environment, the Skein, producers,
grazers, predators and ambushers) with pointwise and stencil operators only,
nodes in CultMath and compositions in Aetheria; invariants trophic-dag-acyclic,
-pointwise, trophic-nodes-in-cultmath and skein-gate-continuous are added. The
map gains r4 body facts B28-B36 (CultMath's node and parity surface, the tape,
the probe), decisions, two questions and the r4 cut sequence. The probe renders
filament candidates and trophic layers on the orbiting B20 zone and measures
popping and turnover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 05:30:34 +02:00
Meta Vens
66d069890a Merge origin/master into inspection-scanner
Behaviors.cs union list keeps sensor-catalog's ListenerData at 50 and
this cut's ScannerData at 51. No old design-name literal on the branch.
Commit builds unverified until the merge verify job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 05:21:37 +02:00
Meta Vens
521cb6b9d5 Planar-distance test: the target stands in a gravity well, so space and plane disagree
Units cannot be lifted by hand (Entity.Update resets Position.y to the zone's height), so the first version of the test never put the target off the plane. The zone's depth now does it, and the Dart claim's Range is set between the two distances.

Builds: yes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:49:06 +02:00
Meta Vens
47f047865a Research: filament constructions, layered ecology graphs and CPU/GPU dual evaluation, for the trophic DAG
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:47:50 +02:00
Meta Vens
7d2443b511 Fused Rounds: the fragile hull sits inside the DeathCluster's effective range; the smoke round follows the first damage
Falloff made the DeathCluster too slow at the old Fragile position (443 m, past its 308 m effective range). Fragile moves to (80, 240), 253 m on the same bearing, and the scenario test asserts it against the weapon's evaluated EffectiveRange. Inside effective range the kill takes about 2.35 s whatever the distance and a Smoke Proximity round flies 1.75 s, so the round is fired once the hull has lost half its durability; the scenario Brief says the same. Probe: lead 0 landed 1.75 / dead 2.35; lead 1.0 dead 2.35 before the round.

Builds: yes. Not run yet (verified at the final job).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:43:16 +02:00
Meta Vens
e3f65c894f Combat measures the gate's planar distance: a test that fails when it measures space
The planar-distance mutant survived the first mutation table; no test put a target off the ground plane.

Builds: yes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:37:47 +02:00
Meta Vens
0a3636a4b1 Scanner tests: a broken scan resumes from what decayed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:24:05 +02:00
Meta Vens
b26fbbaa95 Scanner decays from the zone clock, not tick order; revert the consumable reorder
bdc8f1d0 ran Execute before the always-updated pass in ConsumableItemEffect.Update; that moved StatModifier timing for every consumable (TheOverdriveLocksAndBoosts failed). The scan's decay is now derived from the zone clock since the last held tick (Scanner.Progress, one site), so it is the same on the tick it breaks for an equipment host and a consumable host, and Scanner is no longer an IAlwaysUpdatedBehavior. The pilot-path test attaches a task-less Agent: a patrolling Minion needs zone orbits. bdc8f1d0 does not pass its own suite; this commit is the intended state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:23:13 +02:00
Meta Vens
cef4f0a746 AI test authors the live gun's damage, cooldown and ranges; Fused Rounds keeps its original Fragile position
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:06:25 +02:00
Meta Vens
d095d9bd61 AI test: report the live gun's range figures when the optimum is wrong
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:04:17 +02:00
Meta Vens
bdc8f1d048 Scanner tests: the pilot-path test steps both ships together and pins no scan before ScanTime
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:04:11 +02:00
Meta Vens
26d0e66ff5 Scanner fix batch: consumable hosts decay on the same tick; hostility, pilot-path, consumable and wire-key-51 tests
ConsumableItemEffect.Update now runs Execute before the always-updated pass, as EquippedItem.Update does, so a consumable-hosted scanner decays on the tick the scan breaks. Tests: AScanReadsNoHostility (hostile/friendly, honest/lying), AScanTakesTheSamePathForAnyPilot (AI-agent ship and agentless ship), AConsumableScannerDecaysOnTheSameTick, ScannerDataRoundTripsAsUnionKey51. Not yet verified at this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:03:20 +02:00
Meta Vens
f03811a40f TheAIHoldsAtItsEffectiveRange authors the live gun's Range too
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:00:16 +02:00
Meta Vens
5cbc555e4a TheAIHoldsAtItsEffectiveRange authors the live gun's falloff
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:00:06 +02:00
Meta Vens
ac1a5d29ed Fused Rounds: Fragile sits inside the DeathCluster's effective range; the AI test refreshes the gun's stats
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 03:57:58 +02:00
Meta Vens
ca810b9b1a TheAIHoldsAtItsEffectiveRange samples over the evaluated stats
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 03:55:29 +02:00
Meta Vens
3ca3fcde7b TheAIHoldsAtItsEffectiveRange lets the Online change land between toggles
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 03:52:32 +02:00
Meta Vens
6c924e79e0 TheAIHoldsAtItsEffectiveRange toggles Enabled, the writable source of Online
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 03:50:10 +02:00
Meta Vens
aed269ad94 Fire clamps the delivery range to Range; smoke-rounds keeps the template's falloff ratio; fixtures follow the gate
Not yet verified together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 03:04:45 +02:00
Meta Vens
6025009b24 range-falloff: rewrite only stale records; EffectiveRange joins the Range role; fixture fixes
EffectiveRange takes Range's role in the roles maps; the lock weapon's array
header grows to 37 with the weapon base's new keys; the AI test builds its
gun's effective range in the encounter fixture. Not yet verified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 02:44:45 +02:00
Meta Vens
b026ea6eb6 Scanner tests: add the third ship before the scan so no zero-dt tick scans untracked 2026-10-14 02:41:58 +02:00
Meta Vens
4bcf5aa61a wip diag2 2026-10-14 02:36:08 +02:00
Meta Vens
9c5e53e88e wip diag 2026-10-14 02:33:20 +02:00
Meta Vens
417dd6c94a Fauna habitat probe kept as research; option images; lattice verdict (B25g, B25h)
Moves the closed-form habitat probe scripts and the images each habitat
question option names into docs/research/2026-10-14-fauna-habitat-probe/,
points the map there, and adds the option renders under the spec's law and
the probe that shows the convergence lattice is FlowField's own structure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 02:30:21 +02:00
Meta Vens
b5573e9b28 Scanner tests: the scanner ship carries no sensor, so its AI cannot ping beside the scan
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 02:27:19 +02:00
Meta Vens
e760333baf Fauna map r3: presence-fade and cycle-sampled pop probes (B25e, B25f)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 02:22:51 +02:00
Meta Vens
177bf4ba50 AetherDb range-falloff: fix a newline literal that did not compile
The previous commit (5e0ec925) does not build tools/AetherDb.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 02:21:46 +02:00
Meta Vens
064167bd06 Fauna target r3: placement is a closed form of slope and flow at run time
Applies ruling aetheria-fauna:ruling:fauna-placement-ruled: the target gains a
Placement law section and replaces fauna-placement-seeded with the placement
invariants closed-form, story, nested and no-pop. The map gains r3 body facts
(B24-B27, the closed-form density probe), decisions, the two questions and the
r3 cut sequence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 02:16:36 +02:00
Meta Vens
5e0ec92509 Damage falloff: effective range and damage at max range replace DamageCurve
WeaponData.RangeFactor is the one function of damage over range: 0 outside
[MinRange, Range], 1 up to EffectiveRange, one lerp to DamageAtMaxRange.
DamageCurve (key 7, retired) and its formula parameters, CurveField's
BezierCurve path, and every fixture curve line are deleted. Fire freezes
damage times the factor at BurstReach; the AI's DPS reads, Combat's optimum
sampling and the properties panel chart read the same function. AetherDb
gains range-falloff (command only; the catalog is re-saved in a later commit).
Cut and wiring land in one commit rather than two; this commit has not been
built yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 02:16:04 +02:00
Meta Vens
86843648a7 Add the inspection scanner: a held scan on a tracked target reveals all its gear
Scanner (ScannerData, Union 51) builds Progress while the ship's Target is tracked within Range, paints the target (PaintMode.Scan), emits at Range / InterceptFactor and decays in IAlwaysUpdatedBehavior.Update when not held. At 1 Entity.RecordInspection records every fitted non-hull non-destroyed item through RecordReveal with channel null and the subject's claim. Reveal.Channel and RecordReveal take SignatureChannel?. Scanner.cs.meta is hand-written with a fresh guid; Unity has not compiled this yet. Builds on the headless test project only if the next verify says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 02:07:17 +02:00
Meta Vens
198d6e28dd Research: fix header line breaks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:57:20 +02:00
Meta Vens
1f8492c328 Research: name the ruling the closed-form placement facts serve; drop a scratch path
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:56:55 +02:00
Meta Vens
52eecd6da1 Research: closed-form, runtime fauna placement prior art, for fauna target r3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:56:32 +02:00
Meta Vens
b3d1e92904 FlightTests: the patrolling fixture starts far from its circuit point
At distance 0 MoveToOrbit hands back to patrol every tick and the patrol
state's unconditional transition wins over the combat interrupt, so no
combat transition was ever reachable in the fixture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:52:43 +02:00
Meta Vens
a2ad79a68e FlightTests: combat transitions are looked for across the patrol sub-states' own hand-overs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:33:31 +02:00
Meta Vens
c1b83e84e5 FlightTests: the combat transition tests give the pilot a track it sees
Entity clears a target its pilot does not see; the earlier commit set one
without perception, so the pilot never entered combat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:31:32 +02:00
Meta Vens
1fea9cd2e1 FlightTests: pin the executor's combat and root transitions
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:13:50 +02:00
Meta Vens
f91f658ad2 Flight: a member that cannot see the engaged track steers on its bearing
The member kept Target to its own sensors but fell back to patrol while a
flightmate fought; it now follows the engaged track as it followed the
hailed one (ruling shared-track-bearing-only: a bearing, never targeting data).

Builds: not yet verified at this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:13:48 +02:00
Meta Vens
e95116b670 Tests: removal order, per-faction flights, zone tick, exponent margin
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 16:30:34 +02:00
Meta Vens
7217112873 FlightTests: a patrolling pilot never falls back to the root state
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 16:21:37 +02:00
Meta Vens
290b34331a Minion: a task change leaves its own state only; the walk no longer crosses into the other task's states
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 16:20:33 +02:00
Meta Vens
c3c6d12db8 FlightTests: boundary, selection, trespass, standoff and task-change pins
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 16:12:53 +02:00
Meta Vens
c8c544f489 PatrolOrbitsState: compare the task by reference
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 14:35:00 +02:00
Meta Vens
080dbed07d Catalog: doctrine-catalog apply (Zhestokost, Lucent Media, Aeronautics Unlimited)
Ran on Yggdrasil against the base catalog; the Pirates have no Faction record
(follow-up pirates-catalog-record), so their doctrine is not written. Miss
Terri's untouched.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 14:32:15 +02:00
Meta Vens
9978d28763 PatrolOrbitsState: arrival advances the circuit only while the pilot is still on its task
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 14:29:47 +02:00
Meta Vens
90462179b8 SteeringTests: Agent takes its RoleDoctrine
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 14:21:04 +02:00
Meta Vens
8f42265de3 Faction play 1: typed doctrine on Faction and one flight per faction per zone
FactionDoctrine/RoleDoctrine (Faction key 17), Flight as the only writer of
members' Target and Task, hail/grace/compliance, FollowTask/FollowState,
agent removal on Entities removal, doctrine-catalog command, FlightTests.
Not yet built or run; verification follows in later commits.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 14:18:41 +02:00
798 changed files with 49161 additions and 17469 deletions

2
.gitignore vendored
View file

@ -94,7 +94,7 @@ crashlytics-build.properties
Packages/packages-lock.json
GameData/run.cc
GameData/player.cc
Vault/Aetheria/.obsidian/plugins/copilot/data.json
GameData/RemovedMods/
# Agent scratch, sandboxed toolchain caches, and throwaway checkouts. These had
# grown to 47 untracked entries; the worktree must stay legible, so they never

2
.lfsconfig Normal file
View file

@ -0,0 +1,2 @@
[lfs]
url = https://eureka.gamecult.org/forge/GameCult/Aetheria.git/info/lfs

View file

@ -146,6 +146,5 @@ MonoBehaviour:
ActiveEmission: 100
ArmedEmission: 1000
RotationSpeed: 1
GridOffset: 0
HitEffect: {fileID: 1121192042590650, guid: e1b08032bec16ba4eb1ae23cbd572664, type: 3}
HitEffectScale: 10

View file

@ -81,87 +81,6 @@ MeshRenderer:
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &7783244517717541194
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7783244517717541195}
- component: {fileID: 7783244517717541175}
- component: {fileID: 7783244517717541174}
m_Layer: 9
m_Name: Gravity Well
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &7783244517717541195
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7783244517717541194}
m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 7783244518521719148}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!33 &7783244517717541175
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7783244517717541194}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &7783244517717541174
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7783244517717541194}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 44a8bbb89acb4e34c9f41a9da5842bb4, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &7783244518317531027
GameObject:
m_ObjectHideFlags: 0
@ -289,7 +208,6 @@ Transform:
m_Children:
- {fileID: 589192530128514016}
- {fileID: 7783244518317531036}
- {fileID: 7783244517717541195}
- {fileID: 7783244516809707565}
m_Father: {fileID: 0}
m_RootOrder: 0
@ -307,7 +225,6 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
Body: {fileID: 7783244518317531038}
GravityWell: {fileID: 7783244517717541174}
Icon: {fileID: 7783244516809707566}
Generator: {fileID: 741236912059895623}
--- !u!114 &741236912059895623

View file

@ -8,12 +8,12 @@ using UnityEditor.AddressableAssets.Settings;
using UnityEditor.Build.Reporting;
using Debug = UnityEngine.Debug;
// The one way to make a Win64 player directory. Owns what the directory contains: Aetheria.exe and its data,
// The one way to make a player directory, for Windows or Linux. Owns what the directory contains: the player and its data,
// GameData/Aetheria.cc, GameData/Narrative/**, GameData/Mods/** and ModTools/AetherDb. Manual builds, smokes and
// release cuts all call this and stage nothing themselves.
//
// Unity -batchmode -quit -projectPath <repo> -executeMethod AetheriaBuild.Build
// -aetheriaVersion <semver> -aetheriaOut <dir> -logFile <log>
// -aetheriaVersion <semver> -aetheriaOut <dir> [-aetheriaTarget win64|linux64] -logFile <log>
//
// AetherDb is published from tools/AetherDb, so the headless build's CULTLIB_ROOT / CULTMATH_ROOT (Directory.Build.props)
// must be set for dotnet when the defaults do not hold the pinned revisions.
@ -22,6 +22,14 @@ public static class AetheriaBuild
{
private static readonly Regex SemVer = new Regex(@"^\d+\.\d+\.\d+([-+][0-9A-Za-z.-]+)?$");
// A target: the player Unity builds, its executable, and the runtime AetherDb is published for.
private static (BuildTarget target, string player, string runtime, string aetherDb) Target(string name) => name switch
{
"win64" => (BuildTarget.StandaloneWindows64, "Aetheria.exe", "win-x64", "AetherDb.exe"),
"linux64" => (BuildTarget.StandaloneLinux64, "Aetheria.x86_64", "linux-x64", "AetherDb"),
_ => throw new InvalidOperationException($"-aetheriaTarget {name}: expected win64 or linux64"),
};
public static void Build()
{
var step = "arguments";
@ -30,6 +38,7 @@ public static class AetheriaBuild
var version = Argument("-aetheriaVersion");
if (!SemVer.IsMatch(version)) throw new InvalidOperationException("-aetheriaVersion is not a semver");
var output = Path.GetFullPath(Argument("-aetheriaOut"));
var target = Target(Argument("-aetheriaTarget", "win64"));
var repoRoot = Directory.GetParent(UnityEngine.Application.dataPath).FullName;
step = "version";
@ -46,8 +55,8 @@ public static class AetheriaBuild
var report = BuildPipeline.BuildPlayer(new BuildPlayerOptions
{
scenes = scenes,
locationPathName = Path.Combine(output, "Aetheria.exe"),
target = BuildTarget.StandaloneWindows64,
locationPathName = Path.Combine(output, target.player),
target = target.target,
options = BuildOptions.None,
});
if (report.summary.result != BuildResult.Succeeded || report.summary.totalErrors != 0)
@ -57,7 +66,7 @@ public static class AetheriaBuild
PlayerStaging.Stage(repoRoot, output);
step = "aetherdb";
Publish(repoRoot, Path.Combine(output, "ModTools"));
Publish(repoRoot, Path.Combine(output, "ModTools"), target.runtime, target.aetherDb);
step = "burst";
var burst = Path.Combine(output, "Aetheria_BurstDebugInformation_DoNotShip");
@ -74,11 +83,11 @@ public static class AetheriaBuild
EditorApplication.Exit(0);
}
private static void Publish(string repoRoot, string modTools)
private static void Publish(string repoRoot, string modTools, string runtime, string aetherDb)
{
var info = new ProcessStartInfo("dotnet")
{
Arguments = $"publish tools/AetherDb -c Release -r win-x64 --self-contained -p:PublishSingleFile=true -o \"{modTools}\"",
Arguments = $"publish tools/AetherDb -c Release -r {runtime} --self-contained -p:PublishSingleFile=true -o \"{modTools}\"",
WorkingDirectory = repoRoot,
UseShellExecute = false,
RedirectStandardOutput = true,
@ -92,15 +101,16 @@ public static class AetheriaBuild
if (process.ExitCode != 0)
throw new InvalidOperationException($"dotnet publish exited {process.ExitCode}:\n{text}\n{errors.Result}");
}
if (!File.Exists(Path.Combine(modTools, "AetherDb.exe")))
throw new InvalidOperationException("dotnet publish produced no ModTools/AetherDb.exe");
if (!File.Exists(Path.Combine(modTools, aetherDb)))
throw new InvalidOperationException($"dotnet publish produced no ModTools/{aetherDb}");
}
private static string Argument(string name)
private static string Argument(string name, string fallback = null)
{
var args = Environment.GetCommandLineArgs();
var at = Array.IndexOf(args, name);
if (at < 0 || at + 1 >= args.Length) throw new InvalidOperationException(name + " is required");
if (at < 0 || at + 1 >= args.Length)
return fallback ?? throw new InvalidOperationException(name + " is required");
return args[at + 1];
}
}

View file

@ -2,9 +2,8 @@ using System;
using System.IO;
// What a player directory holds beside the executable's data: GameData/Aetheria.cc, GameData/Narrative/** and, when
// the repo has one, GameData/Mods/**. Nothing else under GameData ships (run.cc and player.cc are the player's own;
// SoundbanksInfo.json is read by no code). Unity-free so the headless suite can prove the rules; AetheriaBuild is the
// only caller.
// the repo has one, GameData/Mods/**. Nothing else under GameData ships (run.cc and player.cc are the player's own).
// Unity-free so the headless suite can prove the rules; AetheriaBuild is the only caller.
internal static class PlayerStaging
{
public const string Catalog = "Aetheria.cc";

View file

@ -7,8 +7,8 @@ Material:
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Gravity Well
m_Shader: {fileID: 4800000, guid: 268da230635cfb34ab4ed6ad39503fa0, type: 3}
m_Name: Gravity Field
m_Shader: {fileID: 4800000, guid: 08d8efa80af84065b54c9bfed784e435, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
@ -57,8 +57,6 @@ Material:
m_Offset: {x: 0, y: 0}
m_Floats:
- _BumpScale: 1
- _Cutoff: 1
- _Depth: 80
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
@ -68,7 +66,6 @@ Material:
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _Power: 16
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1

View file

@ -1,83 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Gravity Waves
m_Shader: {fileID: 4800000, guid: 8a8e593b17063b54ea02f7ea5363b59d, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _BumpScale: 1
- _Cutoff: 0.5
- _Depth: 3.5
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _Frequency: 27.43
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _Phase: 0
- _Power: 8
- _SinePower: 1.25
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []

View file

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 11fbaa87dcc7a3743a6bd3aa5c86e5ae
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View file

@ -8,7 +8,7 @@ Material:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Sector Tint
m_Shader: {fileID: 4800000, guid: 268da230635cfb34ab4ed6ad39503fa0, type: 3}
m_Shader: {fileID: 4800000, guid: 782740c1a3d7455ea75dc87fafdc8432, type: 3}
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
@ -59,8 +59,8 @@ Material:
m_Ints: []
m_Floats:
- _BumpScale: 1
- _Cutoff: 1
- _Depth: 0.025
- _EdgeBrightness: 0.12
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _Frequency: 6.2832
@ -72,7 +72,6 @@ Material:
- _OcclusionStrength: 1
- _Parallax: 0.02
- _Phase: 0
- _Power: 2
- _SinePower: 1.25
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1

View file

@ -1,80 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Wormhole Gravity 1
m_Shader: {fileID: 4800000, guid: 268da230635cfb34ab4ed6ad39503fa0, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _BumpScale: 1
- _Cutoff: 1
- _Depth: 54
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _Power: 4
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []

View file

@ -1,80 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Wormhole Gravity 1
m_Shader: {fileID: 4800000, guid: 268da230635cfb34ab4ed6ad39503fa0, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _BumpScale: 1
- _Cutoff: 1
- _Depth: 54
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _Power: 4
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []

View file

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 0ec0689c58b99c64297b3e429e1d9112
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,82 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Wormhole Gravity
m_Shader: {fileID: 4800000, guid: 268da230635cfb34ab4ed6ad39503fa0, type: 3}
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _BumpScale: 1
- _Cutoff: 0.15
- _Depth: 2000
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _Power: 64
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []

View file

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: a9ef17c902709ce41a837c54b809184b
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,84 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Wormhole Waves
m_Shader: {fileID: 4800000, guid: 8681ac064d59d15458d16cc9b884262c, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _BumpScale: 1
- _Cutoff: 0.5
- _Depth: 33.9
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _Frequency: 2
- _FrequencyDist: 60
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _Power: 2
- _SinePower: 1.25
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _Speed: 15
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []

View file

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: ef2f6090d5cc14a4e9ce38def3bed9ac
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,73 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Fog Marching 1
m_Shader: {fileID: 4800000, guid: 7a890b6ceccf53c44bfc54b18b5daa58, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _Displacement:
m_Texture: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _Patch:
m_Texture: {fileID: 8400000, guid: c618a6b20b3c7394590c59a9fa467e86, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _Surface:
m_Texture: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _TintTexture:
m_Texture: {fileID: 8400000, guid: d196505464047f04a97f28689041209b, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _AlphaFloor: 0.01
- _DepthBlend: 512
- _DepthCeiling: 4096
- _FlowSpeed: 2
- _Gamma: 0.5
- _GridCeilingBlend: -300
- _GridCeilingDensity: 0.01
- _GridCeilingFollow: 0
- _GridCeilingOffset: 0
- _GridFillDensity: 0.001
- _GridFloorBlend: 100
- _GridFloorDensity: 2
- _GridFloorOffset: 20
- _GridPatchBlend: 50
- _GridPatchDensity: 2
- _NoiseExponent: 2.25
- _NoiseFrequency: 0.015
- _NoiseSpeed: 0
- _NoiseStrength: 500
- _SafetyDistance: 20
- _Scattering: 0
- _ScatteringDensityExponent: 0.5
- _ScatteringDistExponent: 0.5
- _ScatteringMinDist: 500
- _StepExponent: 2
- _TintExponent: 0.1
- _TurbulenceAmplitude: 100
- _TurbulenceScale: 100
m_Colors:
- _Tint: {r: 0.0627451, g: 0.0627451, b: 0.0627451, a: 1}
m_BuildTextureStacks: []

View file

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: a72896cb7097bfa46a1a8f4092e47c72
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,99 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Fog Marching Main Menu
m_Shader: {fileID: 4800000, guid: 7a890b6ceccf53c44bfc54b18b5daa58, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _Displacement:
m_Texture: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _NebulaPatch:
m_Texture: {fileID: 8400000, guid: c618a6b20b3c7394590c59a9fa467e86, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _NebulaPatchHeight:
m_Texture: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _NebulaSurfaceHeight:
m_Texture: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _NebulaTint:
m_Texture: {fileID: 8400000, guid: d196505464047f04a97f28689041209b, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _Patch:
m_Texture: {fileID: 8400000, guid: c618a6b20b3c7394590c59a9fa467e86, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _Surface:
m_Texture: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _TintTexture:
m_Texture: {fileID: 8400000, guid: d196505464047f04a97f28689041209b, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _AlphaFloor: 0
- _DepthBlend: 512
- _DepthCeiling: 1024
- _Exposure: 100
- _FlowAmplitude: 1
- _FlowScale: 1
- _FlowScroll: 1
- _FlowSpeed: 5
- _Gamma: 0.3
- _GridCeilingBlend: -300
- _GridCeilingDensity: 0.01
- _GridCeilingFollow: 0
- _GridCeilingOffset: 0
- _GridFillDensity: 0.001
- _GridFloorBlend: 50
- _GridFloorDensity: 1
- _GridFloorOffset: 100
- _GridPatchBlend: 50
- _GridPatchDensity: 1
- _NebulaFillDensity: 0.00075
- _NebulaFloorBlend: 50
- _NebulaFloorDensity: 1
- _NebulaFloorOffset: 80
- _NebulaPatchBlend: 25
- _NebulaPatchDensity: 0.1
- _NoiseExponent: 2
- _NoiseFrequency: 0.02
- _NoiseSpeed: 0.00001
- _NoiseStrength: 200
- _SafetyDistance: 50
- _Scattering: 0
- _ScatteringDensityExponent: 8
- _ScatteringDistExponent: 0.5
- _ScatteringMinDist: 1
- _StepExponent: 2
- _TintExponent: 1.2
- _TurbulenceAmplitude: 20
- _TurbulenceScale: 30
m_Colors:
- _Tint: {r: 0.09411765, g: 0.09411765, b: 0.09411765, a: 1}
m_BuildTextureStacks: []

View file

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: e21702a5e6e98b7409d6913663ceab7e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,65 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Fog Marching Test
m_Shader: {fileID: 4800000, guid: 7a890b6ceccf53c44bfc54b18b5daa58, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _Displacement:
m_Texture: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _Patch:
m_Texture: {fileID: 8400000, guid: c618a6b20b3c7394590c59a9fa467e86, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _Surface:
m_Texture: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _TintTexture:
m_Texture: {fileID: 8400000, guid: d196505464047f04a97f28689041209b, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _AlphaFloor: 0
- _DepthBlend: 512
- _DepthCeiling: 4096
- _Gamma: 0.66
- _GridCeilingBlend: -300
- _GridCeilingDensity: 0.01
- _GridCeilingFollow: 0
- _GridCeilingOffset: 0
- _GridFillDensity: 0.000025
- _GridFloorBlend: 100
- _GridFloorDensity: 4
- _GridFloorOffset: 0
- _GridPatchBlend: 5
- _GridPatchDensity: 0.1
- _NoiseFrequency: 0.01
- _NoiseSpeed: 0.02
- _NoiseStrength: 50
- _SafetyDistance: 0
- _StepExponent: 2
- _TintExponent: 8
m_Colors:
- _Tint: {r: 2.0078435, g: 2.0078435, b: 2.0078435, a: 1}
m_BuildTextureStacks: []

View file

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: ad63650ae6d6cc542a5aad16694b3c46
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,99 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Fog Marching
m_Shader: {fileID: 4800000, guid: 7a890b6ceccf53c44bfc54b18b5daa58, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _Displacement:
m_Texture: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _NebulaPatch:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _NebulaPatchHeight:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _NebulaSurfaceHeight:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _NebulaTint:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _Patch:
m_Texture: {fileID: 8400000, guid: c618a6b20b3c7394590c59a9fa467e86, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _Surface:
m_Texture: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _TintTexture:
m_Texture: {fileID: 8400000, guid: d196505464047f04a97f28689041209b, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _AlphaFloor: 0.01
- _DepthBlend: 512
- _DepthCeiling: 4096
- _Exposure: 10
- _FlowAmplitude: 512
- _FlowScale: 1024
- _FlowScroll: 0
- _FlowSpeed: 1
- _Gamma: 0.3
- _GridCeilingBlend: -300
- _GridCeilingDensity: 0.01
- _GridCeilingFollow: 0
- _GridCeilingOffset: 0
- _GridFillDensity: 0.0005
- _GridFloorBlend: 50
- _GridFloorDensity: 1
- _GridFloorOffset: 10
- _GridPatchBlend: 150
- _GridPatchDensity: 1
- _NebulaFillDensity: 0.00075
- _NebulaFloorBlend: 150
- _NebulaFloorDensity: 1
- _NebulaFloorOffset: 0
- _NebulaPatchBlend: 51.47
- _NebulaPatchDensity: 0.25
- _NoiseExponent: 3
- _NoiseFrequency: 0.005
- _NoiseSpeed: 0
- _NoiseStrength: -400
- _SafetyDistance: 200
- _Scattering: 50
- _ScatteringDensityExponent: 1
- _ScatteringDistExponent: 1.5
- _ScatteringMinDist: 10
- _StepExponent: 2
- _TintExponent: 2
- _TurbulenceAmplitude: 20
- _TurbulenceScale: 60
m_Colors:
- _Tint: {r: 0.01, g: 0.01, b: 0.01, a: 1}
m_BuildTextureStacks: []

View file

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 2ff39f5d101241043ba1dcde19d92f77
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,31 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: MapAsteroid
m_Shader: {fileID: 4800000, guid: 22e2fc798f044a3aa2f6cd71f1df09a7, type: 3}
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 0
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: 3000
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _MainTex:
m_Texture: {fileID: 2800000, guid: 060aefd5a3d5ced4dabd9190a140e76e, type: 3}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats: []
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
m_BuildTextureStacks: []

View file

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 244f3e4c0c030a24b8d7aa944895bdcf
guid: c1012d5466454651b7279be93ef9af88
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000

View file

@ -189,7 +189,6 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
RotationSpeed: 1
GridOffset: 0
LabelFadeDuration: 0.5
LabelPersistDuration: 3
LabelDisplayAngle: 15

View file

@ -162,7 +162,6 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
RotationSpeed: 1
GridOffset: 0
LabelFadeDuration: 0.5
LabelPersistDuration: 3
LabelDisplayAngle: 15

View file

@ -162,7 +162,6 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
RotationSpeed: 1
GridOffset: 0
LabelFadeDuration: 0.5
LabelPersistDuration: 3
LabelDisplayAngle: 15

View file

@ -162,7 +162,6 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
RotationSpeed: 1
GridOffset: 0
LabelFadeDuration: 0.5
LabelPersistDuration: 3
LabelDisplayAngle: 15

View file

@ -162,7 +162,6 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
RotationSpeed: 1
GridOffset: 0
LabelFadeDuration: 0.5
LabelPersistDuration: 3
LabelDisplayAngle: 15

View file

@ -30,8 +30,6 @@ Transform:
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 3973935122748960680}
- {fileID: 3973935121058605868}
- {fileID: 3973935122114073483}
- {fileID: 3973935121358855036}
m_Father: {fileID: 0}
m_RootOrder: 0
@ -49,94 +47,9 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
Body: {fileID: 3973935122748960683}
GravityWell: {fileID: 3973935121058605871}
Icon: {fileID: 3973935121358855037}
Generator: {fileID: 0}
SunMaterial: {fileID: 3973935122748960685}
GravityWaves: {fileID: 3973935122114073485}
--- !u!1 &3973935121058605867
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3973935121058605868}
- component: {fileID: 3973935121058605856}
- component: {fileID: 3973935121058605871}
m_Layer: 9
m_Name: Gravity Well
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3973935121058605868
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3973935121058605867}
m_LocalRotation: {x: 0.7071068, y: -0, z: -0, w: 0.7071068}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 3973935120991099173}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!33 &3973935121058605856
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3973935121058605867}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &3973935121058605871
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3973935121058605867}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 44a8bbb89acb4e34c9f41a9da5842bb4, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &3973935121358855035
GameObject:
m_ObjectHideFlags: 0
@ -220,89 +133,6 @@ MeshRenderer:
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &3973935122114073482
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3973935122114073483}
- component: {fileID: 3973935122114073486}
- component: {fileID: 3973935122114073485}
m_Layer: 10
m_Name: Gravity Waves
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3973935122114073483
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3973935122114073482}
m_LocalRotation: {x: 0.7071068, y: -0, z: -0, w: 0.7071068}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 3973935120991099173}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!33 &3973935122114073486
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3973935122114073482}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &3973935122114073485
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3973935122114073482}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 11fbaa87dcc7a3743a6bd3aa5c86e5ae, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &3973935122748960663
GameObject:
m_ObjectHideFlags: 0

View file

@ -83,89 +83,6 @@ MeshRenderer:
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &8859079004671924238
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8859079004671924239}
- component: {fileID: 8859079004671924339}
- component: {fileID: 8859079004671924338}
m_Layer: 9
m_Name: Gravity Well
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &8859079004671924239
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8859079004671924238}
m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 8859079004937162792}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!33 &8859079004671924339
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8859079004671924238}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &8859079004671924338
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8859079004671924238}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 44a8bbb89acb4e34c9f41a9da5842bb4, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &8859079004937162791
GameObject:
m_ObjectHideFlags: 0
@ -197,7 +114,6 @@ Transform:
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 8859079005270853336}
- {fileID: 8859079004671924239}
- {fileID: 8859079003226229097}
m_Father: {fileID: 0}
m_RootOrder: 0
@ -215,7 +131,6 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
Body: {fileID: 8859079005270853338}
GravityWell: {fileID: 8859079004671924338}
Icon: {fileID: 8859079003226229098}
Generator: {fileID: -7153121627769399805}
--- !u!114 &-7153121627769399805

View file

@ -30,8 +30,6 @@ Transform:
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 7852221720971737233}
- {fileID: 7852221720969441490}
- {fileID: 7852221720741322892}
- {fileID: 7852221720816817928}
- {fileID: 7852221720964551245}
m_Father: {fileID: 0}
@ -50,96 +48,11 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
Body: {fileID: 7852221720971737229}
GravityWell: {fileID: 7852221720969441484}
Icon: {fileID: 7852221720964551246}
Generator: {fileID: 0}
SunMaterial: {fileID: 7852221720971737234}
GravityWaves: {fileID: 7852221720741322894}
FogTint: {fileID: 7852221720816817929}
Light: {fileID: 8064377225639657765}
--- !u!1 &7852221720741322899
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7852221720741322892}
- component: {fileID: 7852221720741322895}
- component: {fileID: 7852221720741322894}
m_Layer: 10
m_Name: Gravity Waves
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &7852221720741322892
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7852221720741322899}
m_LocalRotation: {x: 0.7071068, y: -0, z: -0, w: 0.7071068}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 7852221720480299767}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!33 &7852221720741322895
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7852221720741322899}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &7852221720741322894
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7852221720741322899}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 11fbaa87dcc7a3743a6bd3aa5c86e5ae, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &7852221720816817935
GameObject:
m_ObjectHideFlags: 0
@ -306,89 +219,6 @@ MeshRenderer:
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &7852221720969441489
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7852221720969441490}
- component: {fileID: 7852221720969441485}
- component: {fileID: 7852221720969441484}
m_Layer: 9
m_Name: Gravity Well
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &7852221720969441490
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7852221720969441489}
m_LocalRotation: {x: 0.7071068, y: -0, z: -0, w: 0.7071068}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 7852221720480299767}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!33 &7852221720969441485
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7852221720969441489}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &7852221720969441484
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7852221720969441489}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 44a8bbb89acb4e34c9f41a9da5842bb4, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &7852221720971737232
GameObject:
m_ObjectHideFlags: 0

View file

@ -1,167 +1,5 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &1826894967093516808
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1030590306481442051}
- component: {fileID: 3216580199816124320}
- component: {fileID: 3211969211988968959}
m_Layer: 9
m_Name: Gravity Well
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1030590306481442051
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1826894967093516808}
m_LocalRotation: {x: 0.7071068, y: -0, z: -0, w: 0.7071068}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1024, y: 1024, z: 1024}
m_Children: []
m_Father: {fileID: 463084220523011477}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!33 &3216580199816124320
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1826894967093516808}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &3211969211988968959
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1826894967093516808}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 244f3e4c0c030a24b8d7aa944895bdcf, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &3908760799087214860
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 581261949390603689}
- component: {fileID: 987447324816385543}
- component: {fileID: 3959510878930752720}
m_Layer: 9
m_Name: Gravity Waves
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &581261949390603689
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3908760799087214860}
m_LocalRotation: {x: 0.7071068, y: -0, z: -0, w: 0.7071068}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 512, y: 512, z: 512}
m_Children: []
m_Father: {fileID: 463084220523011477}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!33 &987447324816385543
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3908760799087214860}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &3959510878930752720
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3908760799087214860}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: ef2f6090d5cc14a4e9ce38def3bed9ac, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &4837153162502999620
GameObject:
m_ObjectHideFlags: 0
@ -189,9 +27,6 @@ Transform:
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 8222188360457572195}
- {fileID: 1030590306481442051}
- {fileID: 581261949390603689}
- {fileID: 8249320701965103923}
m_Father: {fileID: 0}
m_RootOrder: 0
@ -277,84 +112,3 @@ MeshRenderer:
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &7647029665738834910
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8222188360457572195}
- component: {fileID: 2468954688819198096}
- component: {fileID: 8290522694236574882}
m_Layer: 9
m_Name: Gravity Pit
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &8222188360457572195
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7647029665738834910}
m_LocalRotation: {x: 0.7071068, y: -0, z: -0, w: 0.7071068}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 512, y: 512, z: 512}
m_Children: []
m_Father: {fileID: 463084220523011477}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!33 &2468954688819198096
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7647029665738834910}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &8290522694236574882
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7647029665738834910}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: a9ef17c902709ce41a837c54b809184b, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}

View file

@ -1,111 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &992254348637625916
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1779911639310264026}
- component: {fileID: 349597964696402918}
- component: {fileID: 2844878085845264008}
- component: {fileID: 917803079556930035}
- component: {fileID: 4397878771830533054}
m_Layer: 14
m_Name: Asteroid Belt
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1779911639310264026
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 992254348637625916}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &349597964696402918
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 992254348637625916}
m_Mesh: {fileID: 0}
--- !u!23 &2844878085845264008
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 992254348637625916}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: c018d99199afeca448c64b9022ac7270, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 0
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!64 &917803079556930035
MeshCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 992254348637625916}
m_Material: {fileID: 0}
m_IsTrigger: 0
m_Enabled: 1
serializedVersion: 4
m_Convex: 0
m_CookingOptions: 30
m_Mesh: {fileID: 0}
--- !u!114 &4397878771830533054
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 992254348637625916}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 52538a3309c36fc4583c964a1c0b705f, type: 3}
m_Name:
m_EditorClassIdentifier:

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 1f1bfa2175a13b647888e2949e0cb138
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -223,7 +223,6 @@ Transform:
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 5321404120083801960}
- {fileID: 5321404118931821940}
- {fileID: 5321404119494867799}
- {fileID: 1093865191529778971}
@ -245,7 +244,7 @@ MonoBehaviour:
Label: {fileID: 5321404119494867796}
Message: {fileID: 6196070888394201391}
Children: []
GravityMesh: {fileID: 5321404120083801962}
GravityMesh: {fileID: 0}
--- !u!1 &5321404118931821943
GameObject:
m_ObjectHideFlags: 0
@ -564,93 +563,3 @@ MonoBehaviour:
m_hasFontAssetChanged: 0
m_renderer: {fileID: 5321404119494867795}
m_maskType: 0
--- !u!1 &5321404120083801964
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5321404120083801960}
- component: {fileID: 5321404120083801963}
- component: {fileID: 5321404120083801962}
m_Layer: 8
m_Name: Gravity Well
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &5321404120083801960
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5321404120083801964}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 10, y: 10, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 5321404118521994135}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &5321404120083801963
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5321404120083801964}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &5321404120083801962
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5321404120083801964}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 44a8bbb89acb4e34c9f41a9da5842bb4, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 0
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}

View file

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 0f3e518a4c9bcc2469abe6cebdcb3691
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/Australia.csv (Stored with Git LFS)

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:01bb478f4433dc42d5533e5030ef103ad163deca6bc40fb412025acd12e35c73
3 size 442795

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 8ba25d5d0ecee31408c7ce7460b4e015
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/Congo.csv (Stored with Git LFS)

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:c84ff44083e4f0d9d9314e7b7095ed4ce2955905f26e7926faa09ed222655729
3 size 506722

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 1e43b383bf860524986a6947496776d0
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/Germany.csv (Stored with Git LFS)

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:73e4e7231d448879d0a6d8524b3200be75fc8f5011a70cfd670b0897d07eca10
3 size 1284208

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: ed470b8eb6cc68c4485ddc05d5a17ca1
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/India.csv (Stored with Git LFS)

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:96c66414822c43a5451ce1910c1e545dedb224b0f3e79bc54f945dd484afc6a2
3 size 3229324

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: ca62f6d649360184aaa975c2716dfb27
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/Iran.csv (Stored with Git LFS)

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:b70fbbe6b4832eb1a3f35844bd87c0eb479fc77b6e59d6600b64cbbe7cc1bf16
3 size 1073119

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: e2d60c7158f6a01418d78e46c5845729
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/Japan.csv (Stored with Git LFS)

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:d58dc0227cc6e3b7622726c155031467b6d7314623c7e3a33aad8ce1462b2c5b
3 size 1031132

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 5dd450a6f5ec92d49871674b33509135
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/Mexico.csv (Stored with Git LFS)

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:9a654b8451a08a0c769712f4fb1d96c3c661457a88bb57ec4288eab36eab7ec1
3 size 633432

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: b75e17ea31f6a62488628bf9605bd48c
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/Russia.csv (Stored with Git LFS)

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:f4ed581eef681e5357a14efe4e6a1fc52370def7ada6565f4b953e410fa7f39e
3 size 2375319

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 5c1aee30ccf3a1e44bddeb4d550edbf0
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/Swahili.csv (Stored with Git LFS)

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:fb0f44e7948c637a2b0f9e0ca098b2f4689d065fe6484723acf0bfa34a26d0a5
3 size 501002

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 63e21276398c8b0449e01d97a088add1
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/Thailand.csv (Stored with Git LFS)

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:888c929f0e29728369448e6981bb8a043c49610f4c4390a2cb549bf6b494d3dc
3 size 143084

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 2923f8bd3a2f0fb4084f64138e6d793d
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/USA.csv (Stored with Git LFS)

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:314a30df2033795f04738589c660991e4310f1bfe0578c71bda2a9a72d8a617f
3 size 954928

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: a06c0e26f73d51f489303deadad77030
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load diff

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 790ef350f8658b04480e0645919a21fa
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/pleiades.csv (Stored with Git LFS)

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:45b0dbc07d492958b26de3695427076d270bdd0525ab45c415be72560ebccb5f
3 size 400944

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: b532694a5ec3cc6458d42aa2835537b5
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Settings.asset (Stored with Git LFS)

Binary file not shown.

View file

@ -409,13 +409,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 23420356}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068}
m_LocalPosition: {x: 0, y: -5, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1037490739}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!23 &23420359
MeshRenderer:
m_ObjectHideFlags: 0
@ -8037,7 +8037,7 @@ Camera:
m_Depth: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 5632
m_Bits: 5120
m_RenderingPath: 1
m_TargetTexture: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2}
m_TargetDisplay: 0
@ -16322,7 +16322,7 @@ MonoBehaviour:
Settings: {fileID: 11400000, guid: cd2344e2001a37544aab0960c164d4ff, type: 2}
ZoneRoot: {fileID: 2095699057}
SectorBrushes: {fileID: 1037490739}
SectorBoundaryBrush: {fileID: 1476765550}
AmbientLight: {fileID: 23420357}
SceneCamera: {fileID: 887677622}
FogCameras:
- {fileID: 559840917}
@ -16333,70 +16333,98 @@ MonoBehaviour:
- {fileID: 488792362}
- {fileID: 970624071}
- {fileID: 2095741464}
FogMaterial: {fileID: 2100000, guid: 2ff39f5d101241043ba1dcde19d92f77, type: 2}
FogFarFadeFraction: 0.125
FarPlaneDistanceMultiplier: 1
AsteroidMeshes:
- Mesh: {fileID: 4300004, guid: bdb4c719a85f3f2419f6c573dd1daf15, type: 3}
Material: {fileID: 2100000, guid: ce975d2d2ccd0914b9260b7d66319aaa, type: 2}
Radius: 1.5022775
- Mesh: {fileID: 4300004, guid: 19fa9aa3b5fee244fb007d58c351be01, type: 3}
Material: {fileID: 2100000, guid: 443981befe15f604aba2b04eef2f0de2, type: 2}
Radius: 1.0907339
- Mesh: {fileID: 4300004, guid: 693c11c21d8f22240ae2b6c576bf923a, type: 3}
Material: {fileID: 2100000, guid: 818a00140c1abb047be1d65d1c20b52a, type: 2}
Radius: 1.345119
- Mesh: {fileID: 4300004, guid: 73f7fb37d1925f246919650feda3df71, type: 3}
Material: {fileID: 2100000, guid: 60bb3870b22a23b458c2bc94e4403bda, type: 2}
Radius: 0.9581426
- Mesh: {fileID: 4300004, guid: 13503d5cc77421a4384053d90e12610f, type: 3}
Material: {fileID: 2100000, guid: 3175592343a31774b872b32ae89a7ee8, type: 2}
Radius: 1.3620772
- Mesh: {fileID: 4300004, guid: 7c93e75f8f4cde14d82932c65389f566, type: 3}
Material: {fileID: 2100000, guid: edee875c32d24154688acf645e314864, type: 2}
Radius: 1.1282945
- Mesh: {fileID: 4300004, guid: b2e60056527750e4c87bd4f6de85ec32, type: 3}
Material: {fileID: 2100000, guid: c3d0e5db17690724b89cff9a71876aa6, type: 2}
Radius: 1.5490558
- Mesh: {fileID: 4300004, guid: 87ef785989ec82b498488e651b61b2c6, type: 3}
Material: {fileID: 2100000, guid: 5686a9131ca8350448768971c9e21da0, type: 2}
Radius: 1.4786077
- Mesh: {fileID: 4300004, guid: a0f93e74d454f5046908a26ecbbc31cf, type: 3}
Material: {fileID: 2100000, guid: 85e9ac4c62af0fb4a94bc35ac5e24254, type: 2}
Radius: 1.1443666
- Mesh: {fileID: 4300004, guid: 4a9e0d7ffd8f8e94abf7a800653c0b4e, type: 3}
Material: {fileID: 2100000, guid: 27b1a299ede684d499133b9a0dc85bda, type: 2}
Radius: 1.1114912
- Mesh: {fileID: 4300004, guid: 232b4a71860923049890d661dee4aff7, type: 3}
Material: {fileID: 2100000, guid: d9e9ebabc89054f4495754372a7b4d8f, type: 2}
Radius: 1.5410137
- Mesh: {fileID: 4300004, guid: 335c54d8095aa3f44bab46ca5fa5bd4c, type: 3}
Material: {fileID: 2100000, guid: 71cd479bb26cd7140ba9d3753f49ffbc, type: 2}
Radius: 1.3792597
- Mesh: {fileID: 4300004, guid: 8942a135422bd014bb3451fe02d656f2, type: 3}
Material: {fileID: 2100000, guid: 0f7035043b158484995ff86e9371b141, type: 2}
Radius: 1.2433527
- Mesh: {fileID: 4300004, guid: 47e8c832aa7d46e4ba06961fddf3cc06, type: 3}
Material: {fileID: 2100000, guid: d5788bb4b0d2d93429d9c6462034e470, type: 2}
Radius: 1.3664026
- Mesh: {fileID: 4300004, guid: 7c1227bb235e0f74aa9a6cdb9e7ccac5, type: 3}
Material: {fileID: 2100000, guid: 060b5e360701d5146adc3d1c65c7a3df, type: 2}
Radius: 1.2458941
- Mesh: {fileID: 4300004, guid: 77a272b796d88344eacf56a79372b45e, type: 3}
Material: {fileID: 2100000, guid: 3cd15eb9c358aa547bcd223ee3c1edde, type: 2}
Radius: 2.157539
- Mesh: {fileID: 4300004, guid: bafc829a0712b174d9485c5bcbc1b529, type: 3}
Material: {fileID: 2100000, guid: b58fd84189173114d8708fb50b43f2d7, type: 2}
Radius: 1.5399581
- Mesh: {fileID: 4300004, guid: 47fa308b6b417034ea47809cb08bccfb, type: 3}
Material: {fileID: 2100000, guid: f56b0896907ac9e41b6fd9dca05f1fa2, type: 2}
Radius: 1.8855069
- Mesh: {fileID: 4300004, guid: fb03a3e40b409db44a810641e0a6c493, type: 3}
Material: {fileID: 2100000, guid: 78859c2284122944493cc283967036db, type: 2}
Radius: 1.2214345
- Mesh: {fileID: 4300004, guid: de7739fee205f4a4f9ae290126a36b61, type: 3}
Material: {fileID: 2100000, guid: 89b312038af5203469d447f7ff01105f, type: 2}
Radius: 1.8183402
- Mesh: {fileID: 4300004, guid: a966f9aba651bf34da1bb6e3e7f26301, type: 3}
Material: {fileID: 2100000, guid: 3326bc3e2b8ada34c9bfd44cf8cf1a1c, type: 2}
Radius: 1.5686527
- Mesh: {fileID: 4300004, guid: 0d55ec2b68453b7418abcd63378eed69, type: 3}
Material: {fileID: 2100000, guid: 12b1f515279281a42b2bd53cdb174488, type: 2}
Radius: 1.9112675
- Mesh: {fileID: 4300004, guid: e5dc38d7ffa6ed242831255d93657aaa, type: 3}
Material: {fileID: 2100000, guid: 5a06a4f6d5df32b4dae76c13939d3ce9, type: 2}
Radius: 1.9507759
- Mesh: {fileID: 4300004, guid: 9f36793bd68d3d84d8116485bf933992, type: 3}
Material: {fileID: 2100000, guid: decc37f956dfc244b956de2d6f5375c2, type: 2}
Radius: 1.5778105
- Mesh: {fileID: 4300004, guid: 214c1534cdd5b3a4a80927e9d879290f, type: 3}
Material: {fileID: 2100000, guid: a20b424628f81de4e8cf080a503b66af, type: 2}
Radius: 1.4580269
- Mesh: {fileID: 4300004, guid: 7db51bba443e10f4f9a597a296ceb0c6, type: 3}
Material: {fileID: 2100000, guid: f96d19f3fe978b044972d1360d2e7707, type: 2}
Radius: 2.430305
- Mesh: {fileID: 4300004, guid: 6116ec4d9786d04408c3b26d0c525407, type: 3}
Material: {fileID: 2100000, guid: 1788de6db766a4444a09dc0c80e1bcb2, type: 2}
Radius: 1.7872484
- Mesh: {fileID: 4300004, guid: 3d6e611c9ebead04b9d6d7d490977ddc, type: 3}
Material: {fileID: 2100000, guid: 0fab294fd86e3b84394a967642904d55, type: 2}
Radius: 1.9273407
- Mesh: {fileID: 4300004, guid: 76b1f709ca820234bbf6c03507333b7e, type: 3}
Material: {fileID: 2100000, guid: 63a8a57976371814689d7fb222e635e9, type: 2}
Radius: 1.70707
- Mesh: {fileID: 4300004, guid: d3c9f05eabff9e24ca9521e0b8dffd50, type: 3}
Material: {fileID: 2100000, guid: 9be65d82397829b46894243c2d91a06d, type: 2}
Radius: 1.6336656
AsteroidSpritesheetWidth: 4
AsteroidSpritesheetHeight: 4
LODHandler: {fileID: 1009503736}
@ -16417,13 +16445,13 @@ MonoBehaviour:
MapGravityMaterials:
- {fileID: 2100000, guid: 841e4091d19548347bd59115047b4e49, type: 2}
- {fileID: 2100000, guid: a92fc6d958edb9f46b6e58134f609cc0, type: 2}
GravityField: {fileID: 2100000, guid: 44a8bbb89acb4e34c9f41a9da5842bb4, type: 2}
Tour: 0
TourSwitchTime: 10
TourFollowDistance: 100
TourHeightOffset: 50
TourFollowOffsetDegrees: 55
AsteroidBeltUI: {fileID: 349597964696402918, guid: 1f1bfa2175a13b647888e2949e0cb138,
type: 3}
MapAsteroidMaterial: {fileID: 2100000, guid: c1012d5466454651b7279be93ef9af88, type: 2}
Planet: {fileID: 8287217960984982033, guid: 88ace6073e3db1a46bd3aaf4f971acb9, type: 3}
GasGiant: {fileID: 3250623180916197132, guid: 85753b4a7c762274385af384abed7a04,
type: 3}
@ -16463,7 +16491,6 @@ MonoBehaviour:
NebulaTint: {fileID: 8400000, guid: d196505464047f04a97f28689041209b, type: 2}
Heightmap: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2}
ParticleColors: {fileID: 2800000, guid: 56db63da398b2d04394ddbccedce943c, type: 3}
GravityCamera: {fileID: 1537342003}
--- !u!114 &1009503736
MonoBehaviour:
m_ObjectHideFlags: 0
@ -16864,8 +16891,6 @@ Transform:
m_LocalScale: {x: 3000, y: 3000, z: 3000}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1476765548}
- {fileID: 23420357}
- {fileID: 1108843909}
- {fileID: 1647230682}
- {fileID: 1254851255}
@ -22639,96 +22664,6 @@ RectTransform:
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &1476765547
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1476765548}
- component: {fileID: 1476765551}
- component: {fileID: 1476765550}
m_Layer: 9
m_Name: Boundary
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1476765548
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1476765547}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1037490739}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!23 &1476765550
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1476765547}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
m_DynamicOccludee: 0
m_StaticShadowCaster: 0
m_MotionVectors: 2
m_LightProbeUsage: 0
m_ReflectionProbeUsage: 0
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 8f45fb3a0f420d5428f1528827d55fa2, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!33 &1476765551
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1476765547}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!1 &1485884351
GameObject:
m_ObjectHideFlags: 0
@ -23565,7 +23500,7 @@ Camera:
m_Depth: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 1536
m_Bits: 512
m_RenderingPath: 1
m_TargetTexture: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2}
m_TargetDisplay: 0
@ -38486,6 +38421,7 @@ SceneRoots:
- {fileID: 906767442}
- {fileID: 1009503730}
- {fileID: 1037490739}
- {fileID: 23420357}
- {fileID: 2095699057}
- {fileID: 134113271}
- {fileID: 666227432}

View file

@ -892,7 +892,6 @@ MonoBehaviour:
NebulaTint: {fileID: 8400000, guid: d196505464047f04a97f28689041209b, type: 2}
Heightmap: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2}
ParticleColors: {fileID: 2800000, guid: 56db63da398b2d04394ddbccedce943c, type: 3}
GravityCamera: {fileID: 2103364771}
--- !u!114 &750375536
MonoBehaviour:
m_ObjectHideFlags: 0
@ -982,7 +981,7 @@ Camera:
m_Depth: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 5632
m_Bits: 5120
m_RenderingPath: 1
m_TargetTexture: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2}
m_TargetDisplay: 0
@ -1061,16 +1060,6 @@ PrefabInstance:
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7852221720741322892, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.x
value: 512
objectReference: {fileID: 0}
- target: {fileID: 7852221720741322892, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.y
value: 512
objectReference: {fileID: 0}
- target: {fileID: 7852221720816817928, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.x
@ -1086,16 +1075,6 @@ PrefabInstance:
propertyPath: m_Materials.Array.data[0]
value:
objectReference: {fileID: 2100000, guid: b9f52544f98f47146b486fb6e27179bb, type: 2}
- target: {fileID: 7852221720969441490, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.x
value: 512
objectReference: {fileID: 0}
- target: {fileID: 7852221720969441490, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.y
value: 512
objectReference: {fileID: 0}
- target: {fileID: 7852221720971737233, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.x

View file

@ -911,26 +911,6 @@ PrefabInstance:
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7852221720741322892, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.x
value: 40
objectReference: {fileID: 0}
- target: {fileID: 7852221720741322892, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.y
value: 40
objectReference: {fileID: 0}
- target: {fileID: 7852221720969441490, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.x
value: 20
objectReference: {fileID: 0}
- target: {fileID: 7852221720969441490, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.y
value: 20
objectReference: {fileID: 0}
- target: {fileID: 7852221720971737233, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalPosition.y
@ -1778,26 +1758,6 @@ PrefabInstance:
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7852221720741322892, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.x
value: 40
objectReference: {fileID: 0}
- target: {fileID: 7852221720741322892, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.y
value: 40
objectReference: {fileID: 0}
- target: {fileID: 7852221720969441490, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.x
value: 20
objectReference: {fileID: 0}
- target: {fileID: 7852221720969441490, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.y
value: 20
objectReference: {fileID: 0}
- target: {fileID: 7852221720971737233, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalPosition.y
@ -3809,7 +3769,7 @@ Camera:
m_Depth: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 5632
m_Bits: 5120
m_RenderingPath: 1
m_TargetTexture: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2}
m_TargetDisplay: 0
@ -5297,26 +5257,6 @@ PrefabInstance:
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7852221720741322892, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.x
value: 40
objectReference: {fileID: 0}
- target: {fileID: 7852221720741322892, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.y
value: 40
objectReference: {fileID: 0}
- target: {fileID: 7852221720969441490, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.x
value: 20
objectReference: {fileID: 0}
- target: {fileID: 7852221720969441490, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.y
value: 20
objectReference: {fileID: 0}
- target: {fileID: 7852221720971737233, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalPosition.y

View file

@ -0,0 +1,244 @@
using System;
using System.IO;
using GameCult.Caching;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
namespace Aetheria.EditorTools
{
/// <summary>
/// Batchmode check for campaign aetheria-gravity, cut ambient-follows-populated-area: the ambient tint falls softly to
/// dark and never hits an edge (ruling dark-beyond-populated-area). It opens ARPG.unity, takes the Ambient Fog Light the
/// scene wires into ZoneRenderer, sizes it for a generated zone with ZoneRenderer.FitAmbientLight (the call LoadZone
/// makes, not a copy), renders it through an orthographic camera that culls its layer into a target built from the descriptor of the production
/// Fog Tint render texture (B10G11R11_UFloatPack32, the format the Tint cameras write), and checks the
/// radial profile against exp(-(r/P)^2 ln(1/_EdgeBrightness)) times the centre, to within what that format stores (a
/// 5-bit mantissa) wherever the gaussian is at least a few ZoneRenderer.AmbientFloor (P is Zone.PopulatedRadius), that it
/// is lit at Pack.Radius, that the quad's rim and every lit texel beside a dark one are
/// at most two floors, and that no two lit texels are more than half a decade apart.
///
/// Renders, so it needs a graphics device: run WITHOUT -nographics.
/// Unity -batchmode -projectPath &lt;repo&gt; -executeMethod Aetheria.EditorTools.AmbientEdgeVerify.Run -logFile &lt;path&gt;
/// Do not pass -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
/// </summary>
public static class AmbientEdgeVerify
{
const string Tag = "[AmbientEdgeVerify]";
const int Size = 512;
const int AmbientLayer = 13;
const string FogTintPath = "Assets/Resources/Fog Tint.renderTexture";
// A float with a 5-bit mantissa (Fog Tint's blue) steps by 2^-5 of its value, and the target truncates to a step.
const float FormatRelativeError = 1f / 32f;
// The profile is held to the gaussian where it is at least this many floors; below that the format's tail is measured, not held.
const float FaithfulFloors = 8f;
// The rim and every lit texel beside a dark one hold at most this many floors, 2^-14 (the smallest normal of the format's
// floats): the one step to dark is the format's last value. Measured on the Fog Tint format it is 1.5 floors at most.
const float StepToDarkFloors = 2f;
static bool _ok = true;
static void OnLog(string message, string stack, LogType type)
{
if ((type == LogType.Error || type == LogType.Exception || type == LogType.Assert) && !message.StartsWith(Tag))
Fail("logged-error", message.Split('\n')[0]);
}
static void Fail(string check, string detail)
{
Debug.LogError($"{Tag} FAIL {check}: {detail}");
_ok = false;
}
public static void Run()
{
if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Null)
{
Debug.LogError($"{Tag} FAIL: no graphics device in this session; rerun batchmode without -nographics.");
EditorApplication.Exit(1);
return;
}
Application.logMessageReceived += OnLog;
var work = Path.Combine(Path.GetTempPath(), "ambient-edge-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(Path.Combine(work, "probe"));
try
{
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset")
?? throw new InvalidOperationException("No Settings.asset.");
// Generated zones have the populated radius at about .78 of the pack radius (p50 over 1500 zones): this one's
// pack radius is set so, which puts Pack.Radius where it sits in play, inside the quad's reach.
var probe = GravityFieldParityVerify.BuildZone(settings, Path.Combine(work, "probe"), out _);
var zone = GravityFieldParityVerify.BuildZone(settings, work, out _, probe.PopulatedRadius / .78f);
EditorSceneManager.OpenScene("Assets/Scenes/ARPG.unity", OpenSceneMode.Single);
try
{
var renderer = UnityEngine.Object.FindFirstObjectByType<ZoneRenderer>(FindObjectsInactive.Include);
if (renderer == null || renderer.AmbientLight == null) throw new InvalidOperationException("ARPG.unity wires no ZoneRenderer.AmbientLight.");
Check(renderer, zone);
}
finally
{
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
}
Debug.Log(_ok ? $"{Tag} PASS" : $"{Tag} FAIL: see errors above");
}
catch (Exception e)
{
Debug.LogError($"{Tag} FAIL exception: {e}");
_ok = false;
}
finally
{
Application.logMessageReceived -= OnLog;
try { Directory.Delete(work, true); } catch { }
}
EditorApplication.Exit(_ok ? 0 : 1);
}
// A bare ambient quad for the verifies that run LoadZone on a renderer built in code: the scene's material, no scene.
public static Transform NewAmbientLight()
{
var quad = new GameObject("Ambient Fog Light") { layer = AmbientLayer };
quad.AddComponent<MeshFilter>().sharedMesh = Resources.GetBuiltinResource<Mesh>("Quad.fbx");
quad.AddComponent<MeshRenderer>().sharedMaterial = AssetDatabase.LoadAssetAtPath<Material>("Assets/Materials/Brushes/Sector Tint.mat");
return quad.transform;
}
static void Check(ZoneRenderer renderer, Zone zone)
{
var production = AssetDatabase.LoadAssetAtPath<RenderTexture>(FogTintPath);
if (production == null || production.graphicsFormat != GraphicsFormat.B10G11R11_UFloatPack32)
Fail("format", $"Fog Tint is {(production == null ? "missing" : production.graphicsFormat.ToString())}, not the B10G11R11_UFloatPack32 that AmbientFloor and FormatRelativeError describe");
var light = renderer.AmbientLight;
if (light.parent != null) Fail("wiring", $"the ambient quad sits under {light.parent.name}, whose scale is the pack radius's");
if (light.gameObject.layer != AmbientLayer) Fail("wiring", $"the ambient quad is on layer {light.gameObject.layer}, not {AmbientLayer}");
ZoneRenderer.FitAmbientLight(light, zone);
var meshRenderer = light.GetComponent<MeshRenderer>();
var material = meshRenderer.sharedMaterial;
var edge = material.GetFloat("_EdgeBrightness");
var tint = material.GetColor("_Color").linear;
var centre = material.GetFloat("_Depth") * Mathf.Max(tint.r, Mathf.Max(tint.g, tint.b));
var populated = zone.PopulatedRadius;
var half = light.localScale.x / 2f;
Debug.Log($"{Tag} populated radius {populated:F1} u, pack radius {zone.Pack.Radius:F1} u, quad half-extent {half:F1} u, edge brightness {edge:G3}, centre {centre:G4}");
if (!(populated > 0f) || !(half > populated)) { Fail("sizing", $"the quad half-extent {half:F1} u does not reach beyond the populated radius {populated:F1} u"); return; }
Render(light, half * 1.05f, out var value, out var world);
var floor = ZoneRenderer.AmbientFloor;
Debug.Log($"{Tag} format floor {floor:E2} = {floor / centre:E2} of the centre; tail (drawn / floor): "
+ string.Join(" ", System.Linq.Enumerable.Select(new[] { 1.5f, 1.75f, 1.9f, 2f, 2.05f, 2.1f, 2.15f, 2.2f },
k => $"{k}P:{Sample(value, world, new Vector2(k * populated, 0f)) / floor:G3}")));
var logFall = Mathf.Log(1f / edge);
foreach (var k in new[] { 0f, .5f, 1f, 1.5f, 2f })
{
var drawn = Sample(value, world, new Vector2(k * populated, 0f));
Debug.Log($"{Tag} r = {k} P: drawn {drawn:G5}, expected {centre * Mathf.Exp(-k * k * logFall):G5}");
}
var worstProfile = 0f;
var compared = 0;
var rim = 0f;
for (var i = 0; i < value.Length; i++)
{
if (float.IsNaN(value[i])) { Fail("texels", $"texel {i}: NaN"); return; }
var r = world[i].magnitude;
var expected = centre * Mathf.Exp(-(r / populated) * (r / populated) * logFall);
if (r <= half && expected >= FaithfulFloors * floor)
{
var error = Mathf.Abs(value[i] - expected) / (FormatRelativeError * expected);
worstProfile = Mathf.Max(worstProfile, error);
compared++;
if (error > 1f) { Fail("profile", $"at r = {r / populated:F3} P: expected {expected:G5}, drawn {value[i]:G5}, off by {error:F2} times the format's step"); return; }
}
// The rim: the texels in the quad's outermost ring (within two texels of its edge).
var edgeDistance = half - Mathf.Max(Mathf.Abs(world[i].x), Mathf.Abs(world[i].y));
if (edgeDistance >= 0f && edgeDistance < 2f * (half * 1.05f * 2f / Size)) rim = Mathf.Max(rim, value[i]);
}
Debug.Log($"{Tag} profile: {compared} texels held to the gaussian, worst error {worstProfile:F3} of the format's step; rim {rim / centre:E2} of the centre");
if (compared < Size * Size / 16) Fail("profile", $"only {compared} texels were held to the gaussian; the check is vacuous");
if (rim > floor * StepToDarkFloors) Fail("rim", $"the quad's rim is {rim / floor:G3} times the format floor");
var atPack = Sample(value, world, new Vector2(zone.Pack.Radius, 0f));
Debug.Log($"{Tag} at Pack.Radius: {atPack / centre:E2} of the centre");
if (!(atPack > 0f)) Fail("pack-radius", "the ambient is zero at Pack.Radius");
var worstJump = 0f;
for (var y = 0; y < Size; y++)
for (var x = 0; x < Size; x++)
{
var here = value[y * Size + x];
if (here <= 0f) continue;
foreach (var next in new[] { x + 1 < Size ? value[y * Size + x + 1] : 0f, y + 1 < Size ? value[(y + 1) * Size + x] : 0f })
{
if (next <= 0f) { if (here > floor * StepToDarkFloors) Fail("last-step", $"a lit texel of {here / floor:G3} floors sits beside a dark one"); continue; }
worstJump = Mathf.Max(worstJump, Mathf.Abs(Mathf.Log10(here / next)));
}
}
Debug.Log($"{Tag} largest jump between neighbouring lit texels: {worstJump:F3} decades (limit 0.5)");
if (worstJump > .5f) Fail("jump", $"neighbouring texels differ by {worstJump:F2} decades");
var lit = 0;
foreach (var v in value) if (v > 0f) lit++;
if (lit < Size * Size / 2) Fail("jump", $"only {lit} texels are lit; the check is vacuous");
}
static float Sample(float[] value, Vector2[] world, Vector2 at)
{
var best = 0;
for (var i = 1; i < world.Length; i++)
if ((world[i] - at).sqrMagnitude < (world[best] - at).sqrMagnitude) best = i;
return value[best];
}
// Renders the ambient layer through a downward orthographic camera centred on the origin, as the Tint cameras see it,
// and returns the brightest channel of every texel with the world xz of its centre, which is asked of the camera.
static void Render(Transform light, float half, out float[] value, out Vector2[] world)
{
var production = AssetDatabase.LoadAssetAtPath<RenderTexture>(FogTintPath) ?? throw new InvalidOperationException("No Fog Tint render texture.");
var rt = new RenderTexture(production.descriptor) { filterMode = FilterMode.Point };
var cameraObject = new GameObject("Ambient Edge Camera");
var read = new Texture2D(Size, Size, TextureFormat.RGBAFloat, false);
var previous = RenderTexture.active;
try
{
var camera = cameraObject.AddComponent<Camera>();
camera.orthographic = true;
camera.orthographicSize = half;
camera.aspect = 1f;
camera.nearClipPlane = .3f;
camera.farClipPlane = 1000f;
camera.cullingMask = 1 << light.gameObject.layer;
camera.clearFlags = CameraClearFlags.SolidColor;
camera.backgroundColor = Color.black;
camera.allowHDR = false;
camera.allowMSAA = false;
camera.targetTexture = rt;
cameraObject.transform.SetPositionAndRotation(new Vector3(0f, 100f, 0f), Quaternion.Euler(90f, 0f, 0f));
camera.Render();
RenderTexture.active = rt;
read.ReadPixels(new Rect(0, 0, Size, Size), 0, 0);
read.Apply();
var pixels = read.GetPixelData<float>(0);
value = new float[Size * Size];
for (var i = 0; i < value.Length; i++)
value[i] = Mathf.Max(pixels[4 * i], Mathf.Max(pixels[4 * i + 1], pixels[4 * i + 2]));
world = new Vector2[Size * Size];
for (var y = 0; y < Size; y++)
for (var x = 0; x < Size; x++)
{
var p = camera.ViewportToWorldPoint(new Vector3((x + .5f) / Size, (y + .5f) / Size, 100f));
world[y * Size + x] = new Vector2(p.x, p.z);
}
}
finally
{
RenderTexture.active = previous;
UnityEngine.Object.DestroyImmediate(cameraObject);
UnityEngine.Object.DestroyImmediate(read);
rt.Release();
UnityEngine.Object.DestroyImmediate(rt);
}
}
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 63b03af05dfdae44c89e21ca7ebec892

View file

@ -0,0 +1,265 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using GameCult.Caching;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using float2 = CultMath.float2;
using float4 = CultMath.float4;
// End-to-end proof of the belt draw (rulings asteroid-tops-at-layer, rock-meshes-by-size), measured where it fails: two belts are loaded
// into a ZoneRenderer carrying ARPG.unity's real AsteroidMeshes (meshes, stored radii) and Settings.asset, over a zone whose wells make the
// field differ rock to rock, and ZoneRenderer.DrawBelts runs as Update runs it. Every instanced matrix it hands to the GPU is read back with the
// mesh it is drawn with (ZoneRenderer.DrawInstanced), and for each drawn rock the verify measures, from the mesh's real vertices:
// - the farthest vertex from the rock's centre after the matrix (the reach, a bound on the rock's top under any rotation) is the rock's
// gameplay radius, and centre y + reach is the floater layer over the field at the rock's xz and the drawn time;
// - the rock is drawn exactly once, on exactly one matrix of a real pose (no spare instance in a batch);
// - within a belt a bigger rock never takes a smaller mesh (by the mesh's own farthest vertex), and the belt keeps min(AsteroidMeshCount, meshes) meshes.
// The arguments the renderer hands the draw list (radius field, belt, batch size, kept count) are not inspected: their consequence is.
//
// Reached: ZoneRenderer.LoadPlanet for each belt, DrawBelts. Not reached: LoadZone's other planets (their prefabs), the GPU draw itself.
// Needs a GPU device for MapPresenter's belt buffer: run without -nographics.
//
// Unity -batchmode -projectPath <project> -executeMethod Aetheria.EditorTools.BeltFloaterLayerVerify.Run -logFile <log>
//
// Do not pass -quit: Run enters play mode (a domain reload), the check runs there, and the Editor exits itself, 0 on success and 1 on any failure.
namespace Aetheria.EditorTools
{
[InitializeOnLoad]
public static class BeltFloaterLayerVerify
{
private const string Tag = "[BeltFloaterLayerVerify]";
private const string Flag = "BeltFloaterLayerVerify.running";
private const string Wiring = "BeltFloaterLayerVerify.wiring";
private const double Budget = 300; // real seconds
private const float Lead = .008f, Layer = -1.5f, Tolerance = .02f;
private static IEnumerator _check;
private static double _started;
private static string _work;
static BeltFloaterLayerVerify()
{
if (SessionState.GetBool(Flag, false)) EditorApplication.playModeStateChanged += OnPlayMode;
}
public static void Run()
{
try
{
SessionState.SetString(Wiring, ReadWiring());
SessionState.SetBool(Flag, true);
EditorApplication.EnterPlaymode();
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
private static void OnPlayMode(PlayModeStateChange change)
{
if (change != PlayModeStateChange.EnteredPlayMode) return;
_work = Path.Combine(Path.GetTempPath(), $"belt-floater-{Guid.NewGuid():N}");
Directory.CreateDirectory(_work);
_started = Time.realtimeSinceStartupAsDouble;
_check = Check();
EditorApplication.update += Step;
}
private static void Step()
{
try
{
if (Time.realtimeSinceStartupAsDouble - _started > Budget) throw new InvalidOperationException($"The check did not finish in {Budget} seconds.");
if (_check.MoveNext()) return;
Finish(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
Finish(1);
}
}
private static void Finish(int code)
{
EditorApplication.update -= Step;
SessionState.SetBool(Flag, false);
try { if (Directory.Exists(_work)) Directory.Delete(_work, true); } catch (Exception) { /* a temp directory */ }
EditorApplication.Exit(code);
}
private static void Require(bool condition, string what)
{
if (!condition) throw new InvalidOperationException(what);
}
// A belt of rocks of every size, its sizes in the given order, round an orbit about the planet.
private static Asteroid[] Rocks(int count, Func<int, float> size, float start) => Enumerable.Range(0, count).Select(i => new Asteroid
{
Distance = start + i * 9.7f, Phase = Mathf.Repeat(i * .6180339887f, 1f), Size = size(i), RotationSpeed = .1f + i * .013f
}).ToArray();
private static IEnumerator Check()
{
var game = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset") ?? throw new InvalidOperationException("No Settings.asset.");
var settings = game.GameplaySettings;
settings.FloaterLayerHeight = Layer; // in memory, never saved back to the asset: a draw that drops the layer fails
var meshes = SessionState.GetString(Wiring, "").Split('|').Select(entry => entry.Split('~')).Select(p => new InstancedMesh
{
Mesh = AssetDatabase.LoadAllAssetsAtPath(p[0]).OfType<Mesh>().First(m => m.name == p[1]),
Material = p[2] == "" ? null : AssetDatabase.LoadAssetAtPath<Material>(p[2]),
Radius = float.Parse(p[3], System.Globalization.CultureInfo.InvariantCulture)
}).ToArray();
Require(meshes.Length > game.AsteroidMeshCount, $"ARPG.unity has {meshes.Length} rock meshes for a kept count of {game.AsteroidMeshCount}: nothing to prune.");
var cache = AetheriaStores.Open(Path.Combine(_work, "belts.cc"), Path.Combine(_work, "belts-run.cc"), catalogWritable: true);
cache.Upsert(new VerseGrammar { Revision = 1 });
cache.FlushAsync().Wait();
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings, _ => { });
CultRecordKey Orbit(CultRecordKey parent, float distance, float phase, float2 fixedPosition = default)
{
var data = new OrbitData { Distance = distance, Phase = phase, FixedPosition = fixedPosition };
if (parent.IsSet()) data.Parent = new CultRecordRef<OrbitData>(parent);
return cache.Upsert(data).Key;
}
// A heavy sun and a planet on a moving rail, the belts round the planet: the field under a rock differs from rock to rock.
var sunOrbit = Orbit(default, 0f, 0f, new float2(700f, -300f));
var planetOrbit = Orbit(sunOrbit, 600f, .3f);
var beltOrbitA = Orbit(planetOrbit, 150f, .1f);
var beltOrbitB = Orbit(planetOrbit, 300f, .6f);
var beltA = cache.Upsert(new AsteroidBeltData { Orbit = new CultRecordRef<OrbitData>(beltOrbitA), Asteroids = Rocks(24, i => (i * 7 % 24) / 23f, 40f) }).Key;
var beltB = cache.Upsert(new AsteroidBeltData { Orbit = new CultRecordRef<OrbitData>(beltOrbitB), Asteroids = Rocks(24, i => (i * 5 % 24) / 23f, 60f) }).Key;
var pack = new ZonePack { Radius = 5000f, Mass = 10000f };
foreach (var orbit in new[] { sunOrbit, planetOrbit, beltOrbitA, beltOrbitB }) pack.Orbits.Add(new CultRecordRef<OrbitData>(orbit));
pack.Planets.Add(new CultRecordRef<BodyData>(cache.Upsert(new PlanetData { Name = "Belt Sun", Orbit = new CultRecordRef<OrbitData>(sunOrbit), Mass = 400f }).Key));
pack.Planets.Add(new CultRecordRef<BodyData>(cache.Upsert(new PlanetData { Name = "Belt Planet", Orbit = new CultRecordRef<OrbitData>(planetOrbit), Mass = 400f }).Key));
pack.Planets.Add(new CultRecordRef<BodyData>(beltA));
pack.Planets.Add(new CultRecordRef<BodyData>(beltB));
var curves = new PlanetSettings
{
BodyRadius = new ExponentialCurve(),
WellStrength = new ExponentialCurve { Multiplier = 1f, Exponent = 1f },
RocheLimit = new ExponentialCurve { Constant = 10f },
AsteroidSize = new ExponentialLerp { Minimum = 2f, Maximum = 9f, Exponent = 1.7f },
AsteroidHitpoints = new ExponentialLerp { Minimum = 10f, Maximum = 300f, Exponent = 2f },
AsteroidRespawnTime = new ExponentialLerp { Minimum = 5f, Maximum = 60f, Exponent = 1f }
};
var zone = new Zone(items, curves, pack, null, null);
zone.Update(.5f);
// The renderer, configured as the scene configures it for belts: Awake builds the map, LoadPlanet reads Zone, Settings and AsteroidMeshes.
var rendererObject = new GameObject("Belt Zone Renderer");
rendererObject.SetActive(false);
var renderer = rendererObject.AddComponent<ZoneRenderer>();
renderer.Settings = game;
renderer.AsteroidMeshes = meshes;
renderer.ItemManager = items;
renderer.LODHandler = new GameObject("Belt LOD").AddComponent<LODHandler>();
const BindingFlags Any = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
typeof(ZoneRenderer).GetProperty("Zone").GetSetMethod(true).Invoke(renderer, new object[] { zone });
typeof(ZoneRenderer).GetMethod("Awake", Any).Invoke(renderer, null);
var load = typeof(ZoneRenderer).GetMethod("LoadPlanet", Any);
foreach (var belt in new[] { beltA, beltB }) load.Invoke(renderer, new object[] { belt, zone.Planets[belt] });
var planetAt = DrawAhead.Orbit(zone, planetOrbit, Lead);
var camera = new GameObject("Belt Camera").AddComponent<Camera>();
camera.transform.position = new Vector3(planetAt.x, 4000f, planetAt.y);
camera.transform.rotation = Quaternion.Euler(90, 0, 0);
camera.farClipPlane = 20000f;
renderer.MainCamera = camera;
yield return null;
ActionGameManager.Clock.Reset();
ActionGameManager.Clock.Advance(Lead, _ => throw new InvalidOperationException("a step ran"));
var lead = ActionGameManager.Clock.Lead;
Require(Mathf.Abs(lead - Lead) < 1e-6f, $"The clock's lead is {lead}, not {Lead}.");
var drawn = new List<(Mesh mesh, Matrix4x4[] matrices)>();
renderer.DrawInstanced = (mesh, _, matrices) => drawn.Add((mesh, (Matrix4x4[]) matrices.Clone()));
renderer.DrawBelts(lead);
// The poses the sim defines at the drawn time: every rock of both belts.
var rocks = new List<(CultRecordKey belt, int index, float4 pose)>();
foreach (var belt in new[] { beltA, beltB })
{
var poses = new float4[((AsteroidBeltData) zone.Planets[belt]).Asteroids.Length];
zone.EvaluateBelt(belt, poses, DrawAhead.Time(zone, lead));
for (var i = 0; i < poses.Length; i++) rocks.Add((belt, i, poses[i]));
}
Require(drawn.Count > 0, "DrawBelts drew nothing: the belts are out of the camera or have no rocks.");
// The field differs between rocks, so a draw that drops it or samples it at the wrong place fails.
var layers = rocks.Select(r => DrawAhead.FloaterHeight(zone, settings, r.pose.xy, lead)).ToArray();
Require(layers.Max() - layers.Min() > 1f, $"The fixture's field is flat under the rocks: layer heights span {layers.Max() - layers.Min()}.");
var seen = new Dictionary<(CultRecordKey, int), float>(); // rock -> its mesh's own farthest vertex
var vertices = new Dictionary<Mesh, Vector3[]>();
foreach (var (mesh, matrices) in drawn)
{
if (!vertices.TryGetValue(mesh, out var v)) vertices[mesh] = v = mesh.vertices;
var own = Mathf.Sqrt(v.Max(x => x.sqrMagnitude));
foreach (var matrix in matrices)
{
var centre = (Vector3) matrix.GetColumn(3);
var at = rocks.Where(r => Mathf.Abs(r.pose.x - centre.x) < .01f && Mathf.Abs(r.pose.y - centre.z) < .01f).ToArray();
Require(at.Length == 1, $"An instance of {mesh.name} sits at {centre.x},{centre.z}, where {at.Length} rocks are (a spare or misplaced instance).");
var rock = at[0];
Require(seen.TryAdd((rock.belt, rock.index), own), $"Rock {rock.index} of {rock.belt} is drawn twice.");
var reach = v.Max(x => (matrix.MultiplyPoint3x4(x) - centre).magnitude);
Require(Mathf.Abs(reach - rock.pose.w) < Tolerance * Mathf.Max(1f, rock.pose.w),
$"Rock {rock.index} of {rock.belt} on {mesh.name}: its farthest vertex is {reach} from its centre, its gameplay radius {rock.pose.w}.");
var top = centre.y + reach;
var want = DrawAhead.FloaterHeight(zone, settings, rock.pose.xy, lead);
Require(Mathf.Abs(top - want) < Tolerance,
$"Rock {rock.index} of {rock.belt} on {mesh.name}: its top is {top}, the floater layer over the field there {want}.");
}
}
Require(seen.Count == rocks.Count, $"{seen.Count} of {rocks.Count} rocks were drawn.");
foreach (var belt in new[] { beltA, beltB })
{
var mine = rocks.Where(r => r.belt.Equals(belt)).ToArray();
var radii = mine.Select(r => seen[(belt, r.index)]).Distinct().OrderBy(x => x).ToArray();
Require(radii.Length >= 2, $"Belt {belt} is drawn on {radii.Length} distinct mesh radii: the size match is not exercised.");
var kept = drawn.Where(d => d.matrices.Any(m => mine.Any(r => Mathf.Abs(r.pose.x - m.m03) < .01f && Mathf.Abs(r.pose.y - m.m23) < .01f))).Select(d => d.mesh).Distinct().Count();
Require(kept == Mathf.Min(game.AsteroidMeshCount, meshes.Length), $"Belt {belt} is drawn on {kept} meshes, not the kept count {game.AsteroidMeshCount}.");
var size = mine.ToDictionary(r => r.index, r => zone.AsteroidBelts[belt].UndamagedSize(r.index, zone.Settings));
foreach (var a in mine)
foreach (var b in mine)
if (size[a.index] < size[b.index])
Require(seen[(belt, a.index)] <= seen[(belt, b.index)],
$"Belt {belt}: rock {a.index} (size {size[a.index]}) is smaller than rock {b.index} ({size[b.index]}) and takes a bigger mesh ({seen[(belt, a.index)]} against {seen[(belt, b.index)]}).");
var biggest = mine.OrderByDescending(r => size[r.index]).First();
Require(Mathf.Approximately(seen[(belt, biggest.index)], radii[^1]), $"Belt {belt}: its biggest rock is not on its biggest mesh.");
}
ActionGameManager.Clock.Reset();
Debug.Log($"{Tag} ok: {rocks.Count} rocks of 2 belts drawn at the floater layer, big rocks on big meshes ({drawn.Count} batches)");
}
// The AsteroidMeshes ARPG.unity's ZoneRenderer names, read before the play session (a scene cannot be opened inside one) and carried across the
// domain reload as asset paths: mesh path, mesh name, material path, stored radius.
private static string ReadWiring()
{
var scene = EditorSceneManager.OpenScene("Assets/Scenes/ARPG.unity", OpenSceneMode.Additive);
try
{
var found = UnityEngine.Object.FindObjectsByType<ZoneRenderer>(FindObjectsInactive.Include, FindObjectsSortMode.None)
.Where(r => r.gameObject.scene == scene).ToArray();
Require(found.Length == 1, $"ARPG.unity has {found.Length} ZoneRenderers.");
var meshes = found[0].AsteroidMeshes ?? throw new InvalidOperationException("ARPG.unity's ZoneRenderer has no AsteroidMeshes.");
return string.Join("|", meshes.Select(m => string.Join("~", AssetDatabase.GetAssetPath(m.Mesh), m.Mesh.name,
m.Material == null ? "" : AssetDatabase.GetAssetPath(m.Material), m.Radius.ToString("R", System.Globalization.CultureInfo.InvariantCulture))));
}
finally { EditorSceneManager.CloseScene(scene, true); }
}
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7d25a93db1972324fa5460b2415c1e4a

View file

@ -62,7 +62,7 @@ public static class CapabilityPresentationSmoke
cache.FlushAsync().Wait();
var ledger = new ProvenanceLedger();
for (var i = 1; i <= 8; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
var items = new ItemManager(cache, ledger, settings, _ => { });
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Smoke", Owner = null }, null);
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Smoke Hull"));
var ship = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 1 }, new EntitySettings());
@ -85,6 +85,11 @@ public static class CapabilityPresentationSmoke
?? throw new InvalidOperationException("No Tractor Beam prefab.");
// An entity's instance as the game builds it: the beam is a child before SetEntity on both ship paths, so the
// binder finds it as the game does.
// The scene's map: an entity registers its icon with the ZoneRenderer's presenter, so each instance carries a quad.
var mapRenderer = new GameObject("Smoke Zone Renderer").AddComponent<ZoneRenderer>();
mapRenderer.Settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset");
mapRenderer.MapGravityMaterials = new Material[0];
Awaken(mapRenderer);
(GameObject Object, EntityInstance Instance, TractorBeam Beam) Bound(string name, Entity entity)
{
var go = new GameObject(name);
@ -98,7 +103,11 @@ public static class CapabilityPresentationSmoke
var tractor = ((GameObject)PrefabUtility.InstantiatePrefab(beamPrefab, go.transform)).GetComponent<TractorBeam>();
tractor.Power = .8f;
tractor.Direction = Vector3.forward;
bound.SetEntity(null, entity);
var icon = GameObject.CreatePrimitive(PrimitiveType.Quad);
icon.transform.SetParent(go.transform);
bound.MapIcon = icon.GetComponent<MeshRenderer>();
bound.MapIcon.sharedMaterial = new Material(Shader.Find("Unlit/Color"));
bound.SetEntity(mapRenderer, entity);
return (go, bound, tractor);
}
var (instanceObject, instance, beam) = Bound("Smoke Entity Instance", ship);

View file

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System;
using System.Linq;
using System.Reflection;
@ -87,14 +88,13 @@ public sealed class InspectableTemperatureDrawer : ICultInspectorDrawer
}
}
// A size and a toggle per cell. When the owning item has a schematic texture, the texture lies under the grid and the
// height follows its aspect; a hull's hardpoint cells are tinted. Both read the owning record through
// A size and a toggle per cell. The underlay (the item's schematic texture, or a hull's line art stretched to the Shape's
// Width x Height) is drawn by HullUnderlay; the height follows a texture's aspect. A hull's hardpoint cells are tinted.
// Both read the owning record through
// inspector.Record, which is a read-only copy: nothing here writes to it.
[CultInspectorDrawer(typeof(InspectableSchematicShapeAttribute))]
public sealed class InspectableSchematicShapeDrawer : ICultInspectorDrawer
{
private static Material _schematicMaterial;
public object Draw(CultInspector inspector, string label, Type type, object value, MemberInfo member)
{
if (type != typeof(Shape)) return inspector.DrawDefault(label, type, value, member);
@ -122,18 +122,13 @@ public sealed class InspectableSchematicShapeDrawer : ICultInspectorDrawer
{
using (var grid = new EditorGUILayout.VerticalScope())
{
if (schematic != null && Event.current.type == EventType.Repaint)
{
_schematicMaterial ??= new Material(Shader.Find("Legacy Shaders/Particles/Additive"));
EditorGUI.DrawPreviewTexture(grid.rect, schematic, _schematicMaterial);
}
HullUnderlay.Draw(grid.rect, item, inspector.Records);
for (var y = shape.Height - 1; y >= 0; y--)
using (new EditorGUILayout.HorizontalScope())
for (var x = 0; x < shape.Width; x++)
{
var hardpoint = hull?.Hardpoints.FirstOrDefault(hp => hp.Shape.Coordinates
.Any(v => v.x + hp.Position.x == x && v.y + hp.Position.y == y));
var hardpoint = hull?.Hardpoints.FirstOrDefault(hp => HullFootprint.Cells(hp).Contains(int2(x, y)));
var previous = GUI.backgroundColor;
if (hardpoint != null) GUI.backgroundColor = hardpoint.TintColor.ToColor();
shape[int2(x, y)] = GUILayout.Toggle(shape[int2(x, y)], GUIContent.none, GUILayout.Width(16));
@ -145,3 +140,93 @@ public sealed class InspectableSchematicShapeDrawer : ICultInspectorDrawer
return shape;
}
}
// The underlay under a hull's cell grids, drawn on Repaint only. A shipped hull's schematic texture is stretched over
// the grid rect; a mod hull's line art (its ShipAuthoring, found in the open cache at hull.Visual) is placed by
// SchematicUnderlay.Fit in cell units and mapped to the rect here, the one site that maps cells to a grid's screen
// rect. The hull Shape grid and the footprint painter both draw through it. No lookup found: no line underlay.
public static class HullUnderlay
{
private static Material _schematicMaterial;
public static void Draw(Rect grid, EquippableItemData item, IReadOnlyList<CultStoredDocument> records)
{
if (item == null || Event.current.type != EventType.Repaint) return;
var texture = string.IsNullOrEmpty(item.Schematic) ? null : AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(item.Schematic));
if (texture != null)
{
_schematicMaterial ??= new Material(Shader.Find("Legacy Shaders/Particles/Additive"));
EditorGUI.DrawPreviewTexture(grid, texture, _schematicMaterial);
}
if (!(item is HullData hull) || hull.Shape == null || hull.Shape.Width == 0 || hull.Shape.Height == 0) return;
var key = CultInspectorModel.RecordKey(hull.Visual);
var authoring = records?.FirstOrDefault(record => record.Key.Value == key && record.Document is ShipAuthoring)?.Document as ShipAuthoring;
if (authoring == null) return;
var lines = authoring.SchematicLines;
var fit = SchematicUnderlay.Fit(lines, hull.Shape);
for (var i = 0; i < fit.Length; i++)
{
var rgba = lines[i].Color;
var color = rgba == null ? Color.white : new Color(rgba[0], rgba[1], rgba[2], rgba[3]);
var points = fit[i].Select(p => new Vector3(
grid.x + p.x / hull.Shape.Width * grid.width,
grid.yMax - p.y / hull.Shape.Height * grid.height)).ToList();
if (lines[i].Cyclic && points.Count > 2) points.Add(points[0]);
using (new Handles.DrawingScope(color)) Handles.DrawAAPolyLine(2f, points.ToArray());
}
}
}
// A hardpoint's schematic footprint over its hull's cells: the hull's occupied cells shaded, this hardpoint's cells lit
// in its tint, other hardpoints' cells tinted and disabled. A click edits Position and Shape together through
// HullFootprint.Toggle, which owns the rule so a Thing host reads the same one. The footprint is authored for gameplay,
// never derived from the model. Read-only on inspector.Record; only the HardpointData it is given changes.
[CultInspectorDrawer(typeof(HardpointData))]
public sealed class HardpointFootprintDrawer : ICultInspectorDrawer
{
public object Draw(CultInspector inspector, string label, Type type, object value, MemberInfo member)
{
var hardpoint = value as HardpointData;
if (type != typeof(HardpointData) || hardpoint == null) return inspector.DrawDefault(label, type, value, member);
var hull = inspector.Record as HullData;
var own = hull?.Hardpoints.FirstOrDefault(hp => hp.Type == hardpoint.Type && hp.Position.Equals(hardpoint.Position)
&& HullFootprint.Cells(hp).SequenceEqual(HullFootprint.Cells(hardpoint)));
var drawn = (HardpointData) inspector.DrawDefault(label, type, value, member);
if (hull?.Shape == null) return drawn;
var others = hull.Hardpoints.Where(hp => hp != own).ToArray();
EditorGUILayout.LabelField("Footprint");
using (new EditorGUILayout.HorizontalScope())
{
using (var grid = new EditorGUILayout.VerticalScope())
{
HullUnderlay.Draw(grid.rect, hull, inspector.Records);
for (var y = hull.Shape.Height - 1; y >= 0; y--)
using (new EditorGUILayout.HorizontalScope())
for (var x = 0; x < hull.Shape.Width; x++)
{
var cell = int2(x, y);
var other = others.FirstOrDefault(hp => HullFootprint.Cells(hp).Contains(cell));
var on = HullFootprint.Cells(drawn).Contains(cell);
var previous = GUI.backgroundColor;
GUI.backgroundColor = on ? drawn.TintColor.ToColor() : other != null ? other.TintColor.ToColor()
: hull.Shape[cell] ? Color.gray : previous;
EditorGUI.BeginChangeCheck();
using (new EditorGUI.DisabledScope(other != null && !on))
GUILayout.Toggle(on || other != null, GUIContent.none, GUILayout.Width(16));
if (EditorGUI.EndChangeCheck() && HullFootprint.Toggle(drawn.Shape, drawn.Position, cell, out var shape, out var position))
{
drawn.Shape = shape;
drawn.Position = position;
GUI.changed = true;
}
GUI.backgroundColor = previous;
}
}
GUILayout.FlexibleSpace();
}
return drawn;
}
}

View file

@ -0,0 +1,232 @@
using System;
using System.Collections;
using System.IO;
using System.Linq;
using GameCult.Caching;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using float2 = CultMath.float2;
using float3 = CultMath.float3;
// Batch proof that the presenters read the drawn pose through the clock: DrawnPoseTests pins the pose maths headlessly,
// and this is what pins that each presenter passes ActionGameManager.Clock.Lead into it. A zone with a sun and a planet on a moving rail
// is built and the pickup and mine prefabs ARPG.unity's ZoneRenderer names are bound to it as the renderer binds them; a loose Projectile is launched on the
// zone. The zone is stepped once, then the clock is set to leads 0, 0.008 and 0.015 s with no step, and each presenter's
// own Update places its transform. A body is drawn at its last step plus its step velocity times the lead, spelled here
// from the body's fields, so a presenter that reads lead 0 (or half the lead) sits on the step and fails.
//
// Reached: ItemPickup, MineInstance, Projectile.Draw. Not reached: ZoneRenderer.Update (Lower needs a ComputeBuffer, a
// GPU device) and the ActionGameManager markers (they need the ARPG scene running); a Lower split so its record fill is
// pure, or a run with a GPU and ARPG.unity in play mode, would reach them.
//
// Unity -batchmode -nographics -projectPath <project> -executeMethod Aetheria.EditorTools.DrawnPoseWiringVerify.Run -logFile <log>
//
// Do not pass -quit: Run enters play mode (a domain reload), the check runs there, and the Editor exits itself, 0 on
// success and 1 on any failure.
namespace Aetheria.EditorTools
{
[InitializeOnLoad]
public static class DrawnPoseWiringVerify
{
private const string Tag = "[DrawnPoseWiringVerify]";
private const string Flag = "DrawnPoseWiringVerify.running";
private const string Wiring = "DrawnPoseWiringVerify.wiring";
private const double Budget = 300; // real seconds
private const float RoundSpeed = 100f, Tolerance = 1e-3f;
private static IEnumerator _check;
private static double _started;
private static string _work;
static DrawnPoseWiringVerify()
{
if (SessionState.GetBool(Flag, false)) EditorApplication.playModeStateChanged += OnPlayMode;
}
public static void Run()
{
try
{
SessionState.SetString(Wiring, ReadWiring());
SessionState.SetBool(Flag, true);
EditorApplication.EnterPlaymode();
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
private static void OnPlayMode(PlayModeStateChange change)
{
if (change != PlayModeStateChange.EnteredPlayMode) return;
_work = Path.Combine(Path.GetTempPath(), $"drawn-pose-wiring-{Guid.NewGuid():N}");
Directory.CreateDirectory(_work);
_started = Time.realtimeSinceStartupAsDouble;
_check = Check();
EditorApplication.update += Step;
}
private static void Step()
{
try
{
if (Time.realtimeSinceStartupAsDouble - _started > Budget) throw new InvalidOperationException($"The check did not finish in {Budget} seconds.");
if (_check.MoveNext()) return;
Finish(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
Finish(1);
}
}
private static void Finish(int code)
{
EditorApplication.update -= Step;
SessionState.SetBool(Flag, false);
try { if (Directory.Exists(_work)) Directory.Delete(_work, true); } catch (Exception) { /* a temp directory */ }
EditorApplication.Exit(code);
}
private static void Require(bool condition, string what)
{
if (!condition) throw new InvalidOperationException(what);
}
private static IEnumerator Check()
{
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset")?.GameplaySettings
?? throw new InvalidOperationException("No Settings.asset.");
settings.FloaterLayerHeight = -1.5f; // nonzero, so a presenter that drops the floater layer fails
var wired = SessionState.GetString(Wiring, "").Split('|').Select(entry => entry.Split('=')).ToDictionary(pair => pair[0], pair => pair[1]);
var pickupPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(wired["SimpleCommodityPickup"])?.GetComponent<ItemPickup>();
var minePrefab = AssetDatabase.LoadAssetAtPath<GameObject>(wired["MinePrefab"])?.GetComponent<MineInstance>();
Require(pickupPrefab != null, "ZoneRenderer.SimpleCommodityPickup is not an ItemPickup in ARPG.unity.");
Require(minePrefab != null, "ZoneRenderer.MinePrefab is not a MineInstance in ARPG.unity.");
var oneCell = new Shape(1, 1);
oneCell[new CultMath.int2(0, 0)] = true;
var cache = AetheriaStores.Open(Path.Combine(_work, "wiring.cc"), Path.Combine(_work, "wiring-run.cc"), catalogWritable: true);
cache.Upsert(new VerseGrammar { Revision = 1 });
cache.Upsert(new SimpleCommodityData { Name = "Wiring Ore", Shape = oneCell, MaxStack = 100 });
cache.FlushAsync().Wait();
var ledger = new ProvenanceLedger();
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
// A sun and a planet on a moving rail round it, both near the bodies below: the field under a floater changes with the
// drawn time, so a presenter that samples it at the step's time, drops it, or reads lead 0 for the height draws a different y.
var sunOrbit = cache.Upsert(new OrbitData { FixedPosition = new float2(-55, -.5f) }).Key;
var planetOrbit = cache.Upsert(new OrbitData { Parent = new CultRecordRef<OrbitData>(sunOrbit), Distance = 40f, Phase = 0f }).Key;
var pack = new ZonePack { Radius = 5000f, Mass = 10000f };
pack.Orbits.Add(new CultRecordRef<OrbitData>(sunOrbit));
pack.Orbits.Add(new CultRecordRef<OrbitData>(planetOrbit));
pack.Planets.Add(new CultRecordRef<BodyData>(cache.Upsert(new PlanetData { Name = "Wiring Sun", Orbit = new CultRecordRef<OrbitData>(sunOrbit), Mass = 400f }).Key));
pack.Planets.Add(new CultRecordRef<BodyData>(cache.Upsert(new PlanetData { Name = "Wiring Planet", Orbit = new CultRecordRef<OrbitData>(planetOrbit), Mass = 400f }).Key));
var curves = new PlanetSettings
{
BodyRadius = new ExponentialCurve(),
WellStrength = new ExponentialCurve { Multiplier = 1f, Exponent = 1f },
RocheLimit = new ExponentialCurve { Constant = 10f }
};
var zone = new Zone(items, curves, pack, null, null);
var rootObject = new GameObject("Wiring Zone Root");
var ore = new SimpleCommodity { Data = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<SimpleCommodityData>("Wiring Ore")), Quantity = 7 };
var floating = zone.Release(ore, new float2(-17, 4), new float2(10, 0));
var mine = new Mine
{
Body = new KinematicBody { Position = new float2(-20, 8), Velocity = new float2(0, 14) },
ArmingDelay = 1e6f, FuseDelay = 1f, Lifetime = 1e6f, BlastRadius = 1f
};
zone.Lay(mine);
// Bound the way ZoneRenderer.LoadFloatingItem and LoadMine bind them: the prefab ARPG.unity names, Bind(zone, body, settings).
// (A ZoneRenderer would load this zone's sun and planet too, and that needs the planet prefab, the map and a GPU.)
var pickup = UnityEngine.Object.Instantiate(pickupPrefab, rootObject.transform);
pickup.Bind(zone, floating, settings);
var mineInstance = UnityEngine.Object.Instantiate(minePrefab, rootObject.transform);
mineInstance.Bind(zone, mine, settings);
// A round on its frozen line, no target and no outcome known: drawn at FireOrigin + direction * speed * (T - FireTime).
var shot = new PendingShot
{
ShotId = 1, FireOrigin = new float3(0, 0, 0), FireTime = 0f, TravelDirection = new float2(1, 0), Speed = RoundSpeed,
MaxRange = 1e6f, FireRange = 1e6f, CommitTime = 1e5f, ArrivalTime = 2e5f
};
var projectile = new GameObject("Wiring Round").AddComponent<Projectile>();
projectile.Launch(null, zone, shot, new Vector3(0, 0, 0));
yield return null;
zone.Update(.5f);
var pickupVelocity = floating.Body.TotalVelocity;
var mineVelocity = mine.Body.TotalVelocity;
Require(Mathf.Abs(pickupVelocity.x) > 1f && Mathf.Abs(mineVelocity.y) > 1f, "The fixture's bodies stopped: a lead would not move them.");
// The field under each floater really is a function of the drawn time here, by more than the tolerance at the largest lead.
const float LargestLead = .015f;
foreach (var at in new[] { floating.Body.Position + pickupVelocity * LargestLead, mine.Body.Position + mineVelocity * LargestLead })
Require(Mathf.Abs(DrawAhead.Height(zone, at, LargestLead) - zone.GetHeight(at, zone.ExactTime)) > 5 * Tolerance,
$"The fixture's field is flat under a floater: height {DrawAhead.Height(zone, at, LargestLead)} at the drawn time, {zone.GetHeight(at, zone.ExactTime)} at the step, planet at {zone.OrbitPositionAt(planetOrbit, zone.ExactTime)}, floater at {at}.");
Vector2 baseline = default;
foreach (var leadSeconds in new[] { 0f, .008f, .015f })
{
ActionGameManager.Clock.Reset();
if (leadSeconds > 0) ActionGameManager.Clock.Advance(leadSeconds, _ => throw new InvalidOperationException("a step ran"));
var lead = ActionGameManager.Clock.Lead;
Require(Mathf.Abs(lead - leadSeconds) < 1e-6f, $"The clock's lead is {lead}, not {leadSeconds}.");
yield return null;
var seen = pickup.transform.position;
var want = floating.Body.Position + pickupVelocity * lead;
Require(Mathf.Abs(seen.x - want.x) < Tolerance && Mathf.Abs(seen.z - want.y) < Tolerance,
$"ItemPickup at lead {lead}: drawn x/z {seen.x},{seen.z}, the step plus velocity times lead {want.x},{want.y}.");
Require(Mathf.Abs(seen.y - DrawAhead.FloaterHeight(zone, settings, new float2(want.x, want.y), lead)) < Tolerance,
$"ItemPickup y {seen.y} is not on the floater layer.");
seen = mineInstance.transform.position;
want = mine.Body.Position + mineVelocity * lead;
Require(Mathf.Abs(seen.x - want.x) < Tolerance && Mathf.Abs(seen.z - want.y) < Tolerance,
$"MineInstance at lead {lead}: drawn x/z {seen.x},{seen.z}, the step plus velocity times lead {want.x},{want.y}.");
Require(Mathf.Abs(seen.y - DrawAhead.FloaterHeight(zone, settings, new float2(want.x, want.y), lead)) < Tolerance,
$"MineInstance y {seen.y} is not on the floater layer.");
// The round's line is a function of the drawn time: it moves RoundSpeed * lead along its direction between leads.
seen = projectile.transform.position;
if (leadSeconds == 0f) baseline = new Vector2(seen.x, seen.z);
var moved = new Vector2(seen.x, seen.z) - baseline;
Require(Mathf.Abs(moved.x - RoundSpeed * lead) < Tolerance && Mathf.Abs(moved.y) < Tolerance,
$"Projectile at lead {lead}: drawn {moved.x},{moved.y} past its lead-0 pose, expected {RoundSpeed * lead},0.");
}
ActionGameManager.Clock.Reset();
Debug.Log($"{Tag} ok: ItemPickup, MineInstance and Projectile draw at the clock's lead (0, 0.008, 0.015 s)");
}
// What ARPG.unity's ZoneRenderer names in the pickup and mine fields, read from the saved scene before the play session
// (a scene cannot be opened inside one) and carried across the domain reload as asset paths.
private static string ReadWiring()
{
var scene = EditorSceneManager.OpenScene("Assets/Scenes/ARPG.unity", OpenSceneMode.Additive);
try
{
var found = UnityEngine.Object.FindObjectsByType<ZoneRenderer>(FindObjectsInactive.Include, FindObjectsSortMode.None)
.Where(r => r.gameObject.scene == scene).ToArray();
Require(found.Length == 1, $"ARPG.unity has {found.Length} ZoneRenderers.");
var wired = found[0];
return string.Join("|", new (string, UnityEngine.Object)[]
{
("SimpleCommodityPickup", wired.SimpleCommodityPickup), ("MinePrefab", wired.MinePrefab)
}.Select(f => f.Item1 + "=" + (f.Item2 == null ? "" : AssetDatabase.GetAssetPath(f.Item2))));
}
finally { EditorSceneManager.CloseScene(scene, true); }
}
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c2270cf68a4af384eb715c8b5a55f5e8

View file

@ -24,7 +24,8 @@ public static class FloatingItemsDrawnSmoke
private const string Flag = "FloatingItemsDrawnSmoke.running";
private const string Wiring = "FloatingItemsDrawnSmoke.wiring";
private static IEnumerator _check;
private static int _frames;
private const double Budget = 300; // real seconds: batch frames can be far shorter than a game second
private static double _started;
private static string _work;
static FloatingItemsDrawnSmoke()
@ -52,6 +53,7 @@ public static class FloatingItemsDrawnSmoke
if (change != PlayModeStateChange.EnteredPlayMode) return;
_work = Path.Combine(Path.GetTempPath(), $"floating-items-smoke-{Guid.NewGuid():N}");
Directory.CreateDirectory(_work);
_started = Time.realtimeSinceStartupAsDouble;
_check = Check();
EditorApplication.update += Step;
}
@ -60,7 +62,7 @@ public static class FloatingItemsDrawnSmoke
{
try
{
if (++_frames > 3000) throw new InvalidOperationException("The check did not finish in 3000 frames.");
if (Time.realtimeSinceStartupAsDouble - _started > Budget) throw new InvalidOperationException($"The check did not finish in {Budget} seconds.");
if (_check.MoveNext()) return;
Finish(0);
}
@ -88,6 +90,8 @@ public static class FloatingItemsDrawnSmoke
{
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset")?.GameplaySettings
?? throw new InvalidOperationException("No Settings.asset.");
// A floater rides its own layer: a nonzero one (never saved back to the asset), so a presenter that drops it fails.
settings.FloaterLayerHeight = -1.5f;
// The wiring: the scene's ZoneRenderer names an ItemPickup prefab in each of the four fields, and none of those
// prefabs keeps a script that no longer exists.
@ -120,7 +124,7 @@ public static class FloatingItemsDrawnSmoke
cache.FlushAsync().Wait();
var ledger = new ProvenanceLedger();
for (var i = 1; i <= 8; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
var items = new ItemManager(cache, ledger, settings, _ => { });
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), null, null);
EquippableItem Make(string name, int lot) => new EquippableItem
{
@ -145,6 +149,7 @@ public static class FloatingItemsDrawnSmoke
typeof(ZoneRenderer).GetField(name).SetValue(renderer, pickup);
renderer.ItemManager = items;
renderer.SectorBrushes = new GameObject("Smoke Brushes").transform;
renderer.AmbientLight = Aetheria.EditorTools.AmbientEdgeVerify.NewAmbientLight();
renderer.SlimeGravityCamera = new GameObject("Smoke Gravity Camera").AddComponent<Camera>();
var slimeObject = new GameObject("Smoke Slime");
slimeObject.SetActive(false); // the real Slime needs its compute shader; LoadZone only sets its radius
@ -155,8 +160,9 @@ public static class FloatingItemsDrawnSmoke
ItemPickup[] Pickups() => rootObject.GetComponentsInChildren<ItemPickup>(true).Where(p => p != null).ToArray();
void At(ItemPickup pickup, FloatingItem floating, string what)
{
var at = floating.Body.Position;
var want = new Vector3(at.x, zone.GetHeight(at), at.y);
var lead = ActionGameManager.Clock.Lead;
var at = DrawAhead.Position(floating.Body, lead);
var want = new Vector3(at.x, DrawAhead.FloaterHeight(zone, settings, at, lead), at.y);
Require(Vector3.Distance(pickup.transform.position, want) < 1e-3f,
$"{what}: the pickup is at {pickup.transform.position}, the body at {want}.");
}
@ -185,10 +191,13 @@ public static class FloatingItemsDrawnSmoke
var earlyPickup = Pickups().Single(p => p.Item == early.Item);
earlyPickup.LabelPersistDuration = 1;
earlyPickup.LabelFadeDuration = .2f;
for (var i = 0; i < 1200 && earlyPickup.ScanLabel.color.a > .05f; i++) yield return null;
// Game time, not frames: batch frames can be far shorter than a game second, so a frame count can run out before the fade.
var labelUntil = Time.time + earlyPickup.LabelPersistDuration + earlyPickup.LabelFadeDuration + 1f;
while (Time.time < labelUntil && earlyPickup.ScanLabel.color.a > .05f) yield return null;
Require(earlyPickup.ScanLabel.color.a <= .05f, "label: an unviewed, unheld label did not go dark.");
Require(zone.TryHold(taker, early.Id), "label: the zone refused to hold the item.");
for (var i = 0; i < 1200 && earlyPickup.ScanLabel.color.a < .95f; i++) yield return null;
labelUntil = Time.time + earlyPickup.LabelPersistDuration + earlyPickup.LabelFadeDuration + 1f;
while (Time.time < labelUntil && earlyPickup.ScanLabel.color.a < .95f) yield return null;
Require(earlyPickup.ScanLabel.color.a >= .95f, "label: a held item's label is not lit.");
// The test scene's stand-in sim and tendril ride the zone's own held-pose curve: its pose at each point of a grab

View file

@ -106,7 +106,7 @@ namespace Aetheria.EditorTools
var cache = AetheriaStores.Open(Path.Combine(work, Guid.NewGuid().ToString("N") + ".cc"), catalogWritable: true);
cache.Upsert(new VerseGrammar { Revision = 1 });
cache.FlushAsync().Wait();
var items = new ItemManager(cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { });
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { });
return new Zone(items, new PlanetSettings { Flow = flow }, new ZonePack { Time = time }, new GalaxyZone { Name = "Parity", Owner = null }, null);
}

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
@ -140,6 +140,9 @@ namespace Aetheria.EditorTools
Call(volume, "Update");
if (Differing(flowing, Frame()) == 0) Fail("scene-stardust-run", "the particle buffer did not move while the zone ran");
if (Shader.GetGlobalFloat("_NebulaNoiseTime") == noiseTime) Fail("scene-noise-clock", "_NebulaNoiseTime did not move while the zone ran");
CheckStardustUncullable(dust, bufferField);
CheckStardustUnderFog(dust, bufferField);
}
finally
{
@ -147,6 +150,214 @@ namespace Aetheria.EditorTools
}
}
// The event Stardust itself attaches its draw at, read from the class so the verifier cannot agree with a copy of it.
static CameraEvent DrawEvent() =>
(CameraEvent) typeof(Stardust).GetField("DrawEvent", BindingFlags.NonPublic | BindingFlags.Static).GetRawConstantValue();
// The dust is a command buffer on the target camera, which no culling sees: with every particle 10000 below the
// camera (far past any box a hand could inflate round it) a render still shows dust, and shows none when the
// same particles have no size.
static void CheckStardustUncullable(Stardust dust, FieldInfo bufferField)
{
var camera = dust.TargetCamera;
if (camera == null) { Fail("scene-stardust-uncullable", "Stardust has no TargetCamera"); return; }
var drawEvent = DrawEvent();
var attached = camera.GetCommandBuffers(drawEvent).Count(buffer => buffer.name == "Stardust");
if (attached != 1) Fail("scene-stardust-uncullable", $"{attached} Stardust command buffers at {drawEvent} on the target camera, expected 1");
foreach (var other in Enum.GetValues(typeof(CameraEvent)).Cast<CameraEvent>().Where(other => other != drawEvent))
{
var stray = camera.GetCommandBuffers(other).Count(buffer => buffer.name == "Stardust");
if (stray != 0) Fail("scene-stardust-uncullable", $"{stray} Stardust command buffers at {other}, expected 0");
}
var particles = (ComputeBuffer) bufferField.GetValue(dust);
float[] Layout(float size, Vector3 centre, float below) => DustLayout(particles.count, size, centre, below);
var transform = camera.transform;
var (position, rotation) = (transform.position, transform.rotation);
var (far, flags, background, mask, target) = (camera.farClipPlane, camera.clearFlags, camera.backgroundColor, camera.cullingMask, camera.targetTexture);
var texture = RenderTexture.GetTemporary(64, 64, 24, RenderTextureFormat.ARGB32);
var readback = new Texture2D(64, 64, TextureFormat.RGBA32, false);
var previous = RenderTexture.active;
// Image effects on the camera would write their own frame over the dust; only the dust draw is under test.
var effects = camera.GetComponents<MonoBehaviour>().Where(behaviour => behaviour.enabled).ToArray();
Color32[] Render(float size, Vector3 centre, float below)
{
Call(dust, "Update"); // on a bounded draw this queues the draw for the camera; here it dispatches the compute
particles.SetData(Layout(size, centre, below));
camera.Render();
RenderTexture.active = texture;
readback.ReadPixels(new Rect(0, 0, 64, 64), 0, 0);
readback.Apply();
return readback.GetPixels32();
}
try
{
foreach (var effect in effects) effect.enabled = false;
camera.clearFlags = CameraClearFlags.SolidColor;
camera.backgroundColor = Color.black;
camera.cullingMask = 1;
camera.targetTexture = texture;
// control: the camera where the scene put it, dust 100 below, inside any box a hand-set extent would give
transform.SetPositionAndRotation(position, Quaternion.Euler(90f, 0f, 0f));
camera.farClipPlane = 1000f;
var nearWith = Render(5f, position, 100f); var nearWithout = Render(0f, position, 100f);
var nearDiff = 0; for (var i = 0; i < nearWith.Length; i++) if (!nearWith[i].Equals(nearWithout[i])) nearDiff++;
Debug.Log($"{Tag} stardust near-control differing pixels={nearDiff}");
if (nearDiff == 0) Fail("scene-stardust-uncullable", "control: dust 100 below the camera drew no pixels");
transform.SetPositionAndRotation(new Vector3(0f, -40000f, 0f), Quaternion.Euler(90f, 0f, 0f));
camera.farClipPlane = 30000f;
var withDust = Render(400f, transform.position, 10000f); // the material scales a quad by distance^.33, so at this range it needs this size to cover pixels
var without = Render(0f, transform.position, 10000f);
var differing = 0;
for (var i = 0; i < withDust.Length; i++)
if (!withDust[i].Equals(without[i])) differing++;
if (differing == 0) Fail("scene-stardust-uncullable", "the dust 10000 below the camera drew no pixels");
}
finally
{
RenderTexture.active = previous;
foreach (var effect in effects) effect.enabled = true;
camera.targetTexture = target;
camera.cullingMask = mask;
camera.backgroundColor = background;
camera.clearFlags = flags;
camera.farClipPlane = far;
transform.SetPositionAndRotation(position, rotation);
RenderTexture.ReleaseTemporary(texture);
UnityEngine.Object.DestroyImmediate(readback);
}
}
// The nebula is an [ImageEffectOpaque] that fogs the whole ray to a depth the dust never writes (B49): dust drawn
// before it is attenuated by the fog, dust drawn after it sits on top at full strength. The dust's contribution to a
// pixel (render with dust minus render without) is read with the cloud renderer off and on, in an HDR target.
static void CheckStardustUnderFog(Stardust dust, FieldInfo bufferField)
{
const int Size = 128;
var camera = dust.TargetCamera;
var cloud = camera == null ? null : camera.GetComponent<VolumeCloudRenderer>();
if (cloud == null) { Fail("scene-stardust-under-fog", "the target camera has no VolumeCloudRenderer"); return; }
var particles = (ComputeBuffer) bufferField.GetValue(dust);
var transform = camera.transform;
var (position, rotation) = (transform.position, transform.rotation);
var (far, flags, background, mask, target, hdr) = (camera.farClipPlane, camera.clearFlags, camera.backgroundColor, camera.cullingMask, camera.targetTexture, camera.allowHDR);
var texture = RenderTexture.GetTemporary(Size, Size, 24, RenderTextureFormat.ARGBHalf);
var readback = new Texture2D(Size, Size, TextureFormat.RGBAHalf, false);
var previous = RenderTexture.active;
var effects = camera.GetComponents<MonoBehaviour>().Where(behaviour => behaviour.enabled).ToArray();
var fields = new System.Collections.Generic.List<Texture2D>();
GameObject floor = null;
float[] Render(bool fog, float size)
{
cloud.enabled = fog;
for (var frame = 0; frame < 2; frame++) // the cloud's first frame forces ULTRA and fills its history; the second is the settled one
{
Call(dust, "Update");
particles.SetData(DustLayout(particles.count, size, position, 100f));
camera.Render();
}
RenderTexture.active = texture;
readback.ReadPixels(new Rect(0, 0, Size, Size), 0, 0);
readback.Apply();
return readback.GetPixels().Select(pixel => pixel.r * .2126f + pixel.g * .7152f + pixel.b * .0722f).ToArray();
}
try
{
foreach (var effect in effects) effect.enabled = false;
camera.allowHDR = true;
camera.clearFlags = CameraClearFlags.SolidColor;
camera.backgroundColor = Color.black;
camera.cullingMask = 1;
camera.targetTexture = texture;
camera.farClipPlane = 1000f;
transform.SetPositionAndRotation(position, Quaternion.Euler(90f, 0f, 0f));
// The fog only measures a partial density where depth is written: a pixel with no depth is fogged whole (CloudShader sets
// density 1 past the far plane). A floor under the dust gives the march a finite ray.
floor = GameObject.CreatePrimitive(PrimitiveType.Plane);
floor.transform.position = position + Vector3.down * 200f;
floor.transform.localScale = Vector3.one * 100f;
UnityEngine.Object.DestroyImmediate(floor.GetComponent<Collider>());
// The scene's own nebula fields are textures the game renders; here a uniform fill of white fog stands in for them.
foreach (var (name, format) in new[] { ("_NebulaSurfaceHeight", TextureFormat.RFloat), ("_NebulaPatchHeight", TextureFormat.RHalf), ("_NebulaPatch", TextureFormat.RHalf) })
Shader.SetGlobalTexture(name, Uniform(format, Color.black, fields));
Shader.SetGlobalTexture("_NebulaTint", Uniform(TextureFormat.RGBA32, Color.white, fields));
Shader.DisableKeyword("NOISE_SLOPE");
Shader.SetGlobalFloat("_NebulaFillDensity", .01f);
Shader.SetGlobalFloat("_NebulaFillDistance", 120f);
Shader.SetGlobalFloat("_NebulaFillExponent", 0f);
Shader.SetGlobalFloat("_NebulaFillOffset", 0f);
Shader.SetGlobalFloat("_NebulaPatchDensity", 0f);
Shader.SetGlobalFloat("_NebulaLuminance", .01f); // a fog dimmer than the dust: dust drawn over it would otherwise read as a negative contribution
Shader.SetGlobalFloat("_ExtinctionCoefficient", .25f); // with the fill density, about 40% of the light to the floor is fogged away
Shader.SetGlobalFloat("_SafetyDistance", -1e6f); // below every sample: only the uniform fill, none of the zone's noise
Shader.SetGlobalFloat("_TintLodExponent", 0f);
var clearBare = Render(false, 0f);
var clearDust = Render(false, 5f);
var fogBare = Render(true, 0f);
var fogDust = Render(true, 5f);
var fogShown = 0f;
var (clearSum, fogSum, drawn) = (0f, 0f, 0);
for (var i = 0; i < clearBare.Length; i++)
{
fogShown += Mathf.Abs(fogBare[i] - clearBare[i]) / clearBare.Length;
if (clearDust[i] == clearBare[i]) continue;
drawn++; clearSum += clearDust[i] - clearBare[i]; fogSum += fogDust[i] - fogBare[i];
}
var (clearMean, fogMean) = (drawn == 0 ? 0f : clearSum / drawn, drawn == 0 ? 0f : fogSum / drawn);
Debug.Log($"{Tag} stardust under fog: fog control mean |difference|={fogShown}, dust pixels={drawn}, dust contribution cloud off={clearMean}, cloud on={fogMean}, ratio={(clearMean == 0f ? 0f : fogMean / clearMean)}");
if (drawn == 0 || clearMean <= 0f) Fail("scene-stardust-under-fog", "control: the dust drew no pixels with the cloud renderer off");
else if (fogShown < 1e-3f) Fail("scene-stardust-under-fog", "control: the cloud renderer changed no pixels, so no fog is in view");
else if (fogMean > .9f * clearMean) Fail("scene-stardust-under-fog", "the dust is not attenuated by the fog in front of it: it was drawn after the nebula composites");
else if (fogMean < .1f * clearMean) Fail("scene-stardust-under-fog", "the dust is erased, not dimmed, by a partial fog");
}
finally
{
RenderTexture.active = previous;
foreach (var effect in effects) effect.enabled = true;
camera.allowHDR = hdr;
camera.targetTexture = target;
camera.cullingMask = mask;
camera.backgroundColor = background;
camera.clearFlags = flags;
camera.farClipPlane = far;
transform.SetPositionAndRotation(position, rotation);
RenderTexture.ReleaseTemporary(texture);
UnityEngine.Object.DestroyImmediate(readback);
foreach (var field in fields) UnityEngine.Object.DestroyImmediate(field);
if (floor != null) UnityEngine.Object.DestroyImmediate(floor);
}
}
static Texture2D Uniform(TextureFormat format, Color colour, System.Collections.Generic.List<Texture2D> owner)
{
var texture = new Texture2D(1, 1, format, false) { wrapMode = TextureWrapMode.Clamp };
texture.SetPixel(0, 0, colour);
texture.Apply();
owner.Add(texture);
return texture;
}
// A square patch of particles 4 apart, `below` under `centre`.
static float[] DustLayout(int count, float size, Vector3 centre, float below)
{
var side = Mathf.RoundToInt(Mathf.Sqrt(count));
var data = new float[count * 7];
for (var i = 0; i < count; i++)
{
data[i * 7 + 0] = centre.x + ((i % side) - side / 2f) * 4f;
data[i * 7 + 1] = centre.y - below;
data[i * 7 + 2] = centre.z + ((i / side) - side / 2f) * 4f;
data[i * 7 + 3] = data[i * 7 + 4] = data[i * 7 + 5] = 1f;
data[i * 7 + 6] = size;
}
return data;
}
// CloudShader's pass 0 compiles with the flow keywords (it must #include_with_pragmas the shared include).
static void CheckCloudKeywords()
{

View file

@ -0,0 +1,428 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using GameCult.Caching;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Rendering;
using CultMath;
namespace Aetheria.EditorTools
{
/// <summary>
/// Batchmode check for campaign aetheria-gravity, cut grid-lowering: what the Gravity Field pass draws is the sim's
/// height. It builds a zone from Settings.asset's PlanetSettings with three bodies, runs the real lowering
/// (GravityLowering.Lower and CreateField), renders the field through an orthographic camera that culls layer 9 into
/// a float target, and compares every texel with -Zone.GetHeight at the texel centre's world position. It then checks
/// that the texture's gradient points where Zone.GetForce does, and that ARPG.unity and the body prefabs are wired
/// the way the cut leaves them (the gravity cameras cull layer 9 alone, nothing but the run-time field sits on it).
///
/// Renders, so it needs a graphics device: run WITHOUT -nographics (as FlowFieldParityVerify).
/// Unity -batchmode -projectPath &lt;repo&gt; -executeMethod Aetheria.EditorTools.GravityFieldParityVerify.Run -logFile &lt;path&gt;
/// Do not pass -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
/// </summary>
public static class GravityFieldParityVerify
{
const string Tag = "[GravityFieldParityVerify]";
const int Size = 256;
// Measured on this tree (the "max error" line this run prints): the largest |texel + GetHeight| over the windows of the three
// frames is 0.0103 u, in the moved-again frame's sun window (float32 on both sides, the GPU's log and the closed form's
// single-precision floor of 1.5e-5 per unit strength against the CPU's, the quad's interpolated world position). The
// tolerance is 2.9x that. A band whose terms were drawn from the wrong edge (reference, inner or outer) moves the
// drawn height by whole units.
const float Tolerance = 0.03f;
// The worst angle between the texture's central difference and GetForce (B28: 1.0 degrees median zone, 2.9 worst).
const float GradientDegrees = 3f;
// Below this force (u/s^2) the drawn height differs between neighbouring texels by less than float32 resolves.
const float FlatForce = 1e-3f;
static float _maxError, _maxAngle, _maxTau;
static bool _ok = true;
static void OnLog(string message, string stack, LogType type)
{
if ((type == LogType.Error || type == LogType.Exception || type == LogType.Assert) && !message.StartsWith(Tag))
Fail("logged-error", message.Split('\n')[0]);
}
static void Fail(string check, string detail)
{
Debug.LogError($"{Tag} FAIL {check}: {detail}");
_ok = false;
}
public static void Run()
{
if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Null)
{
Debug.LogError($"{Tag} FAIL: no graphics device in this session; rerun batchmode without -nographics.");
EditorApplication.Exit(1);
return;
}
Application.logMessageReceived += OnLog;
var work = Path.Combine(Path.GetTempPath(), "gravity-parity-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(work);
ComputeBuffer wells = null;
GameObject field = null;
try
{
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset")
?? throw new InvalidOperationException("No Settings.asset.");
var material = AssetDatabase.LoadAssetAtPath<Material>("Assets/Materials/Brushes/Gravity Field.mat")
?? throw new InvalidOperationException("No Gravity Field.mat.");
var zone = BuildZone(settings, work, out var orbits, multiRoot: true);
GravityLowering.Lower(zone, 0f, ref wells);
field = GravityLowering.CreateField(material);
// Three frames through the one buffer, as ZoneRenderer.Update runs it: the bodies as built, then two moves (an
// orbit's Position is what Zone.GetHeight reads). A move keeps the tree's geometry, since a rail never leaves
// its parent's distance and the walk skips a subtree outside its reach: the sun is the root, the planet is
// its Distance from the sun, the planetoid its Distance from the planet. A lowering that mirrors an axis or
// stops refreshing the upload agrees with the sim in the first frame only where it is symmetric.
CheckFrame("built", zone, orbits);
orbits[1].Position = Around(orbits[0], orbits[1], .31f);
orbits[2].Position = Around(orbits[1], orbits[2], .77f);
orbits[5].Position = Around(orbits[1], orbits[5], .41f);
GravityLowering.Lower(zone, 0f, ref wells);
CheckFrame("moved", zone, orbits);
orbits[0].Position = new float2(210f, -95f);
orbits[1].Position = Around(orbits[0], orbits[1], .52f);
orbits[2].Position = Around(orbits[1], orbits[2], .05f);
orbits[5].Position = Around(orbits[1], orbits[5], .93f);
GravityLowering.Lower(zone, 0f, ref wells);
CheckFrame("moved-again", zone, orbits);
if (_maxTau <= 1f) Fail("series-branch", $"the largest tau over the drawn bounded terms is {_maxTau:G4}; no window draws the series branch");
Debug.Log($"{Tag} largest drawn tau {_maxTau:G4}");
Debug.Log($"{Tag} max error {_maxError:G4} u (tolerance {Tolerance:G4}); max gradient angle {_maxAngle:F2} degrees (limit {GradientDegrees:F0})");
CheckWiring(settings);
Debug.Log(_ok ? $"{Tag} PASS" : $"{Tag} FAIL: see errors above");
}
catch (Exception e)
{
Debug.LogError($"{Tag} FAIL exception: {e}");
_ok = false;
}
finally
{
Application.logMessageReceived -= OnLog;
if (field != null) UnityEngine.Object.DestroyImmediate(field);
wells?.Release();
try { Directory.Delete(work, true); } catch { }
}
EditorApplication.Exit(_ok ? 0 : 1);
}
static float Core(Zone zone, float mass) => zone.Settings.RocheLimit.Evaluate(mass);
// Where an orbit lies when it stands `turns` round its parent at its Distance.
static float2 Around(Orbit parent, Orbit child, float turns) => parent.Position + child.Data.Distance * OrbitData.Evaluate(turns);
static Vector2 At(Orbit orbit) => new Vector2(orbit.Position.x, orbit.Position.y);
// Orbits 0-2 are the sun, the planet and the planetoid, each window centred where the body is now; orbit 3 is the
// body of no strength, orbit 4 the second sun and orbit 5 the planet's moon inside its Roche limit (whose band has
// tau above 1: the series branch of the shader). The zone window holds all of them; the far window is centred on the
// zone node, so its band (nothing inside A, the zone's mass beyond Beta A) is drawn. A window draws a term when
// the term's disc reaches it; the largest tau over the drawn terms must exceed 1 or the series branch went unchecked.
static void CheckFrame(string frame, Zone zone, Orbit[] orbits)
{
CheckTexels(frame + "-planetoid", zone, At(orbits[2]), 4f * Core(zone, 29.2f));
CheckTexels(frame + "-planet", zone, At(orbits[1]), 4f * Core(zone, 273.9f));
CheckTexels(frame + "-sun", zone, At(orbits[0]), 4f * Core(zone, 14152f));
CheckTexels(frame + "-zone", zone, Vector2.zero, 1.05f * zone.Pack.Radius);
var centre = new Vector2(zone.Wells.Centre.x, zone.Wells.Centre.y);
var span = 0f;
foreach (var root in new[] { orbits[0], orbits[4] })
span = Math.Max(span, (At(root) - centre).magnitude + zone.Wells.Reach(zone.Orbits.First(o => o.Value == root).Key));
// The wells have their own windows: out here a texel is 40 u wide, so only the ring from 0.9 A outward, where the
// zone node's band rises and the zone becomes one body, is measured.
CheckTexels(frame + "-far", zone, centre, 1.6f * span, .9f * span);
CheckGradient(zone, orbits.Select(At).ToArray());
}
// A sun (14152 at (-130, 85)), its planet (273.9, placed at (610, -240) at the pack's time), the planet's planetoid
// (29.2, placed at (670, -270)) and a body of mass 0, whose well has no strength (a legal body; its curves give
// c = 0 and R = 0), at (1700, 300): a family, so the band terms are drawn (the planet's and the sun's cusps and
// fades), off both axes and asymmetric, so the zone's wells sit where the verify says before any Update and a
// mirrored or swapped axis cannot agree with the sim. The sun and the zero-mass body are fixed roots.
internal static Zone BuildZone(GameSettings settings, string work, out Orbit[] orbits, float radius = 4182f, bool multiRoot = false)
{
var cache = AetheriaStores.Open(Path.Combine(work, "catalog.cc"), Path.Combine(work, "run.cc"), catalogWritable: true);
cache.Upsert(new VerseGrammar { Revision = 1 });
cache.FlushAsync().Wait();
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { });
var positions = new List<Vector2> { new Vector2(-130f, 85f), new Vector2(610f, -240f), new Vector2(670f, -270f), new Vector2(1700f, 300f) };
var masses = new List<float> { 14152f, 273.9f, 29.2f, 0f };
var parents = new List<int> { -1, 0, 1, -1 };
if (multiRoot)
{
// A second sun (a second subzone, so the zone node exists) and a moon of the planet 0.6 of its Roche limit away.
positions.Add(new Vector2(-2400f, -1900f));
masses.Add(3000f);
parents.Add(-1);
positions.Add(positions[1] + new Vector2(-.6f * settings.PlanetSettings.RocheLimit.Evaluate(273.9f), 0f));
masses.Add(1f);
parents.Add(1);
}
var keys = new CultRecordKey[positions.Count];
var pack = new ZonePack { Radius = radius };
for (var i = 0; i < positions.Count; i++)
{
var data = new OrbitData { FixedPosition = new float2(positions[i].x, positions[i].y) };
if (parents[i] >= 0)
{
var offset = positions[i] - positions[parents[i]];
data.Parent = new CultRecordRef<OrbitData>(keys[parents[i]]);
data.Distance = offset.magnitude;
data.Phase = Mathf.Atan2(offset.y, offset.x) / (2f * Mathf.PI);
}
var orbit = keys[i] = cache.Upsert(data).Key;
var body = cache.Upsert(new PlanetData
{ Name = "Body " + i, Orbit = new CultRecordRef<OrbitData>(orbit), Mass = masses[i] }).Key;
pack.Orbits.Add(new CultRecordRef<OrbitData>(orbit));
pack.Planets.Add(new CultRecordRef<BodyData>(body));
}
var zone = new Zone(items, settings.PlanetSettings, pack, new GalaxyZone { Name = "Gravity Parity", Owner = null }, null);
orbits = keys.Select(k => zone.Orbits[k]).ToArray();
return zone;
}
// Renders the field through a downward orthographic camera that culls layer 9 only, centred on `centre` with
// half-size `half`, and returns the depth of every texel with the world xz of the texel's centre, which is asked
// of the camera rather than assumed.
static void Render(Vector2 centre, float half, out float[] depth, out Vector2[] world)
{
var rt = new RenderTexture(Size, Size, 0, RenderTextureFormat.RFloat) { filterMode = FilterMode.Point };
var cameraObject = new GameObject("Gravity Parity Camera");
var read = new Texture2D(Size, Size, TextureFormat.RFloat, false);
var previous = RenderTexture.active;
try
{
var camera = cameraObject.AddComponent<Camera>();
camera.orthographic = true;
camera.orthographicSize = half;
camera.aspect = 1f;
camera.nearClipPlane = .3f;
camera.farClipPlane = 1000f;
camera.cullingMask = 1 << GravityLowering.FieldLayer;
camera.clearFlags = CameraClearFlags.SolidColor;
camera.backgroundColor = Color.black;
camera.allowHDR = false;
camera.allowMSAA = false;
camera.targetTexture = rt;
cameraObject.transform.SetPositionAndRotation(new Vector3(centre.x, 100f, centre.y), Quaternion.Euler(90f, 0f, 0f));
camera.Render();
RenderTexture.active = rt;
read.ReadPixels(new Rect(0, 0, Size, Size), 0, 0);
read.Apply();
depth = read.GetPixelData<float>(0).ToArray();
world = new Vector2[Size * Size];
for (var y = 0; y < Size; y++)
for (var x = 0; x < Size; x++)
{
var p = camera.ViewportToWorldPoint(new Vector3((x + .5f) / Size, (y + .5f) / Size, 100f));
world[y * Size + x] = new Vector2(p.x, p.z);
}
}
finally
{
RenderTexture.active = previous;
UnityEngine.Object.DestroyImmediate(cameraObject);
UnityEngine.Object.DestroyImmediate(read);
rt.Release();
UnityEngine.Object.DestroyImmediate(rt);
}
}
// The largest tau = (a^2 + eps^2) / (b^2 - a^2) over the bounded terms whose disc reaches the window's square.
static void NoteTau(Zone zone, Vector2 centre, float half)
{
foreach (var term in zone.Wells.Terms)
{
if (float.IsPositiveInfinity(term.Outer)) continue;
var at = term.Node.IsSet() ? zone.Orbits[term.Node].Position : zone.Wells.Centre;
if (Math.Abs(at.x - centre.x) > half + term.Outer || Math.Abs(at.y - centre.y) > half + term.Outer) continue;
_maxTau = Math.Max(_maxTau, (term.Inner * term.Inner + term.Softening * term.Softening) / (term.Outer * term.Outer - term.Inner * term.Inner));
}
}
static void CheckTexels(string window, Zone zone, Vector2 centre, float half, float ringFrom = 0f)
{
Render(centre, half, out var depth, out var world);
NoteTau(zone, centre, half);
var worst = 0f;
var live = 0;
var measured = 0;
for (var i = 0; i < depth.Length; i++)
{
if ((world[i] - centre).magnitude < ringFrom) continue;
measured++;
var expected = -zone.GetHeight(new float2(world[i].x, world[i].y));
if (float.IsNaN(depth[i])) { Fail($"{window}-texels", $"texel {i}: NaN"); return; }
var error = Math.Abs(depth[i] - expected);
worst = Math.Max(worst, error);
if (Math.Abs(expected) > 1f) live++;
if (error > Tolerance)
{
Fail($"{window}-texels", $"texel {i} at ({world[i].x:F2}, {world[i].y:F2}): expected {expected:G6}, drawn {depth[i]:G6}, error {error:G4} > {Tolerance:G4}");
return;
}
}
_maxError = Math.Max(_maxError, worst);
// A window that draws nothing agrees with a zone that is flat: the check must be live.
if (live < measured / 2) Fail($"{window}-texels", $"only {live} of {measured} texels are deeper than 1 u; the check is vacuous");
Debug.Log($"{Tag} {window}: {depth.Length} texels, max error {worst:G4} u");
}
// The texture's central difference (over two 4 u texels, mid-zone) points where GetForce does: depth = -h, so its
// gradient is the force over the exaggeration. This is what the slope readers (the shader's slopeFlow, the nebula
// surface normal, the drawn grid) take from the texture.
static void CheckGradient(Zone zone, Vector2[] bodies)
{
Render(new Vector2(1500f, 0f), 512f, out var depth, out var world);
var compared = 0;
for (var y = 1; y < Size - 1; y++)
for (var x = 1; x < Size - 1; x++)
{
var here = world[y * Size + x];
if (bodies.Any(b => (b - here).magnitude < 16f)) continue;
// The world step of each texel axis is read from the camera, so a flipped or rotated target is still measured.
var au = world[y * Size + x + 1] - world[y * Size + x - 1];
var av = world[(y + 1) * Size + x] - world[(y - 1) * Size + x];
var du = depth[y * Size + x + 1] - depth[y * Size + x - 1];
var dv = depth[(y + 1) * Size + x] - depth[(y - 1) * Size + x];
var det = au.x * av.y - au.y * av.x;
var g = new Vector2((du * av.y - dv * au.y) / det, (dv * au.x - du * av.x) / det);
var f = zone.GetForce(new float2(here.x, here.y));
if (CultMath.math.length(f) < FlatForce) continue; // between the subzones the ground is flat: no direction to compare
var angle = Vector2.Angle(g, new Vector2(f.x, f.y));
if (float.IsNaN(angle) || angle > GradientDegrees)
{
Fail("gradient", $"at ({here.x:F1}, {here.y:F1}) the drawn slope points {angle:F2} degrees from the force (limit {GradientDegrees:F0})");
return;
}
_maxAngle = Math.Max(_maxAngle, angle);
compared++;
}
if (compared < Size * Size / 2) Fail("gradient", $"only {compared} texels were compared");
}
// Main Menu.unity has no ZoneRenderer, so no Gravity Field and no lowered potential: its gravity cameras draw the
// menu's own brush objects (Boundary on layer 9, the Waves on layer 10) and are presentation by spec (follow-up
// menu-grid-brushes), not a reader of the potential. This pins that: a ZoneRenderer in the menu would make its
// cameras readers of the field, and they would have to cull layer 9 alone like ARPG's.
static void CheckTestSceneWiring()
{
EditorSceneManager.OpenScene("Assets/Scenes/TestScene.unity", OpenSceneMode.Single);
try
{
if (CheckPatchCameras("TestScene.unity") < 1) Fail("wiring", "no Fog Patch Displacement camera in TestScene.unity");
}
finally
{
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
}
}
static void CheckMenuWiring()
{
var scene = EditorSceneManager.OpenScene("Assets/Scenes/Main Menu.unity", OpenSceneMode.Single);
try
{
if (UnityEngine.Object.FindFirstObjectByType<ZoneRenderer>(FindObjectsInactive.Include) != null)
Fail("wiring", "Main Menu.unity has a ZoneRenderer: its gravity cameras now read the Gravity Field and must cull layer 9 alone");
var seen = 0;
foreach (var camera in UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsInactive.Include, FindObjectsSortMode.None))
{
if (camera.targetTexture == null || camera.targetTexture.name != "Fog Surface Displacement") continue;
seen++;
const int menuBrushLayers = (1 << 9) | (1 << 10);
if (camera.cullingMask != menuBrushLayers)
Fail("wiring", $"Main Menu camera {camera.name} culls mask {camera.cullingMask}, not the menu brush layers 9 and 10");
}
if (seen != 1) Fail("wiring", $"{seen} Fog Surface Displacement cameras in Main Menu.unity, expected 1");
if (CheckPatchCameras("Main Menu.unity") < 1) Fail("wiring", "no Fog Patch Displacement camera in Main Menu.unity");
}
finally
{
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
}
}
// Every scene's Fog Patch Displacement camera: cloudDensity adds the R32 gravity height to the patch texture, so
// a patch camera that culls the gravity layer counts the field twice. Returns how many it found in the open scene.
static int CheckPatchCameras(string sceneName)
{
var found = 0;
foreach (var camera in UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsInactive.Include, FindObjectsSortMode.None))
{
if (camera.targetTexture == null || camera.targetTexture.name != "Fog Patch Displacement") continue;
found++;
if ((camera.cullingMask & 1 << GravityLowering.FieldLayer) != 0)
Fail("wiring", $"camera {camera.name} (target Fog Patch Displacement) in {sceneName} culls layer {GravityLowering.FieldLayer}, the gravity layer");
}
return found;
}
static void CheckWiring(GameSettings settings)
{
var scene = EditorSceneManager.OpenScene("Assets/Scenes/ARPG.unity", OpenSceneMode.Single);
try
{
var gravityTargets = new[] { "Fog Surface Displacement", "Slime Gravity", "MinimapGravity" };
var seen = 0;
foreach (var camera in UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsInactive.Include, FindObjectsSortMode.None))
{
if (camera.targetTexture == null || !gravityTargets.Contains(camera.targetTexture.name)) continue;
seen++;
if (camera.cullingMask != 1 << GravityLowering.FieldLayer)
Fail("wiring", $"camera {camera.name} (target {camera.targetTexture.name}) culls mask {camera.cullingMask}, not layer {GravityLowering.FieldLayer} alone");
}
if (seen < gravityTargets.Length) Fail("wiring", $"only {seen} gravity cameras found in ARPG.unity");
// The patch texture is half-float: it holds the patch layers only, the nebula reads gravity from the R32 surface.
if (CheckPatchCameras("ARPG.unity") != 1) Fail("wiring", "ARPG.unity does not have exactly 1 Fog Patch Displacement camera");
// The map screen's gravity camera has no target until MapRenderer gives it one at run time.
var map = UnityEngine.Object.FindFirstObjectByType<MapRenderer>(FindObjectsInactive.Include);
if (map == null || map.GravityCamera == null) Fail("wiring", "no MapRenderer.GravityCamera in ARPG.unity");
else if (map.GravityCamera.cullingMask != 1 << GravityLowering.FieldLayer)
Fail("wiring", $"MapRenderer.GravityCamera culls mask {map.GravityCamera.cullingMask}, not layer {GravityLowering.FieldLayer} alone");
var renderer = UnityEngine.Object.FindFirstObjectByType<ZoneRenderer>(FindObjectsInactive.Include);
if (renderer == null) Fail("wiring", "no ZoneRenderer in ARPG.unity");
else if (renderer.GravityField == null || renderer.GravityField.shader.name != "Brushes/Gravity Field")
Fail("wiring", "ZoneRenderer.GravityField is not a Brushes/Gravity Field material");
foreach (var r in UnityEngine.Object.FindObjectsByType<Renderer>(FindObjectsInactive.Include, FindObjectsSortMode.None))
if (r.gameObject.layer == GravityLowering.FieldLayer)
Fail("wiring", $"renderer {r.name} in ARPG.unity sits on layer {GravityLowering.FieldLayer}");
}
finally
{
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
}
CheckMenuWiring();
CheckTestSceneWiring();
foreach (var path in new[]
{
"Assets/Prefabs/RPG/Planets/Planet.prefab", "Assets/Prefabs/RPG/Planets/Gas Giant.prefab",
"Assets/Prefabs/RPG/Planets/Sun.prefab", "Assets/Prefabs/RPG/Wormhole 1.prefab"
})
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (prefab == null) { Fail("wiring", $"no prefab at {path}"); continue; }
foreach (var r in prefab.GetComponentsInChildren<Renderer>(true))
if (r.gameObject.layer == GravityLowering.FieldLayer)
Fail("wiring", $"renderer {r.name} in {path} sits on layer {GravityLowering.FieldLayer}");
}
}
}
}

View file

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 88b5f578fe63a904b997331630e61416
guid: 1ceb2bdab0804463b1998471d47e82fa
MonoImporter:
externalObjects: {}
serializedVersion: 2

View file

@ -0,0 +1,116 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using GameCult.Caching;
using UnityEditor;
using CultMath;
using UnityEngine;
using float4 = CultMath.float4;
using Debug = UnityEngine.Debug;
// The map's belt cost per frame, CPU milliseconds and managed allocation, for belts of 1000, 2250 (the generated
// maximum, Settings.asset) and 4000 asteroids: the base revision's AsteroidBeltUI.Update (MapBeltPerfBase, a verbatim copy)
// and MapPresenter's belt upload and draw, each as the median and interquartile range of 21 runs of 300 frames after
// a warm-up, in this one Unity job. CPU time only: GPU cost is not covered. Needs a GPU device.
//
// Unity -batchmode -projectPath <project> -executeMethod MapBeltPerf.Run -logFile <log>
public static class MapBeltPerf
{
private const int Warmup = 60, Frames = 300, RunCount = 21;
public static void Run()
{
var work = Path.Combine(Path.GetTempPath(), $"map-belt-perf-{Guid.NewGuid():N}");
try
{
Directory.CreateDirectory(work);
foreach (var count in new[] { 1000, 2250, 4000 }) Measure(work, count);
EditorApplication.Exit(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
private static void Measure(string work, int count)
{
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset");
var cache = AetheriaStores.Open(Path.Combine(work, $"c{count}.cc"), Path.Combine(work, $"r{count}.cc"), catalogWritable: true);
cache.Upsert(new VerseGrammar { Revision = 1 });
var sunOrbit = cache.Upsert(new OrbitData()).Key;
var sunKey = cache.Upsert(new PlanetData { Name = "Sun", Orbit = new CultRecordRef<OrbitData>(sunOrbit), Mass = 400f }).Key;
var beltOrbit = cache.Upsert(new OrbitData { Parent = new CultRecordRef<OrbitData>(sunOrbit), Distance = 900f, Phase = .42f }).Key;
var asteroids = Enumerable.Range(0, count).Select(i => new Asteroid
{ Distance = 120f + i * .01f, Phase = CultMath.math.frac(i * .618f), Size = CultMath.math.frac(i * .37f + .1f), RotationSpeed = .15f + i * 1e-4f }).ToArray();
var beltKey = cache.Upsert(new AsteroidBeltData { Orbit = new CultRecordRef<OrbitData>(beltOrbit), Asteroids = asteroids }).Key;
var pack = new ZonePack
{
Orbits = { new CultRecordRef<OrbitData>(sunOrbit), new CultRecordRef<OrbitData>(beltOrbit) },
Planets = { new CultRecordRef<BodyData>(sunKey), new CultRecordRef<BodyData>(beltKey) },
Radius = 5000f, Mass = 10000f, Time = 0
};
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { });
var zone = new Zone(items, new PlanetSettings
{
BodyRadius = new ExponentialCurve(), WellStrength = new ExponentialCurve { Multiplier = 1f, Exponent = 1f },
RocheLimit = new ExponentialCurve { Constant = 2f },
AsteroidSize = new ExponentialLerp { Minimum = 2f, Maximum = 9f, Exponent = 1.7f },
AsteroidHitpoints = new ExponentialLerp { Minimum = 10f, Maximum = 300f, Exponent = 2f },
AsteroidRespawnTime = new ExponentialLerp { Minimum = 5f, Maximum = 60f, Exponent = 1f }
}, pack, new GalaxyZone { Name = "Perf", Owner = null }, null);
var transforms = new float4[count];
var presenter = new MapPresenter(new Material[0], AssetDatabase.LoadAssetAtPath<Material>("Assets/Materials/UI/MapAsteroid.mat"), 4, 4, 3f);
presenter.RegisterBelt(beltKey, transforms);
var baseBelt = new MapBeltPerfBase(zone, zone.AsteroidBelts[beltKey], 4, 4, 3f);
Report("base", count, Runs(zone, beltKey, transforms, () => baseBelt.Update(transforms, 0f)));
Report("head", count, Runs(zone, beltKey, transforms, () => presenter.Draw(zone, 0)));
presenter.Clear();
}
// Each run is Frames frames after Warmup frames; the figure is one run's CPU ms per frame and managed bytes per frame.
private static (double ms, long bytes)[] Runs(Zone zone, CultRecordKey beltKey, float4[] transforms, Action frame)
{
var results = new (double, long)[RunCount];
var stopwatch = new Stopwatch();
var time = 0.0;
for (var run = -1; run < RunCount; run++)
{
stopwatch.Reset();
long allocated = 0;
for (var i = 0; i < Warmup + Frames; i++)
{
zone.EvaluateBelt(beltKey, transforms, time += 0.016);
var bytes = GC.GetAllocatedBytesForCurrentThread();
if (i >= Warmup) stopwatch.Start();
frame();
if (i >= Warmup) stopwatch.Stop();
if (i >= Warmup) allocated += GC.GetAllocatedBytesForCurrentThread() - bytes;
}
if (run >= 0) results[run] = (stopwatch.Elapsed.TotalMilliseconds / Frames, allocated / Frames);
}
return results;
}
private static double Quantile(double[] sorted, double q)
{
var at = q * (sorted.Length - 1);
var low = (int)Math.Floor(at);
var high = Math.Min(low + 1, sorted.Length - 1);
return sorted[low] + (sorted[high] - sorted[low]) * (at - low);
}
private static void Report(string revision, int count, (double ms, long bytes)[] runs)
{
var ms = runs.Select(r => r.ms).OrderBy(v => v).ToArray();
var bytes = runs.Select(r => (double)r.bytes).OrderBy(v => v).ToArray();
var units = Process.GetProcessesByName("Unity").Length;
Debug.Log($"[MapBeltPerf] revision={revision} asteroids={count} runs={runs.Length} frames_per_run={Frames} " +
$"cpu_ms_median={Quantile(ms, .5):F4} iqr={Quantile(ms, .25):F4}-{Quantile(ms, .75):F4} " +
$"gc_bytes_median={Quantile(bytes, .5):F0} unity_processes={units} cpus={Environment.ProcessorCount}");
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 2bf3d3dcd7e3dd940add2640db93e3fe

View file

@ -0,0 +1,88 @@
using GameCult.Caching;
using CultMath;
using UnityEngine;
using static CultMath.math;
using float2 = CultMath.float2;
using float4 = CultMath.float4;
using Random = UnityEngine.Random;
// The base revision's per-frame belt map path, copied from ZoneRenderer.cs at da6fe033 (class AsteroidBeltUI) so that
// MapBeltPerf can measure it beside MapPresenter in one job. Only the lead it read from ActionGameManager.Clock is
// fixed at zero. Not used by the game.
public class MapBeltPerfBase
{
private Vector3[] _vertices;
private Vector3[] _normals;
private Vector2[] _uvs;
private int[] _indices;
private CultRecordKey _orbitParent;
private Mesh _mesh;
private float _size;
private Zone _zone;
private AsteroidBelt _belt;
private float _scale;
public MapBeltPerfBase(Zone zone, AsteroidBelt belt, int spritesheetWidth, int spritesheetHeight, float scale)
{
_belt = belt;
_zone = zone;
var orbit = zone.Orbits[belt.Data.Orbit.Key];
_orbitParent = orbit.Data.Parent.Key;
_vertices = new Vector3[_belt.Data.Asteroids.Length * 4];
_normals = new Vector3[_belt.Data.Asteroids.Length * 4];
_uvs = new Vector2[_belt.Data.Asteroids.Length * 4];
_indices = new int[_belt.Data.Asteroids.Length * 6];
_scale = scale;
var maxDist = 0f;
var spriteSize = float2(1f / spritesheetWidth, 1f / spritesheetHeight);
// vertex order: bottom left, top left, top right, bottom right
for (var i = 0; i < belt.Data.Asteroids.Length; i++)
{
if (belt.Data.Asteroids[i].Distance > maxDist)
maxDist = belt.Data.Asteroids[i].Distance;
var spriteX = Random.Range(0, spritesheetWidth);
var spriteY = Random.Range(0, spritesheetHeight);
_uvs[i * 4] = new Vector2(spriteX * spriteSize.x, spriteY * spriteSize.y);
_uvs[i * 4 + 1] = new Vector2(spriteX * spriteSize.x, spriteY * spriteSize.y + spriteSize.y);
_uvs[i * 4 + 2] = new Vector2(spriteX * spriteSize.x + spriteSize.x, spriteY * spriteSize.y + spriteSize.y);
_uvs[i * 4 + 3] = new Vector2(spriteX * spriteSize.x + spriteSize.x, spriteY * spriteSize.y);
_indices[i * 6] = i * 4;
_indices[i * 6 + 1] = i * 4 + 1;
_indices[i * 6 + 2] = i * 4 + 3;
_indices[i * 6 + 3] = i * 4 + 3;
_indices[i * 6 + 4] = i * 4 + 1;
_indices[i * 6 + 5] = i * 4 + 2;
}
for (var i = 0; i < _normals.Length; i++) _normals[i] = -Vector3.forward;
_mesh = new Mesh();
_mesh.vertices = _vertices;
_mesh.uv = _uvs;
_mesh.triangles = _indices;
_mesh.normals = _normals;
_mesh.bounds = new Bounds(Vector3.zero, Vector3.one * maxDist);
_size = maxDist;
}
public void Update(float4[] transforms, float height)
{
var parentPosition = DrawAhead.Orbit(_zone, _orbitParent, 0f);
for (var i = 0; i < transforms.Length; i++)
{
var transform = transforms[i];
var rotation = Quaternion.Euler(90, transform.z, 0);
var position = new Vector3(transform.x, height, transform.y);
_vertices[i * 4] = rotation * new Vector3(-transform.w * _scale, -transform.w * _scale, 0) + position;
_vertices[i * 4 + 1] = rotation * new Vector3(-transform.w * _scale, transform.w * _scale, 0) + position;
_vertices[i * 4 + 2] = rotation * new Vector3(transform.w * _scale, transform.w * _scale, 0) + position;
_vertices[i * 4 + 3] = rotation * new Vector3(transform.w * _scale, -transform.w * _scale, 0) + position;
}
_mesh.bounds = new Bounds(new Vector3(parentPosition.x, 0, parentPosition.y), Vector3.one * (_size * 2));
_mesh.vertices = _vertices;
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c3e6b631c94f2384c87cf1906907af9f

View file

@ -0,0 +1,119 @@
using System;
using System.IO;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
// Proof that a planet icon and a ping tint reach the pixels of the real map cameras when the planet's well is deeper
// than 1000 below the camera. The ARPG scene's own "Map Overlays" and "Map Tint" cameras (position, orthographic size,
// clip planes and culling mask as authored) render into a render texture on a solid black background. For each, in
// order: a control render with nothing drawn; a render with the thing placed 1100 below the camera (a plain quad, and the
// PingEffect prefab), which must equal the control because the far plane clips it; then MapPresenter's draw of the sun
// (well deeper than 2000) or the PingEffect prefab placed where EntityInstance places it, which must differ from the
// control at its pixel. A capture that draws nothing fails the last check, and one that cannot tell drawn from clipped
// fails the middle one.
//
// Unity -batchmode -projectPath <project> -executeMethod MapPresenterCapture.Run -logFile <log>
//
// Needs a GPU device; do not pass -nographics or -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
public static class MapPresenterCapture
{
private const int Size = 64;
public static void Run()
{
var work = Path.Combine(Path.GetTempPath(), $"map-presenter-capture-{Guid.NewGuid():N}");
try
{
Directory.CreateDirectory(work);
Check(work);
Debug.Log("[MapPresenterCapture] ok");
EditorApplication.Exit(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
private static void Require(bool condition, string what)
{
if (!condition) throw new InvalidOperationException(what);
}
private static Camera SceneCamera(string name)
{
foreach (var camera in UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsInactive.Include, FindObjectsSortMode.None))
if (camera.name == name) return camera;
throw new InvalidOperationException($"No camera named {name} in the scene.");
}
private static Color Render(Camera camera, RenderTexture target, Vector3 at)
{
camera.targetTexture = target;
camera.clearFlags = CameraClearFlags.SolidColor;
camera.backgroundColor = Color.black;
camera.Render();
var viewport = camera.WorldToViewportPoint(new Vector3(at.x, 0, at.z));
var previous = RenderTexture.active;
RenderTexture.active = target;
var read = new Texture2D(Size, Size, TextureFormat.RGBA32, false);
read.ReadPixels(new Rect(0, 0, Size, Size), 0, 0);
RenderTexture.active = previous;
var pixel = read.GetPixel(Mathf.Clamp((int)(viewport.x * Size), 0, Size - 1), Mathf.Clamp((int)(viewport.y * Size), 0, Size - 1));
UnityEngine.Object.DestroyImmediate(read);
return pixel;
}
private static float Distance(Color a, Color b) => Mathf.Abs(a.r - b.r) + Mathf.Abs(a.g - b.g) + Mathf.Abs(a.b - b.b);
private static void Check(string work)
{
EditorSceneManager.OpenScene("Assets/Scenes/ARPG.unity", OpenSceneMode.Single);
var f = MapPresenterSmoke.Build(work, flatIcons: true);
f.Map.SetMinimapScale(300f);
var target = new RenderTexture(Size, Size, 24, RenderTextureFormat.ARGB32);
var layer = LayerMask.NameToLayer("Minimap");
// Planet: the sun's icon through the map overlays camera.
var overlays = SceneCamera("Map Overlays");
Require((overlays.cullingMask & (1 << layer)) != 0, "The Map Overlays camera does not see the Minimap layer.");
var well = f.Zone.GetHeight(DrawAhead.Orbit(f.Zone, f.Zone.PlanetInstances[f.SunKey].BodyData.Orbit.Key, 0), 0);
Require(well < -1000f, $"The fixture sun's well is {well}, not deeper than 1000.");
Require(f.Map.TryDrawn(f.SunKey, out var sun), "The sun registered no icon.");
var sunAt = sun.GetPosition();
// Control: the pixel with nothing queued. Then a quad 1100 below the camera, deeper than the far plane, which must
// leave the pixel as it was. Then the presenter's draw, which must change it. Draws queue for the cameras that
// render after them, so the order is empty, deep, drawn.
var empty = Render(overlays, target, sunAt);
var quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
var deepMesh = quad.GetComponent<MeshFilter>().sharedMesh;
var deepMaterial = new Material(Shader.Find("Unlit/Color")) { color = Color.white };
UnityEngine.Object.DestroyImmediate(quad);
Graphics.RenderMesh(new RenderParams(deepMaterial) { layer = layer }, deepMesh, 0,
Matrix4x4.TRS(new Vector3(sunAt.x, -1100f, sunAt.z), Quaternion.Euler(90, 0, 0), Vector3.one * 300f));
var deep = Render(overlays, target, sunAt);
Require(Distance(deep, empty) < .01f, $"A mesh 1100 below the camera drew: {deep} against {empty}.");
f.Map.Draw(f.Zone, 0);
var drawn = Render(overlays, target, sunAt);
Require(Distance(drawn, empty) > .3f, $"The sun's icon is not in the pixels: drawn {drawn}, empty {empty}.");
// Ping tint: the PingEffect prefab where EntityInstance places it, through the map tint camera.
var tint = SceneCamera("Map Tint");
var ping = (GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Content/Prefabs/PingEffect.prefab"));
ping.transform.localScale = Vector3.one * 300f;
var pingAt = new Vector3(sunAt.x, MapPresenter.PlaneY, sunAt.z);
ping.transform.position = pingAt;
ping.SetActive(false);
var untinted = Render(tint, target, pingAt);
ping.SetActive(true);
ping.transform.position = new Vector3(sunAt.x, -1100f, sunAt.z);
var deepPing = Render(tint, target, pingAt);
Require(Distance(deepPing, untinted) < .01f, $"A ping 1100 below the camera drew: {deepPing} against {untinted}.");
ping.transform.position = pingAt;
var tinted = Render(tint, target, pingAt);
Require(Distance(tinted, untinted) > .02f, $"The ping tint is not in the pixels: with {tinted}, without {untinted}.");
Debug.Log($"[MapPresenterCapture] well={well} sun drawn={drawn} empty={empty} deep={deep}; ping tinted={tinted} untinted={untinted} deep={deepPing}");
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cfa012aaae63b5044bdfd1b6b44ca9da

View file

@ -0,0 +1,233 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using GameCult.Caching;
using UnityEditor;
using UnityEngine;
using CultMath;
using CultMath.UnityBridge;
using static CultMath.math;
using float2 = CultMath.float2;
using float3 = CultMath.float3;
using float4 = CultMath.float4;
// Batch proof of MapPresenter's contract on a real Zone, without a scene: a sun with a very deep well, a belt, and ships
// registered through EntityInstance.SetEntity as the game does. Planet icons draw at the map plane inside both map
// cameras' clip range however deep the well, a rolled and pitched hull's icon turns by yaw alone, every kind registers one
// draw and leaves no icon child behind, a belt is one instanced draw of every asteroid, and an entity's fade reaches its
// icon the frame it is set. Not reached: prefab, mod ship, station and turret instances (they register through the same
// call), and the rendered pixels (a GPU capture).
//
// Unity -batchmode -projectPath <project> -executeMethod MapPresenterSmoke.Run -logFile <log>
//
// Do not pass -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
public static class MapPresenterSmoke
{
private const BindingFlags Any = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
private const int Asteroids = 24;
public static void Run()
{
var work = Path.Combine(Path.GetTempPath(), $"map-presenter-smoke-{Guid.NewGuid():N}");
try
{
Directory.CreateDirectory(work);
Check(work);
Debug.Log("[MapPresenterSmoke] ok");
EditorApplication.Exit(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
private static void Require(bool condition, string what)
{
if (!condition) throw new InvalidOperationException(what);
}
private static void Awaken(Component component) => component.GetType().GetMethod("Awake", Any)?.Invoke(component, null);
// flat: authored lying on the map plane (facing up), as a real icon is; the smoke's own icons are authored upright
// so that their rotation is exactly the yaw.
private static MeshRenderer NewIcon(Transform parent, bool flat)
{
var icon = GameObject.CreatePrimitive(PrimitiveType.Quad);
icon.layer = LayerMask.NameToLayer("Minimap");
icon.transform.SetParent(parent, false);
if (flat) icon.transform.localRotation = Quaternion.Euler(90, 0, 0);
var renderer = icon.GetComponent<MeshRenderer>();
renderer.sharedMaterial = new Material(Shader.Find("Unlit/Color")) { color = new Color(1, 1, 1, .8f) };
return renderer;
}
// What Build leaves registered with a presenter, for the checks here and for MapPresenterCapture.
internal sealed class Fixture
{
public Zone Zone;
public MapPresenter Map;
public Ship Ship, Other;
public EntityInstance ShipInstance, OtherInstance;
public CultRecordKey SunKey, BeltKey;
public float4[] Belt;
public Transform Roots;
}
private static void Check(string work)
{
var f = Build(work, flatIcons: false);
Checks(f);
}
internal static Fixture Build(string work, bool flatIcons)
{
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset")
?? throw new InvalidOperationException("No Settings.asset.");
var planetSettings = new PlanetSettings
{
BodyRadius = new ExponentialCurve(),
WellStrength = new ExponentialCurve { Multiplier = 1f, Exponent = 1f },
RocheLimit = new ExponentialCurve { Constant = 2f },
AsteroidSize = new ExponentialLerp { Minimum = 2f, Maximum = 9f, Exponent = 1.7f },
AsteroidHitpoints = new ExponentialLerp { Minimum = 10f, Maximum = 300f, Exponent = 2f },
AsteroidRespawnTime = new ExponentialLerp { Minimum = 5f, Maximum = 60f, Exponent = 1f }
};
var cache = AetheriaStores.Open(Path.Combine(work, "c.cc"), Path.Combine(work, "r.cc"), catalogWritable: true);
cache.Upsert(new VerseGrammar { Revision = 1 });
var shape = new Shape(5, 4);
foreach (var cell in shape.AllCoordinates) shape[cell] = true;
cache.Upsert(new HullData { Name = "Smoke Hull", HullType = HullType.Ship, Shape = shape, Durability = 1000, Mass = 1000, Armor = 0 });
var sunOrbit = cache.Upsert(new OrbitData()).Key;
var sunKey = cache.Upsert(new PlanetData { Name = "Sun", Orbit = new CultRecordRef<OrbitData>(sunOrbit), Mass = 1e6f }).Key;
var rootOrbit = cache.Upsert(new OrbitData { Parent = new CultRecordRef<OrbitData>(sunOrbit), Distance = 4000f, Phase = .17f }).Key;
var beltOrbit = cache.Upsert(new OrbitData { Parent = new CultRecordRef<OrbitData>(rootOrbit), Distance = 900f, Phase = .42f }).Key;
var asteroids = Enumerable.Range(0, Asteroids).Select(i => new Asteroid
{ Distance = 120f + i * 11.3f, Phase = frac(i * .618f), Size = frac(i * .37f + .1f), RotationSpeed = .15f + i * .037f }).ToArray();
var beltKey = cache.Upsert(new AsteroidBeltData { Orbit = new CultRecordRef<OrbitData>(beltOrbit), Asteroids = asteroids }).Key;
var pack = new ZonePack
{
Orbits = { new CultRecordRef<OrbitData>(sunOrbit), new CultRecordRef<OrbitData>(rootOrbit), new CultRecordRef<OrbitData>(beltOrbit) },
Planets = { new CultRecordRef<BodyData>(sunKey), new CultRecordRef<BodyData>(beltKey) },
Radius = 5000f, Mass = 10000f, Time = 0
};
var ledger = new ProvenanceLedger();
for (var i = 1; i <= 8; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
var items = new ItemManager(1u, cache, ledger, settings.GameplaySettings, _ => { });
var zone = new Zone(items, planetSettings, pack, new GalaxyZone { Name = "Smoke", Owner = null }, null);
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Smoke Hull"));
var ship = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 1 }, new EntitySettings());
var other = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 2 }, new EntitySettings());
ship.Position = float3(300, 50, 200);
other.Position = float3(-300, -90, 100);
zone.Admit(ship, piloted: false);
zone.Admit(other, piloted: false);
// A hull heading 40 degrees from +z, pitched up 89.9 and rolled 30: its forward is almost vertical, where the pose's
// eulerAngles.y is the gimbal decomposition (yaw and roll folded together), not the heading. The heading is the
// forward projected onto the map plane, 40. The fixture asserts the two differ, so a yaw read from eulerAngles
// cannot pass.
var pose = Quaternion.Euler(0, 40, 0) * Quaternion.Euler(-89.9f, 0, 30);
var flatForward = pose * Vector3.forward;
Require(Mathf.Abs(Mathf.DeltaAngle(Mathf.Atan2(flatForward.x, flatForward.z) * Mathf.Rad2Deg, 40f)) < .5f, "The fixture's forward does not head 40.");
Require(Mathf.Abs(Mathf.DeltaAngle(pose.eulerAngles.y, 40f)) > 10f, $"The fixture's eulerAngles.y {pose.eulerAngles.y} is aligned with its heading.");
ship.Direction = float2(Mathf.Sin(40 * Mathf.Deg2Rad), Mathf.Cos(40 * Mathf.Deg2Rad));
typeof(Ship).GetProperty("Rotation", Any).GetSetMethod(true).Invoke(ship, new object[] { new CultMath.quaternion(pose.x, pose.y, pose.z, pose.w) });
var map = new MapPresenter(new Material[0], AssetDatabase.LoadAssetAtPath<Material>("Assets/Materials/UI/MapAsteroid.mat"), 4, 4, 3f);
var roots = new GameObject("Smoke Roots").transform;
EntityInstance Bound(Entity entity)
{
var body = new GameObject(entity.Name);
body.transform.SetParent(roots);
var instance = body.AddComponent<EntityInstance>();
Awaken(instance);
instance.HullColliders = new HullCollider[0];
instance.RadiatorHardpoints = new RadiatorHardpoint[0];
instance.ThrusterHardpoints = new ThrusterHardpoint[0];
instance.WeaponHardpoints = new WeaponHardpoint[0];
instance.ArticulationPoints = new ArticulationPoint[0];
instance.MapIcon = NewIcon(body.transform, flatIcons);
var zoneRenderer = new GameObject("Smoke Zone Renderer").AddComponent<ZoneRenderer>();
zoneRenderer.Settings = settings;
zoneRenderer.MapGravityMaterials = new Material[0];
Awaken(zoneRenderer);
typeof(ZoneRenderer).GetProperty("Map").GetSetMethod(true).Invoke(zoneRenderer, new object[] { map });
instance.SetEntity(zoneRenderer, entity);
return instance;
}
var shipInstance = Bound(ship);
var otherInstance = Bound(other);
var sunRoot = new GameObject("Smoke Sun").transform;
sunRoot.SetParent(roots);
map.RegisterPlanet(sunKey, NewIcon(sunRoot, flatIcons), sunRoot);
var belt = new float4[Asteroids];
map.RegisterBelt(beltKey, belt);
zone.EvaluateBelt(beltKey, belt, DrawAhead.Time(zone, 0));
map.SetFade(ship, 1f);
map.SetFade(other, 1f);
map.SetMinimapScale(2f);
map.Draw(zone, 0);
return new Fixture { Zone = zone, Map = map, Ship = ship, Other = other, ShipInstance = shipInstance,
OtherInstance = otherInstance, SunKey = sunKey, BeltKey = beltKey, Belt = belt, Roots = roots };
}
private static void Checks(Fixture f)
{
var (zone, map, ship, other, sunKey, beltKey, belt, roots) = (f.Zone, f.Map, f.Ship, f.Other, f.SunKey, f.BeltKey, f.Belt, f.Roots);
// Deep planet: the sun's field is deeper than 2000 below the plane, and its icon sits on the map plane inside both
// map cameras' clip range (minimap at y 10, map screen at y 1; near 0.3, far 1000, looking down).
var well = zone.GetHeight(DrawAhead.Orbit(zone, zone.PlanetInstances[sunKey].BodyData.Orbit.Key, 0), 0);
Require(well < -2000f, $"The fixture sun's well is {well}, not below -2000.");
Require(map.TryDrawn(sunKey, out var sunIcon), "The sun registered no icon.");
var y = sunIcon.GetPosition().y;
Require(y < 1f - .3f && y > 10f - 1000f && Mathf.Abs(y - MapPresenter.PlaneY) < .5f, $"The sun's icon is drawn at y {y}, not on the map plane.");
// Axes: each icon sits at its sim position's planar x and z (a ship at (300, 200), the other at (-300, 100), the sun at
// its orbit), never swapped, and never at the 3D height (the ships ride at y 50 and -90, the sun's well is below -2000).
var shipAt = DrawAhead.Position(ship, 0);
Require(Mathf.Abs(shipAt.x - 300f) < 1e-3f && Mathf.Abs(shipAt.z - 200f) < 1e-3f, $"The ship's sim position is ({shipAt.x}, {shipAt.z}), not (300, 200).");
var sunAt = DrawAhead.Orbit(zone, zone.PlanetInstances[sunKey].BodyData.Orbit.Key, 0);
Require(map.TryDrawn(ship, out var shipIcon, out _), "The ship registered no icon.");
Require(Mathf.Abs(shipIcon.GetPosition().x - 300f) < 1e-3f && Mathf.Abs(shipIcon.GetPosition().z - 200f) < 1e-3f,
$"The ship's icon is at ({shipIcon.GetPosition().x}, {shipIcon.GetPosition().z}), not its sim position (300, 200).");
Require(map.TryDrawn(other, out var otherIcon, out _), "The second ship registered no icon.");
Require(Mathf.Abs(otherIcon.GetPosition().x + 300f) < 1e-3f && Mathf.Abs(otherIcon.GetPosition().z - 100f) < 1e-3f,
$"The second ship's icon is at ({otherIcon.GetPosition().x}, {otherIcon.GetPosition().z}), not (-300, 100).");
Require(Mathf.Abs(sunIcon.GetPosition().x - sunAt.x) < 1e-3f && Mathf.Abs(sunIcon.GetPosition().z - sunAt.y) < 1e-3f,
$"The sun's icon is at ({sunIcon.GetPosition().x}, {sunIcon.GetPosition().z}), not its orbit ({sunAt.x}, {sunAt.y}).");
Require(Mathf.Abs(otherIcon.GetPosition().y - (MapPresenter.PlaneY + MapPresenter.ShipOffset)) < 1e-4f, "The second ship's icon took the 3D height.");
// Tilted hull: the icon turns by the heading projected onto the map plane (40) times its authored rotation (identity here), with no roll or pitch.
var want = Quaternion.Euler(0, 40, 0);
Require(Quaternion.Angle(shipIcon.rotation, want) < .1f, $"The icon is rotated {shipIcon.rotation.eulerAngles}, not yaw 40 alone.");
Require(Vector3.Angle(shipIcon.rotation * Vector3.up, Vector3.up) < .1f, "The icon's up axis tilted with the hull.");
Require(Mathf.Abs(shipIcon.GetPosition().y - (MapPresenter.PlaneY + MapPresenter.ShipOffset)) < 1e-4f, "The ship's icon left the map plane.");
// Every kind registers once: three icons drew, and no icon child remains under any registered transform.
Require(map.IconCount == 3 && map.IconDraws == 3, $"{map.IconCount} icons, {map.IconDraws} drawn; expected 3 and 3.");
Require(roots.GetComponentsInChildren<MeshRenderer>(true).Length == 0, "An icon child remains under an entity or planet transform.");
// A belt is one instanced draw of every asteroid, and its buffer holds this frame's floats.
Require(map.BeltDraws == 1 && map.LastBeltInstances == belt.Length, $"{map.BeltDraws} belt draws of {map.LastBeltInstances} instances; expected 1 of {belt.Length}.");
var read = new float4[belt.Length];
map.ReadBelt(beltKey, read);
for (var i = 0; i < belt.Length; i++)
Require(read[i].x == belt[i].x && read[i].y == belt[i].y && read[i].z == belt[i].z && read[i].w == belt[i].w, $"Belt buffer differs from the frame's floats at {i}.");
// Belts draw on the minimap with the map screen closed.
map.OpenMapScreen(false);
map.Draw(zone, 0);
Require(map.BeltDraws == 1, "A belt did not draw on the minimap with the map screen closed.");
// Fade: half faded reaches the icon's colour alpha on the next draw; faded out draws nothing.
map.SetFade(ship, .5f);
map.Draw(zone, 0);
Require(map.TryDrawn(ship, out _, out var alpha) && Mathf.Abs(alpha - .4f) < 1e-3f, $"Icon alpha {alpha}, expected 0.8 * 0.5.");
map.SetFade(ship, 0f);
map.Draw(zone, 0);
Require(map.IconDraws == 2, $"{map.IconDraws} icons drew with one fully faded out; expected 2.");
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 58982a1c397cc2f4d9706fd103af6654

View file

@ -1,93 +0,0 @@
using System.Buffers.Text;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using MessagePack;
using CultMath;
using UnityEngine;
using UnityEditor;
using Random = CultMath.Random;
public class NameTools : EditorWindow
{
public int NameGeneratorMinLength = 5;
public int NameGeneratorMaxLength = 10;
public int NameGeneratorOrder = 4;
private int minWordLength = 4;
private TextAsset nameFile;
private MarkovNameGenerator _nameGenerator;
private bool _stripNumberTokens;
// Add menu named "My Window" to the Window menu
[MenuItem("Window/Aetheria/Name Tools")]
static void Init()
{
// Get existing open window or if none, make a new one:
NameTools window = (NameTools)EditorWindow.GetWindow(typeof(NameTools));
window.Show();
}
bool HasNonASCIIChars(string str)
{
return (System.Text.Encoding.UTF8.GetByteCount(str) != str.Length);
}
void OnGUI()
{
nameFile = (TextAsset) EditorGUILayout.ObjectField("Name File", nameFile, typeof(TextAsset), false);
minWordLength = EditorGUILayout.IntField("Minimum File Word Length", minWordLength);
NameGeneratorMinLength = EditorGUILayout.IntField("Generated Minimum Word Length", NameGeneratorMinLength);
NameGeneratorMaxLength = EditorGUILayout.IntField("Generated Maximum Word Length", NameGeneratorMaxLength);
NameGeneratorOrder = EditorGUILayout.IntField("Generator Order", NameGeneratorOrder);
_stripNumberTokens = GUILayout.Toggle(_stripNumberTokens, "Strip Number Tokens");
if (GUILayout.Button("Clean Name File"))
{
var lines = nameFile.text.Split('\n');
using StreamWriter outputFile = new StreamWriter(Path.Combine(Application.dataPath, nameFile.name + ".csv"));
var names = new HashSet<string>();
foreach (var line in lines)
{
var tokens = line.Split(',', ' ');
foreach (var t in tokens)
{
if (!HasNonASCIIChars(t))
{
var s = new string(t.Where(c => char.IsLetter(c) || c == '-' || c == '`' || c == '\'').ToArray()).Trim().Trim('`','-');
if(s.Length >= minWordLength && !names.Contains(s))
{
names.Add(s);
outputFile.WriteLine(s);
}
}
}
}
}
if (GUILayout.Button("Process Name File") && nameFile != null)
{
var names = new HashSet<string>();
var lines = nameFile.text.Split('\n');
foreach (var line in lines)
{
foreach(var word in line.ToUpperInvariant().Split(' ', ',', '.', '"'))
if (word.Length >= minWordLength && !names.Contains(word))
names.Add(word);
}
Debug.Log($"Found {lines.Length} lines, with {names.Count} unique names!");
var random = new Random(1337);
_nameGenerator = new MarkovNameGenerator(ref random, names, NameGeneratorOrder, NameGeneratorMinLength, NameGeneratorMaxLength);
}
if (_nameGenerator != null)
{
if (GUILayout.Button("Generate Name"))
{
Debug.Log(_nameGenerator.NextName);
}
}
}
}

View file

@ -0,0 +1,112 @@
using System;
using System.Linq;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEditor.SceneManagement;
using UnityEngine;
// The radius of each rock mesh ZoneRenderer draws (ruling rock-meshes-by-size): the farthest vertex from the mesh origin, which bounds the
// mesh's top under any rotation. DrawAhead.RockScale divides the gameplay radius by it, so the drawn rock is the gameplay rock. It is stored on
// InstancedMesh.Radius in ARPG.unity, because a build cannot read vertices of a mesh without Read/Write import.
//
// Unity -batchmode -nographics -projectPath <project> -executeMethod Aetheria.EditorTools.RockMeshRadius.Fill -logFile <log>
// Unity -batchmode -nographics -projectPath <project> -executeMethod Aetheria.EditorTools.RockMeshRadius.Verify -logFile <log>
//
// Fill measures every AsteroidMeshes entry and saves the scene; Verify fails (exit 1) when any stored radius is not the measured one.
// RockMeshRadiusBuildGate runs the same check before every player build and fails the build, so a mesh with no filled radius cannot ship;
// Gate runs that gate in batch mode.
// Unity -batchmode -nographics -projectPath <project> -executeMethod Aetheria.EditorTools.RockMeshRadius.Gate -logFile <log>
namespace Aetheria.EditorTools
{
public static class RockMeshRadius
{
private const string Scene = "Assets/Scenes/ARPG.unity";
private const float Tolerance = 1e-4f;
public static float Measure(Mesh mesh) => Mathf.Sqrt(mesh.vertices.Max(v => v.sqrMagnitude));
public static void Fill() => Batch(() =>
{
var scene = EditorSceneManager.OpenScene(Scene, OpenSceneMode.Single);
var (renderer, meshes, measured) = Load(scene);
for (var i = 0; i < meshes.Length; i++) meshes[i].Radius = measured[i];
EditorUtility.SetDirty(renderer);
EditorSceneManager.MarkSceneDirty(scene);
if (!EditorSceneManager.SaveScene(scene)) throw new InvalidOperationException("The scene did not save.");
return Summary("filled", measured);
});
public static void Verify() => Batch(() => Check(OpenSceneMode.Single));
// The build gate's own entry in batch mode: exit 1 exactly when a build would fail (RockMeshRadiusBuildGate).
public static void Gate() => Batch(() =>
{
try { new RockMeshRadiusBuildGate().OnPreprocessBuild(null); }
catch (BuildFailedException failure) { throw new InvalidOperationException(failure.Message); }
return "the build gate passes";
});
// Throws unless every AsteroidMeshes entry of ARPG.unity has a stored radius equal to its mesh's measured one (a zero radius never does).
public static string Check(OpenSceneMode mode)
{
var scene = EditorSceneManager.OpenScene(Scene, mode);
try
{
var (_, meshes, measured) = Load(scene);
var failures = meshes.Select((m, i) => (m, i)).Where(p => !(measured[p.i] > 0f) || Mathf.Abs(p.m.Radius - measured[p.i]) > Tolerance).ToArray();
if (failures.Length > 0)
throw new InvalidOperationException("InstancedMesh.Radius differs from the measured one, or is unset, on mesh (run RockMeshRadius.Fill): " + string.Join(", ",
failures.Select(f => f.m.Mesh.name)));
return Summary("ok", measured);
}
finally
{
if (mode == OpenSceneMode.Additive) EditorSceneManager.CloseScene(scene, true);
}
}
private static (ZoneRenderer renderer, InstancedMesh[] meshes, float[] measured) Load(UnityEngine.SceneManagement.Scene scene)
{
var renderer = UnityEngine.Object.FindObjectsByType<ZoneRenderer>(FindObjectsInactive.Include, FindObjectsSortMode.None)
.Single(r => r.gameObject.scene == scene);
var meshes = renderer.AsteroidMeshes;
if (meshes == null || meshes.Length == 0) throw new InvalidOperationException("ARPG.unity's ZoneRenderer has no AsteroidMeshes.");
var bad = meshes.Where(m => m.Mesh == null).ToArray();
if (bad.Length > 0) throw new InvalidOperationException($"{bad.Length} AsteroidMeshes entries have no mesh.");
return (renderer, meshes, meshes.Select(m => Measure(m.Mesh)).ToArray());
}
private static string Summary(string what, float[] measured)
{
var sorted = measured.OrderBy(r => r).ToArray();
return $"{what}: {sorted.Length} meshes, radius min {sorted[0]:F3} median {sorted[sorted.Length / 2]:F3} max {sorted[^1]:F3}";
}
private static void Batch(Func<string> run)
{
try
{
Debug.Log($"[RockMeshRadius] {run()}");
EditorApplication.Exit(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
}
// A mesh in AsteroidMeshes with no filled radius must not ship: the build fails, naming the field.
public class RockMeshRadiusBuildGate : IPreprocessBuildWithReport
{
public int callbackOrder => 0;
public void OnPreprocessBuild(BuildReport report)
{
try { RockMeshRadius.Check(OpenSceneMode.Additive); }
catch (InvalidOperationException failure) { throw new BuildFailedException(failure.Message); }
}
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9ea93f3d164825c4b99a0d07f24d5cf6

View file

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.UI;
// A render of the New Game screen as the game builds it: the real Main Menu scene's panel prototype, docked and filled
// by ScenarioPicker.Dock and ScenarioPicker.Show with every scenario group, on a plain dark background (the cloud
// backdrop is a play-mode renderer). Writes <project>/Temp/scenario-picker.png at 1920x1080, or the path after -pngOut (Unity empties Temp on exit).
//
// Unity -batchmode -projectPath <project> -executeMethod ScenarioPickerCapture.Run [-pngOut <file>] -logFile <log>
//
// Needs a GPU device; do not pass -nographics or -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
public static class ScenarioPickerCapture
{
private const int Width = 1920, Height = 1080;
public static void Run()
{
try
{
Capture();
EditorApplication.Exit(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
// The menu's two panes built the way MainMenu.Start builds them, against the Main Menu scene.
internal static (MainMenu menu, PropertiesPanel list, PropertiesPanel detail) Build()
{
EditorSceneManager.OpenScene("Assets/Scenes/Main Menu.unity", OpenSceneMode.Single);
var menu = UnityEngine.Object.FindObjectsByType<MainMenu>(FindObjectsInactive.Include, FindObjectsSortMode.None)[0];
menu.PanelPrototype.gameObject.SetActive(false);
var list = menu.PanelPrototype.Instantiate<PropertiesPanel>();
var detail = menu.PanelPrototype.Instantiate<PropertiesPanel>();
// Edit mode runs no Awake, which is what activates each row a panel adds.
list.Awake();
detail.Awake();
ScenarioPicker.Dock(detail, list);
return (menu, list, detail);
}
internal static List<(string label, Scenario[] scenarios)> AllGroups() => new List<(string label, Scenario[] scenarios)>
{
("modes", Scenarios.Modes), ("development", Scenarios.Development), ("test arenas", Scenarios.Tests)
};
private static void Capture()
{
var (menu, list, detail) = Build();
menu.Dialog.gameObject.SetActive(false);
list.Title.text = MainMenu.TitleSubtitle("new game", "scenarios");
ScenarioPicker.Show(list, detail, AllGroups(), _ => { }, () => { }, menu.Dialog.Confirm);
list.RefreshValues();
detail.RefreshValues();
var canvas = menu.GetComponentInParent<Canvas>(true).rootCanvas;
var target = new RenderTexture(Width, Height, 24, RenderTextureFormat.ARGB32);
var cameraObject = new GameObject("ScenarioPickerCapture camera");
var camera = cameraObject.AddComponent<Camera>();
camera.orthographic = true;
camera.orthographicSize = 5;
camera.clearFlags = CameraClearFlags.SolidColor;
camera.backgroundColor = new Color(.04f, .05f, .08f);
camera.cullingMask = 1 << LayerMask.NameToLayer("UI");
camera.targetTexture = target;
canvas.renderMode = RenderMode.ScreenSpaceCamera;
canvas.worldCamera = camera;
canvas.planeDistance = 10;
canvas.gameObject.SetActive(true);
Canvas.ForceUpdateCanvases();
LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform) list.transform);
LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform) detail.transform);
foreach (var text in list.transform.GetComponentsInChildren<TMPro.TMP_Text>())
text.ForceMeshUpdate();
Canvas.ForceUpdateCanvases();
list.Content.GetComponentInParent<ScrollRect>().onValueChanged.Invoke(Vector2.zero);
camera.Render();
var previous = RenderTexture.active;
RenderTexture.active = target;
var read = new Texture2D(Width, Height, TextureFormat.RGBA32, false);
read.ReadPixels(new Rect(0, 0, Width, Height), 0, 0);
RenderTexture.active = previous;
var background = read.GetPixel(0, 0);
var differing = 0;
foreach (var pixel in read.GetPixels32())
if (Mathf.Abs(pixel.r / 255f - background.r) + Mathf.Abs(pixel.g / 255f - background.g) + Mathf.Abs(pixel.b / 255f - background.b) > .05f)
differing++;
if (differing < Width * Height / 100)
throw new InvalidOperationException($"The capture is uniform: only {differing} pixels differ from the background.");
var path = Path.GetFullPath(Path.Combine(Application.dataPath, "..", "Temp", "scenario-picker.png"));
var args = Environment.GetCommandLineArgs();
var outIndex = Array.IndexOf(args, "-pngOut");
if (outIndex >= 0) path = Path.GetFullPath(args[outIndex + 1]);
Directory.CreateDirectory(Path.GetDirectoryName(path));
File.WriteAllBytes(path, read.EncodeToPNG());
Debug.Log($"[ScenarioPickerCapture] {path} ({differing} of {Width * Height} pixels differ from the background)");
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 2e79f280179150e4080250c45e07cd69

View file

@ -0,0 +1,265 @@
using System;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEditor;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
// The picker's selection and keyboard rules, driven through the events the game delivers: Move and Cancel reach the
// focused Launch button through ExecuteEvents, as the Input System UI module sends them, and a click is Button.onClick.
//
// Unity -batchmode -projectPath <project> -executeMethod ScenarioPickerVerify.Run -logFile <log>
//
// Exits the Editor itself, 0 when every rule holds and 1 on the first that does not.
public static class ScenarioPickerVerify
{
private sealed class Fake : Scenario
{
private readonly uint _seed;
private readonly bool _ambient;
public Fake(string name, uint seed = 0, bool ambient = true) { Name = name; _seed = seed; _ambient = ambient; }
public override string Name { get; }
public override string Id => Name.ToLowerInvariant();
public override string Brief => $"brief of {Name}";
public override uint Seed => _seed;
public override bool Ambient => _ambient;
public override Galaxy Generate(GalaxyStage stage) => throw new NotSupportedException();
public override void Stage(ScenarioStage stage) => throw new NotSupportedException();
}
private sealed class Screen
{
public ScenarioPicker Picker;
public PropertiesPanel List, Detail;
public Button[] Rows;
public Button Back, Launch;
public readonly List<Scenario> Launched = new List<Scenario>();
public int Backs;
public Scenario[] A, B, C;
}
public static void Run()
{
try
{
SelectionFollowsClicksAndArrows();
ArrowsStopAtTheEnds();
LeftAndRightAreIgnored();
LaunchIsOnePathAndFocusClears();
CancelAndBackAreOnePath();
DetailPaneFollowsSelection();
OneGroupHasNoSectionLabel();
LaunchesOnceHoweverItIsReached();
ArrowingThroughTheRealListKeepsTheSelectionVisible();
Debug.Log("[ScenarioPickerVerify] ok");
EditorApplication.Exit(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
private static void Require(bool condition, string what)
{
if (!condition) throw new InvalidOperationException(what);
}
private static Screen Build(bool oneGroup = false, bool real = false)
{
var (menu, list, detail) = ScenarioPickerCapture.Build();
// Build opens the scene afresh, which destroys the previous EventSystem.
var events = new GameObject("verify EventSystem").AddComponent<EventSystem>();
// Edit mode never calls OnEnable, which is what registers an EventSystem as current.
typeof(EventSystem).GetMethod("OnEnable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).Invoke(events, null);
var screen = new Screen
{
List = list, Detail = detail,
A = new Scenario[] { new Fake("Alpha", 7), new Fake("Beta") },
B = new Scenario[] { new Fake("Gamma", ambient: false) },
C = new Scenario[] { new Fake("Delta"), new Fake("Echo") }
};
var groups = real ? ScenarioPickerCapture.AllGroups() : oneGroup
? new List<(string label, Scenario[] scenarios)> { ("modes", screen.A) }
: new List<(string label, Scenario[] scenarios)> { ("modes", screen.A), ("development", screen.B), ("test arenas", screen.C) };
// The menu builds a panel while it is inactive and shows it afterwards; the picker must find its scroll all the same.
if (real) list.gameObject.SetActive(false);
screen.Picker = ScenarioPicker.Show(list, detail, groups, screen.Launched.Add, () => screen.Backs++, menu.Dialog.Confirm);
var buttons = list.GetComponentsInChildren<PropertyButton>(false).Where(button => !button.transform.IsChildOf(detail.transform)).Select(button => button.Button).ToArray();
screen.Rows = buttons.Take(buttons.Length - 1).ToArray();
screen.Back = buttons.Last();
screen.Launch = detail.GetComponentsInChildren<Button>(false).Single();
if (real) { list.gameObject.SetActive(true); Layout(menu, list, detail); }
return screen;
}
// A live 1920x1080 layout, as the game has one a frame after Show; edit mode has no frames, so it is forced here.
private static void Layout(MainMenu menu, PropertiesPanel list, PropertiesPanel detail)
{
menu.Dialog.gameObject.SetActive(false);
var canvas = menu.GetComponentInParent<Canvas>(true).rootCanvas;
var camera = new GameObject("verify camera").AddComponent<Camera>();
camera.targetTexture = new RenderTexture(1920, 1080, 24);
canvas.renderMode = RenderMode.ScreenSpaceCamera;
canvas.worldCamera = camera;
canvas.planeDistance = 10;
canvas.gameObject.SetActive(true);
Canvas.ForceUpdateCanvases();
LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform) canvas.transform);
foreach (var text in list.GetComponentsInChildren<TMP_Text>()) text.ForceMeshUpdate();
Canvas.ForceUpdateCanvases();
Scroll(list).onValueChanged.Invoke(Vector2.zero);
}
private static ScrollRect Scroll(PropertiesPanel list) => list.Content.GetComponentInParent<ScrollRect>();
// Wholly inside the viewport, in world space, and not hidden by FitViewport's CanvasGroup.
private static bool Shown(Button row, ScrollRect scroll)
{
var view = new Vector3[4];
((RectTransform) scroll.viewport).GetWorldCorners(view);
var rect = new Vector3[4];
((RectTransform) row.transform).GetWorldCorners(rect);
var alpha = 1f;
for (var t = row.transform; t != null && t != scroll.content; t = t.parent)
if (t.TryGetComponent<CanvasGroup>(out var group)) alpha = Mathf.Min(alpha, group.alpha);
return rect[0].y >= view[0].y - .01f && rect[1].y <= view[1].y + .01f && alpha == 1;
}
private static void Move(Screen screen, MoveDirection direction) =>
ExecuteEvents.Execute(screen.Launch.gameObject, new AxisEventData(EventSystem.current) { moveDir = direction }, ExecuteEvents.moveHandler);
private static void Cancel(Screen screen) =>
ExecuteEvents.Execute(screen.Launch.gameObject, new BaseEventData(EventSystem.current), ExecuteEvents.cancelHandler);
private static string Selected(Screen screen) => screen.Picker.Selected.Name;
private static void SelectionFollowsClicksAndArrows()
{
var screen = Build();
Require(screen.Rows.Length == 5, $"Expected 5 scenario rows, found {screen.Rows.Length}.");
Require(Selected(screen) == "Alpha", "The first scenario is not selected on Show.");
Require(EventSystem.current.currentSelectedGameObject == screen.Launch.gameObject, "Launch is not focused on Show.");
Move(screen, MoveDirection.Down);
Require(Selected(screen) == "Beta", "Down did not select the next row.");
Move(screen, MoveDirection.Down);
Require(Selected(screen) == "Gamma", "Down did not cross into the next group.");
Move(screen, MoveDirection.Up);
Require(Selected(screen) == "Beta", "Up did not select the previous row.");
screen.Rows[3].onClick.Invoke();
Require(Selected(screen) == "Delta", "Clicking a name did not select it.");
Require(screen.Launched.Count == 0, "Clicking a name launched it.");
Require(EventSystem.current.currentSelectedGameObject == screen.Launch.gameObject, "A click on a name took focus off Launch.");
Require(screen.Rows.All(row => row.navigation.mode == Navigation.Mode.None) && screen.Launch.navigation.mode == Navigation.Mode.None
&& screen.Back.navigation.mode == Navigation.Mode.None, "A picker button still takes Unity navigation.");
var accent = screen.Rows[3].targetGraphic.color;
Require(screen.Rows.Where((row, i) => i != 3).All(row => row.targetGraphic.color != accent), "Only the selected row is drawn in the accent.");
Move(screen, MoveDirection.Up);
Require(screen.Rows[2].targetGraphic.color == accent && screen.Rows[3].targetGraphic.color != accent, "The accent did not follow the selection.");
}
private static void ArrowsStopAtTheEnds()
{
var screen = Build();
Move(screen, MoveDirection.Up);
Require(Selected(screen) == "Alpha", "Up wrapped off the first row.");
for (var i = 0; i < 10; i++) Move(screen, MoveDirection.Down);
Require(Selected(screen) == "Echo", "Down wrapped or stopped short of the last row.");
}
private static void LeftAndRightAreIgnored()
{
var screen = Build();
Move(screen, MoveDirection.Down);
Move(screen, MoveDirection.Left);
Move(screen, MoveDirection.Right);
Require(Selected(screen) == "Beta", "Left or Right moved the selection.");
}
private static void LaunchIsOnePathAndFocusClears()
{
var screen = Build();
Move(screen, MoveDirection.Down);
screen.Launch.onClick.Invoke();
Require(screen.Launched.Count == 1 && screen.Launched[0] == screen.A[1], "Launch did not launch the selected scenario once.");
Require(EventSystem.current.currentSelectedGameObject == null, "Focus survived Launch, so a second Enter could launch again.");
Require(screen.Backs == 0, "Launch went back.");
}
private static void CancelAndBackAreOnePath()
{
var screen = Build();
Cancel(screen);
Require(screen.Backs == 1 && screen.Launched.Count == 0, "Cancel did not go back exactly once.");
Require(EventSystem.current.currentSelectedGameObject == null, "Focus survived Cancel.");
screen = Build();
screen.Back.onClick.Invoke();
Require(screen.Backs == 1 && screen.Launched.Count == 0, "The Back button did not go back exactly once.");
Require(EventSystem.current.currentSelectedGameObject == null, "Focus survived Back.");
}
private static string DetailText(Screen screen)
{
screen.Detail.RefreshValues();
return string.Join("|", screen.Detail.GetComponentsInChildren<TextMeshProUGUI>(false).Select(text => text.text));
}
private static void DetailPaneFollowsSelection()
{
var screen = Build();
var first = DetailText(screen);
Require(first.Contains("brief of Alpha") && first.Contains("7") && first.Contains("on"), $"Detail does not describe Alpha: {first}");
Require(first.Contains("modes") && first.Contains("Alpha"), "Detail heading is not the group and name.");
Move(screen, MoveDirection.Down);
Move(screen, MoveDirection.Down);
var third = DetailText(screen);
Require(third.Contains("brief of Gamma") && third.Contains("new each launch") && third.Contains("off"), $"Detail does not describe Gamma: {third}");
Require(third.Contains("development") && !third.Contains("Alpha"), "Detail heading did not follow the selection.");
Require(screen.Detail.GetComponentsInChildren<Button>(false).Single() == screen.Launch, "Selecting rebuilt the Launch button.");
}
private static void OneGroupHasNoSectionLabel()
{
var many = Build();
var one = Build(oneGroup: true);
Require(many.List.Children == 5 + 3 + 2 + 1, $"Three groups should list 5 rows, 3 section labels, 2 spacers and Back, not {many.List.Children} children.");
Require(one.List.Children == 2 + 1, $"One group should list its 2 rows and Back, not {one.List.Children} children.");
}
private static void LaunchesOnceHoweverItIsReached()
{
var screen = Build();
screen.Launch.onClick.Invoke();
screen.Launch.onClick.Invoke();
ExecuteEvents.Execute(screen.Launch.gameObject, new BaseEventData(EventSystem.current), ExecuteEvents.submitHandler);
Require(screen.Launched.Count == 1, $"Launch ran {screen.Launched.Count} times after a click, a second click and Submit.");
Require(screen.Launch.navigation.mode == Navigation.Mode.None && Scroll(screen.List).verticalScrollbar.navigation.mode == Navigation.Mode.None,
"The list scrollbar or Launch takes Unity navigation.");
}
// The real scenario groups overflow the list's viewport; every row the arrows reach must end wholly visible.
private static void ArrowingThroughTheRealListKeepsTheSelectionVisible()
{
var screen = Build(real: true);
var scroll = Scroll(screen.List);
var content = (RectTransform) scroll.content;
var viewport = (RectTransform) scroll.viewport;
Require(content.rect.height > viewport.rect.height + 1, $"The real list ({content.rect.height}) does not overflow its viewport ({viewport.rect.height}); the scroll rules are unmeasured.");
Require(Shown(screen.Rows[0], scroll), "The first row is not shown on Show.");
for (var i = 1; i < screen.Rows.Length; i++)
{
Move(screen, MoveDirection.Down);
Canvas.ForceUpdateCanvases();
Require(Shown(screen.Rows[i], scroll), $"Down to row {i} ({Selected(screen)}) left it hidden or cut off.");
}
for (var i = screen.Rows.Length - 2; i >= 0; i--)
{
Move(screen, MoveDirection.Up);
Canvas.ForceUpdateCanvases();
Require(Shown(screen.Rows[i], scroll), $"Up to row {i} ({Selected(screen)}) left it hidden or cut off.");
}
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7ca0b5db32f745a43a8fdc67dfac2996

View file

@ -10,9 +10,10 @@ using UnityEngine;
// Body() calls for a hull with a Visual. A plain parent transform stands in for ZoneRoot; nothing else in LoadEntity
// touches the body before it takes its ShipInstance.
//
// The package is installed beside a broken sibling: the same ship under another id, with its map-icon anchor moved onto
// its first muzzle's meshless node. The sibling composes, because no compose-time check reads meshes, and then fails
// assembly. The smoke proves the preload skips it and the good package still spawns.
// The package is installed beside two siblings. The .broken one has its map-icon anchor moved onto a meshless node:
// Compose excludes it, naming the anchor. The .unimportable one has its GLB's BIN chunk dropped: the headless reader
// accepts it, so it composes, and the importer cannot build it, so Preload faults Loading with ImportFault naming it.
// RemovePackage then moves it aside and the next Compose leaves it out.
//
// Unity -batchmode -nographics -projectPath <project> -executeMethod ShipModPlaySmoke.Run
// -shipModPath <mods>/<id>/ship.cc [-shippedCatalog GameData/Aetheria.cc] -logFile <log>
@ -37,12 +38,20 @@ public static class ShipModPlaySmoke
{
if (packagePath == null) throw new InvalidOperationException("-shipModPath <package/ship.cc> is required");
var package = ShipModCatalog.ReadPackage(packagePath);
var broken = InstallWithBrokenSibling(packagePath, package, modsRoot);
var composition = ShipModCatalog.Compose(Path.GetFullPath(Option("-shippedCatalog", "GameData/Aetheria.cc")), derived, modsRoot);
foreach (var id in new[] { package.Visual.Id, broken.Visual.Id })
if (!composition.Included.Contains(id))
throw new InvalidOperationException($"{id} was not composed into the catalog: " +
string.Join("; ", composition.Excluded.Select(excluded => $"{excluded.Package}: {excluded.Reason}")));
Install(package.Visual.Id, package, modsRoot, Path.GetFullPath(packagePath));
var broken = InstallSibling(package, modsRoot, ".broken", ship =>
{
var mapIcon = ship.Anchors.Single(anchor => anchor.Role == "map-icon");
var muzzle = ship.Anchors.First(anchor => anchor.Role == "weapon-muzzle");
(mapIcon.ModelNodeId, muzzle.ModelNodeId) = (muzzle.ModelNodeId, mapIcon.ModelNodeId);
}, null);
var shipped = Path.GetFullPath(Option("-shippedCatalog", "GameData/Aetheria.cc"));
var composition = ShipModCatalog.Compose(shipped, derived, modsRoot);
if (!composition.Included.Contains(package.Visual.Id))
throw new InvalidOperationException($"{package.Visual.Id} was not composed into the catalog: " + Reasons(composition));
var excluded = composition.Excluded.SingleOrDefault(entry => entry.Package == broken.Visual.Id);
if (composition.Included.Contains(broken.Visual.Id) || excluded == null || !excluded.Reason.Contains("map-icon anchor"))
throw new InvalidOperationException($"{broken.Visual.Id} was not excluded for its map-icon anchor: " + Reasons(composition));
using (var catalog = AetheriaStores.Open(derived))
{
@ -50,12 +59,6 @@ public static class ShipModPlaySmoke
HullData Hull(string name) => catalog.GetAll<HullData>().Single(candidate => candidate.Visual.IsSet() && candidate.Name == name);
var hull = Hull(package.Hull.Name);
// The broken sibling has no prototype, and Loading did not fault for it (await would have thrown).
var refused = false;
try { ShipModShips.Instantiate(Hull(broken.Hull.Name), null); }
catch (InvalidOperationException error) { refused = error.Message.Contains("no mod ship prototype"); }
if (!refused) throw new InvalidOperationException($"{broken.Visual.Id} assembled, or was refused for the wrong reason.");
zone = new GameObject("Play Smoke Zone");
var ship = ShipModShips.Instantiate(hull, zone.transform).GetComponent<ShipInstance>();
if (ship == null) throw new InvalidOperationException("The mod ship body has no ShipInstance.");
@ -71,7 +74,33 @@ public static class ShipModPlaySmoke
throw new InvalidOperationException("The hull collider is not a MeshCollider over a readable mesh.");
if (ship.MapIcon == null || ship.MapIcon.sharedMaterial == null || ship.MapIcon.gameObject.layer != LayerMask.NameToLayer("Minimap"))
throw new InvalidOperationException("The map icon is missing its renderer, material or Minimap layer.");
Debug.Log($"SHIP_MOD_PLAY_SMOKE thrusters={thrusters} weapons={weapons} collider=ok mapicon=ok skipped={broken.Visual.Id}");
// Phase 2: an unimportable sibling composes, then faults the preload with ImportFault naming exactly it.
var unimportable = InstallSibling(package, modsRoot, ".unimportable", null, DropBinChunk);
var derivedTwo = Path.Combine(work, "Derived2", "Aetheria.modded.cc");
var second = ShipModCatalog.Compose(shipped, derivedTwo, modsRoot);
if (!second.Included.Contains(unimportable.Visual.Id))
throw new InvalidOperationException($"{unimportable.Visual.Id} was not composed: " + Reasons(second));
ShipModShips.ImportFault fault = null;
using (var catalogTwo = AetheriaStores.Open(derivedTwo))
{
try { await ShipModShips.Preload(catalogTwo, modsRoot); }
catch (ShipModShips.ImportFault caught) { fault = caught; }
}
if (fault == null || fault.Faults.Length != 1 || fault.Faults[0].Package != unimportable.Visual.Id ||
!fault.Faults[0].Reason.StartsWith(unimportable.Hull.Name))
throw new InvalidOperationException("Loading did not fault with ImportFault naming exactly " + unimportable.Visual.Id + ".");
// Phase 3: removal moves it aside, and the next compose leaves it out.
var destination = ShipModCatalog.RemovePackage(modsRoot, unimportable.Visual.Id);
if (Directory.Exists(Path.Combine(modsRoot, unimportable.Visual.Id)) ||
destination != Path.Combine(work, "RemovedMods", unimportable.Visual.Id) || !File.Exists(Path.Combine(destination, "ship.cc")))
throw new InvalidOperationException("RemovePackage did not move the package to RemovedMods.");
var third = ShipModCatalog.Compose(shipped, Path.Combine(work, "Derived3", "Aetheria.modded.cc"), modsRoot);
if (third.Included.Contains(unimportable.Visual.Id) || !third.Included.Contains(package.Visual.Id))
throw new InvalidOperationException("The compose after removal still includes the removed package, or lost the good one.");
Debug.Log($"SHIP_MOD_PLAY_SMOKE thrusters={thrusters} weapons={weapons} collider=ok mapicon=ok excluded={broken.Visual.Id} " +
$"faulted={unimportable.Visual.Id} removed={Path.GetFileName(destination)}");
}
Clean(zone, work);
EditorApplication.Exit(0);
@ -84,26 +113,29 @@ public static class ShipModPlaySmoke
}
}
// Copies the package into modsRoot and writes its broken sibling beside it. Returns the sibling's records.
private static (HullData Hull, ShipAuthoring Visual) InstallWithBrokenSibling(string packagePath, ShipModCatalog.Package package, string modsRoot)
{
void Install(string id, string shipFile)
{
var model = Path.Combine(modsRoot, id, package.Visual.ModelAsset);
Directory.CreateDirectory(Path.GetDirectoryName(model));
File.Copy(package.ModelPath, model);
if (shipFile != null) File.Copy(shipFile, Path.Combine(modsRoot, id, "ship.cc"));
}
Install(package.Visual.Id, Path.GetFullPath(packagePath));
private static string Reasons(ShipModCatalog.Composition composition) =>
string.Join("; ", composition.Excluded.Select(excluded => $"{excluded.Package}: {excluded.Reason}"));
private static void Install(string id, ShipModCatalog.Package package, string modsRoot, string shipFile)
{
var model = Path.Combine(modsRoot, id, package.Visual.ModelAsset);
Directory.CreateDirectory(Path.GetDirectoryName(model));
File.Copy(package.ModelPath, model);
if (shipFile != null) File.Copy(shipFile, Path.Combine(modsRoot, id, "ship.cc"));
}
// The package's ship under the id plus suffix, its records changed by tweak and its GLB rewritten in place by glb.
// Returns the sibling's records.
private static (HullData Hull, ShipAuthoring Visual) InstallSibling(ShipModCatalog.Package package, string modsRoot, string suffix,
Action<ShipAuthoring> tweak, Action<string> glb)
{
var (hull, ship, _) = ShipAuthoringStore.Read(Path.Combine(modsRoot, package.Visual.Id, "ship.cc"));
ship.Id += ".broken";
hull.Name += " Broken";
ship.Id += suffix;
hull.Name += suffix;
hull.Visual = new CultRecordRef<ShipAuthoring>(ShipModCatalog.AuthoringKey(ship.Id));
var mapIcon = ship.Anchors.Single(anchor => anchor.Role == "map-icon");
var muzzle = ship.Anchors.First(anchor => anchor.Role == "weapon-muzzle");
(mapIcon.ModelNodeId, muzzle.ModelNodeId) = (muzzle.ModelNodeId, mapIcon.ModelNodeId);
Install(ship.Id, null);
tweak?.Invoke(ship);
Install(ship.Id, package, modsRoot, null);
glb?.Invoke(Path.Combine(modsRoot, ship.Id, package.Visual.ModelAsset));
using (var cache = ShipAuthoringStore.Open(Path.Combine(modsRoot, ship.Id, "ship.cc"), writable: true))
{
ShipAuthoringStore.Write(cache, hull, ship);
@ -112,6 +144,17 @@ public static class ShipModPlaySmoke
return (hull, ship);
}
// Keeps the 12-byte GLB header and the JSON chunk, drops the BIN chunk and rewrites the header's total length: the
// headless reader needs only the JSON, the importer needs the buffer.
private static void DropBinChunk(string path)
{
var bytes = File.ReadAllBytes(path);
var end = 20 + (int)BitConverter.ToUInt32(bytes, 12);
var kept = bytes.Take(end).ToArray();
BitConverter.GetBytes((uint)kept.Length).CopyTo(kept, 8);
File.WriteAllBytes(path, kept);
}
private static void Clean(GameObject zone, string work)
{
if (zone != null) UnityEngine.Object.DestroyImmediate(zone);

Some files were not shown because too many files have changed in this diff Show more