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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
ActivatedBehavior replaces IActivatedBehavior so Activate/Deactivate can be internal under Unity's C# 9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
_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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
Output of `dotnet run --project tools/AetherDb -- sensor-stat-set apply` on origin/master be01ee06's catalog
(sha256 9ee82a86...; result sha256 00c59a641efc5f3cce3504bf604a9dd25781c06494b141a47cc9aaf56727525d). A second apply
changed nothing. The dry run printed:
Census: 2 sensor designs, 0 listener designs.
Compact Sensor [Sensors] roles [emitter, receiver]
not if i see you first by DME: emitter 0.71, receiver 0.75
Sensor Array [Sensors] roles [emitter, receiver]
The Bat by Miss Terri's: emitter 0.45, receiver 0.57
New design: Passive Array [Tool] 1x1, EnergyDraw 10/s, Multiple 2-3 reading receiver; products Murmur by Finch, Wallflower by NiteLife
Intercept factor: 2 designs and 2 products to change, 4 sensor products to add; Passive Array to author.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
master's NoDesignTextNamesAProduct fails on the first name, Feather, because the Racing Drive's
functional text begins "Feather-light". The product is renamed; its design text is generic-designs' and untouched.
Builds. The catalog does not yet carry the command's output (next commit).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: EvasionTermTests keeps the branch's EvLonginusDrive split with master's generic
design names; AetherDb Program.cs keeps both the sensor-stat-set and generic-designs cases and
lists both in the usage line. GameData/Aetheria.cc is taken from origin/master unchanged;
sensor-stat-set apply is rerun on it in the next commit. Tests that read the sensor catalog
(TheCatalogCarriesSensorStats and the roles checks) do not pass at this commit: it builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
Output of `dotnet run --project tools/AetherDb -- generic-designs apply` on f1642a12 (sha256 of the result
9ee82a860bc41fed142226a6bcc5ee8cb26c54ee1112d3d999feffaf1f2339ca). The touched set (139 design and product lines) is
identical to the apply at dc3abbdd on the old base; EW 1b records are untouched. A second dry run prints "Already applied;
nothing to do." The dry run printed:
design name: "ClearPath" -> "Light Machine Gun"
design "Light Machine Gun" text: "Defend your precious cargo and have fun doing it. Comes with optional sound effects." -> "Light ballistic hose: many small rounds at short range."
design name: "6k Shooter" -> "Slug Rifle"
design "Slug Rifle" text: "The fastest gun in Elysium" -> "Single heavy slugs, fired as fast as the trigger."
design name: "Earp" -> "Long Slug Rifle"
design "Long Slug Rifle" text: "Bringing the Wild West to the New Frontier" -> "A lighter slug rifle with the longest ballistic reach."
design name: "DeathCluster" -> "Cluster Cannon"
design "Cluster Cannon" text: "Because why kill two birds with one stone when you could annihilate them with one hundred?" -> "Medium shotgun: a heavy volley of pellets."
design name: "pretty pretty bang bang" -> "Charged Scattergun"
design "Charged Scattergun" text: "make your last stand/ it's a pretty pretty mess you've got us into/ it's a pretty pretty pretty day to die/ locked and loaded we're going out with a bang" -> "Ballistic shotgun that charges for a heavier volley."
design name: "Spectra" -> "Light Laser"
design "Light Laser" text: "Travelling light, for those travelling light. Take out the competition without slowing down with Spectra." -> "The lightest beam: low damage, quick cycle, long reach."
design name: "FastBlast+-" -> "Cell Blaster"
design "Cell Blaster" text: "Do less damage, faster!" -> "Rapid energy bursts fed by cells that cut its reactor draw."
design name: "ColdFire" -> "Long Laser"
design "Long Laser" text: "Operates by taking advantage of the wave/particle duality in a way that has other scientist tearing their hair out. Fact: Adrasteia's R&D department is the reason Einstein's hair looked like that." -> "Heavy small beam: slow, hard-hitting, long reach."
design name: "CShot RainbowLite Lazer" -> "Heavy Beam Laser"
design "Heavy Beam Laser" text: "Light up the cold, infinite void with more than your smile! Our precision full spectrum lasers are both combat and concert ready." -> "The heaviest beam: nine cells, enormous damage and reach."
design name: "ChargeBlast+-" -> "Charged Blaster"
design "Charged Blaster" text: "A knockout weapon, the arms equivalent of downing 9 shots of Tollgan Swamp liquor." -> "One charged knockout shot."
design name: "ChargeBlast SG" -> "Charged Scatter Blaster"
design "Charged Scatter Blaster" text: "For those days when you really want to spread the pain around." -> "Charged energy shotgun."
design name: "plight" -> "Lightning Projector"
design "Lightning Projector" text: "Chain lightning has your enemies chain smoking while you enjoy a relaxing DragOnBreath vapepop, now available in 17! flavours (manufactured by DragOnBreath, a subsidary of Lucent Media)" -> "Charged chain lightning."
design name: "GT 3K" -> "Guided Missile Launcher"
design "Guided Missile Launcher" text: "Smaller, faster, and more determined than ever. The little missile that could!" -> "Fires guided missiles."
design name: "LRMM72" -> "Long-Range Missile Rack"
design "Long-Range Missile Rack" text: "" -> "Large missile rack for long engagements."
design name: "SRMM72" -> "Short-Range Missile Rack"
design "Short-Range Missile Rack" text: "" -> "Extra-large rack of short-range missiles."
design name: "pswarm" -> "Swarm Rocket Pod"
design "Swarm Rocket Pod" text: "let's set the stars on fire/ there's nothing left on earth to burn/ we're just killing time while we're filling space/ and catacombs in turn" -> "Dumbfire rockets in a swarm."
design name: "scorched void policy" -> "Incendiary Rocket Pod"
design "Incendiary Rocket Pod" text: "let's set the stars on fire/ there's nothing left on earth to burn/ we're just killing time while we're filling space/ and catacombs in turn" -> "Incendiary dumbfire rockets."
design name: "Arctica" -> "Adaptive Radiator"
design "Adaptive Radiator" text: "Go blazingly fast, without burning up." -> "Radiator whose output scales widely with conditions."
design name: "Iapyx" -> "Radiator"
design "Radiator" text: "" -> "Standard two-cell radiator."
design name: "Skiron" -> "Long Radiator"
design "Long Radiator" text: "" -> "Four-cell radiator with a large surface."
design name: "OK Disperser" -> "Heat Pump"
design "Heat Pump" text: "Adapted from the patented cooling system Lucent uses in their professional gaming setups. A high quality cooler, built to withstand even the most aggressive of rage quits." -> "Moves heat to where it can be shed."
design name: "cold like my heart" -> "Cryogenic Heat Pump"
design "Cryogenic Heat Pump" text: "nothing can touch me/ i'm an agent of entropy/ heat death in a bottle like a molotov cocktail" -> "Heavy heat pump that drives temperatures far down."
design name: "Core Power" -> "Compact Reactor"
design "Compact Reactor" text: "It gets the job done, but that's all." -> "Small reactor."
design name: "Manhattan" -> "Heavy Reactor"
design "Heavy Reactor" text: "Because might is right and finesse is for fools" -> "Large three-by-three reactor."
design name: "Notorious" -> "High-Yield Reactor"
design "High-Yield Reactor" text: "A reactor for those who want the biggest reaction" -> "Large reactor with a wide output range."
design name: "Vulcan" -> "Ring Reactor"
design "Ring Reactor" text: "High output, high heat. Blaze across the cosmos like the comet you were meant to be." -> "Ring-shaped reactor, four by four."
design name: "MoveOnPro" -> "Muon Fusion Reactor"
design "Muon Fusion Reactor" text: "The only muon catalyst fusion reactor on the market! Always gets a great reaction." -> "Compact fusion reactor that draws a reservoir."
design name: "Core Power Station Reactor" -> "Station Reactor"
design name: "Manhattan Station Reactor" -> "Heavy Station Reactor"
design "Heavy Station Reactor" text: "A reactor sized for a station's core." -> "A heavy reactor sized for a station's core."
design name: "Notorious Station Reactor" -> "High-Yield Station Reactor"
design "High-Yield Station Reactor" text: "A reactor sized for a station's core." -> "A high-yield reactor sized for a station's core."
design name: "Vulcan Station Reactor" -> "Ring Station Reactor"
design "Ring Station Reactor" text: "A reactor sized for a station's core." -> "A ring reactor sized for a station's core."
design name: "MoveOnPro Station Reactor" -> "Muon Fusion Station Reactor"
design "Muon Fusion Station Reactor" text: "A reactor sized for a station's core." -> "A muon fusion reactor sized for a station's core."
design name: "RevvITup 2.0" -> "High-Thrust Drive"
design "High-Thrust Drive" text: "" -> "Large drive: huge thrust, little torque."
design name: "deep space burnout" -> "Burn Drive"
design "Burn Drive" text: "go go go go go/ get the drift/ you'll burn before you stop " -> "Large drive that runs very hot."
design name: "Talaria" -> "Rugged Drive"
design "Rugged Drive" text: "" -> "Two-cell working drive."
design name: "Victoire" -> "Racing Drive"
design "Racing Drive" text: "The most responsive thruster on the market, for the most irresponsible of racing pilots." -> "Feather-light two-cell drive with extreme response."
design name: "The Bat" -> "Sensor Array"
design "Sensor Array" text: "" -> "Two-by-two long-range sensor array."
design name: "not if i see you first" -> "Compact Sensor"
design "Compact Sensor" text: "[TODO]" -> "One-cell sensor."
design name: "PotaT+-" -> "Capacitor"
design "Capacitor" text: "[TODO]" -> "Stores charge for burst draw."
design name: "SafeStuff II" -> "Secure Cargo Bay"
design "Secure Cargo Bay" text: "Your stuff is safe stuffed in the new, bigger SafeStuff" -> "Three-by-three secured hold."
design name: "Store-All Enterprise Edition" -> "Large Cargo Bay"
design "Large Cargo Bay" text: "A large amount of storage" -> "Six-by-six hold."
design name: "Store-All Plus" -> "Cargo Bay"
design "Cargo Bay" text: "Take your cargo wherever your shipgo" -> "Two-by-two hold."
design name: "Store-All Plus Mini" -> "Small Cargo Bay"
design "Small Cargo Bay" text: "Take some of your cargo wherever your shipgo" -> "One-cell hold."
product of "Autocannon" by Zhestokost: "Autocannon" -> "Hammerfall"
product "Hammerfall" text: "" -> "The rounds fall steadily, and they do not stop until the work is done."
design "Autocannon" text: "" -> "Belt-fed cannon: a steady stream of rounds at long reach."
product of "Cockpit 2x2" by Lightsail: "Cockpit 2x2" -> "Wheelhouse"
product "Wheelhouse" text: "" -> "Every Lightsail captain's favourite chair. The view comes standard."
design "Cockpit 2x2" text: "" -> "Two-by-two crew cockpit."
product of "Industrial Thermostatic Heater" by Lightsail: "Industrial Thermostatic Heater" -> "Hearth"
product "Hearth" text: "" -> "A warm hold, a warm crew, and a warm welcome at the cold end of the run."
design "Industrial Thermostatic Heater" text: "" -> "Turns reactor power into heat to keep the hull warm."
product of "Medium Drive" by Lightsail: "Medium Drive" -> "Steady Haul"
product "Steady Haul" text: "" -> "Keeps you going and going and going..."
design "Medium Drive" text: "Keeps you going and going and going..." -> "Mid-sized working drive."
product of "Small Drive" by Lightsail: "Small Drive" -> "Short Hop"
product "Short Hop" text: "" -> "Dock to dock, and not a metre further than the manifest says."
design "Small Drive" text: "" -> "Small working drive."
product of "Turret Control Module" by AU: "Turret Control Module" -> "Watchtower"
product "Watchtower" text: "" -> "Point it at the dark and get back to work. It keeps watch."
design "Turret Control Module" text: "" -> "Control module for a station turret."
product of "Medium Docking Bay" by AU: "Medium Docking Bay" -> "Open Berth"
product "Open Berth" text: "" -> "Room for a friend, a customer or a tug. We don't ask which."
design "Medium Docking Bay" text: "" -> "Docking bay for mid-sized ships."
product of "Mine Launcher" by DME: "Mine Launcher" -> "parting gift"
product "parting gift" text: "Lays a drifting proximity mine that arms after two seconds." -> "we were never here/ but we left you something/ it's waiting where we were"
design "Mine Launcher" text: "" -> "Lays a drifting proximity mine that arms after two seconds."
design "Large Drive" text: "Keeps you going and going and going..." -> "Large working drive."
design "Tractor Beam" text: "Trap your enemies in gravity wells. Haul thousands of tons of hay through space!" -> "Holds a target in a gravity well and hauls it in."
design "Smoke Proximity" text: "A Spectra that bursts by proximity, blast 8. For scenarios; no one builds it." -> "A Light Laser that bursts by proximity, blast 8. For scenarios; no one builds it."
design "Smoke Delayed" text: "A Spectra that bursts after two cells of metal, blast 4. For scenarios; no one builds it." -> "A Light Laser that bursts after two cells of metal, blast 4. For scenarios; no one builds it."
design "Smoke Refused" text: "A Spectra whose blast is wider than its reach, so it never fires. For scenarios; no one builds it." -> "A Light Laser whose blast is wider than its reach, so it never fires. For scenarios; no one builds it."
design "Smoke Refused Charge" text: "A ChargeBlast SG whose blast is wider than its reach, so it never charges. For scenarios; no one builds it." -> "A Charged Scatter Blaster whose blast is wider than its reach, so it never charges. For scenarios; no one builds it."
product "Victoire" text: "" -> "The most responsive thruster on the market, for the most irresponsible of racing pilots."
product "DragOnBreath" text: "The subsidiary plight's own description already namechecks: DragOnBreath, a Lucent Media property." -> "Chain lightning has your enemies chain smoking while you enjoy a relaxing DragOnBreath vapepop, now available in 17! flavours (manufactured by DragOnBreath, a subsidary of Lucent Media)"
product "Steadfast" text: "Rated for continuous operation. "Continuous" does not imply "uninterrupted." (new text)" -> "It gets the job done, but that's all. Rated for continuous operation. "Continuous" does not imply "uninterrupted.""
product "Steadfast Station" text: "" -> "Rated for continuous operation at station scale. The fine print scales too."
product "Manhattan Station" text: "" -> "A fortress needs a furnace. This is the furnace."
product "Notorious Station" text: "" -> "The biggest reaction in the system, and everyone docked here will hear about it."
product "Vulcan Station" text: "" -> "Keeps the lights on and the forges hot."
product "MoveOnPro Station" text: "" -> "The club never closes. Neither does the reactor."
product "LRMM72" text: "" -> "Engages targets at up to seventy-two kilometres!* *In a vacuum, against a cooperative target."
product "SRMM72" text: "" -> "seventy-two reasons/ to keep your distance/ all of them leaving at once"
product "Iapyx" text: "" -> "Two cells of honest fin. Bolt it on and forget it's there."
product "Skiron" text: "" -> "When the job runs hot, run a bigger fin."
product "Talaria" text: "" -> "Takes a knock, keeps pushing. Built for crews who work for a living."
product "RevvITup 2.0" text: "" -> "Floor it. The bass drops when you do."
product "PotaT+-" text: "[TODO]" -> "Holds the charge until the drop, then lets it all go at once."
product "not if i see you first" text: "[TODO]" -> "eyes in the dark/ you were seen before you looked/ say goodnight"
product "Space Tractor" text: "It's a tractor. In space." -> "It's a tractor. In space. Trap your enemies in gravity wells. Haul thousands of tons of hay through space!"
product "Panopticon" authoring note stripped: "Sees everything, judges nothing. Well, mostly nothing. (new text)" -> "Sees everything, judges nothing. Well, mostly nothing."
product "ClapBack" authoring note stripped: "Your target won't clock it until the killcam. (new text)" -> "Your target won't clock it until the killcam."
product "Panopticon Prime" authoring note stripped: "The upgrade nobody at Finch will admit they needed until they had it. (new text)" -> "The upgrade nobody at Finch will admit they needed until they had it."
product "LockOn Pro" authoring note stripped: "Same promise, sharper lock. (new text)" -> "Same promise, sharper lock."
product "ClapBack Ultra" authoring note stripped: "Now with main-character energy built in. (new text)" -> "Now with main-character energy built in."
product "LockOn" authoring note stripped: "Keeps the lights on your target. (new text)" -> "Keeps the lights on your target."
product "True North" authoring note stripped: "Gets your cargo there. Getting you there is your problem. (new text)" -> "Gets your cargo there. Getting you there is your problem."
Dry run. Pass "apply" to write 89 records.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LockOn.cs hardpoint rows (Talaria, Core Power, GT 3K, FastBlast+-, Iapyx, not if i see you first, PotaT+-,
Store-All Plus) and LaunchSuppressorCatalogTests' SensorName take the generic names. Product-name literals in
CatalogRoleTests and GenericDesignsCommandTests stay: they are brands, not designs. Does not pass the scenario
tests until the catalog commit renames the designs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: AetherDb usage line (kept master's list plus generic-designs [apply]), Program.cs class tail (kept the
launch-suppressor catalog and GenericDesigns both), GameData/Aetheria.cc taken from origin/master (f1642a12; the
generic-designs apply rerun follows in its own commit). The old-name sweep of the newly landed code follows separately.
This commit does not pass the generic-name tests: the catalog is master's, not yet renamed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first version compared at the origin, where the field is a zero cross product and the double oracle is NaN; the test failed on a correct tree. This commit builds and passes; the previous one's FoldedScrollIsTheUnfoldedLawAtAnyAge fails.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The first commit's test equipped the sensor after Add admitted the ship, and TryEquip refuses on a deployed ship; the lot built through ItemManager now rides Make into the mount. b63cdf5d does not pass that test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FlowFieldSceneVerify opens ARPG.unity before anything touches the project's types, drives VolumeSampling and Stardust
Start/Update with a zone attached, and asserts no exception or logged error, FLOW_GLOBAL and FLOW_SLOPE enabled,
_FlowAmplitude non-zero, the stardust buffer changing with the flow and freezing with a paused zone, the noise clock,
and CloudShader pass 0 carrying the FLOW_* keywords. FlowFieldParityVerify now binds a ramp height so FLOW_SLOPE and its
pragma matter, and checks the noise follows _NebulaNoiseTime. The mutation harness runs both checks, with six new mutants.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
VolumeSampling created its flow keywords in static field initializers, which a scene runs inside a MonoBehaviour
construction where Unity refuses GlobalKeyword.Create; they are now created lazily from LowerFlow. FlowField.Scroll and
Cycle return phases strictly below 1. The nebula noise reads _NebulaNoiseTime (zone time) instead of _Time.y. New
FlowFieldTests pin the per-octave fold to the unfolded law across wraps, the cycle at days of zone time, and the phase edge.
The Unity checks for these are in the next commit; this one builds, its parity check does not yet read the noise clock.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ASensorReadsItsInterceptFactorFromItsLotsEmissionDiscipline equips a catalog-style sensor from a lot filled at .5, .2 and .8 and reads Sensor.InterceptFactor (1, .6168, 1.5534). The Longinus half of ReachIsWhatTheShipCanDo takes LonginusBroadsideFactor (1.27, from reach 12.74 over measured 10.40) in place of the Djinni's. Builds not verified until the verify job; no catalog change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Produced by a Yggdrasil job (dotnet run AetherDb -- generic-designs apply at 7d1fa5ce) on the catalog at the base (1746e8bd, sha256 1a2cb55b63f77a113dea0571716994ef8311d5da572a4fc41cbd3a7d2561fb68). 89 records written; the other 169 are byte-identical to the base. Of the 89, 85 differ from the base only in Name/Description; four (Compact Sensor, Sensor Array, Long-Range Missile Rack, Guided Missile Launcher) are authored records and still re-encode their Behaviors through the typed object. A second run answers 'Already applied; nothing to do.' Dry-run text:
design name: "ClearPath" -> "Light Machine Gun"
design "Light Machine Gun" text: "Defend your precious cargo and have fun doing it. Comes with optional sound effects." -> "Light ballistic hose: many small rounds at short range."
design name: "6k Shooter" -> "Slug Rifle"
design "Slug Rifle" text: "The fastest gun in Elysium" -> "Single heavy slugs, fired as fast as the trigger."
design name: "Earp" -> "Long Slug Rifle"
design "Long Slug Rifle" text: "Bringing the Wild West to the New Frontier" -> "A lighter slug rifle with the longest ballistic reach."
design name: "DeathCluster" -> "Cluster Cannon"
design "Cluster Cannon" text: "Because why kill two birds with one stone when you could annihilate them with one hundred?" -> "Medium shotgun: a heavy volley of pellets."
design name: "pretty pretty bang bang" -> "Charged Scattergun"
design "Charged Scattergun" text: "make your last stand/ it's a pretty pretty mess you've got us into/ it's a pretty pretty pretty day to die/ locked and loaded we're going out with a bang" -> "Ballistic shotgun that charges for a heavier volley."
design name: "Spectra" -> "Light Laser"
design "Light Laser" text: "Travelling light, for those travelling light. Take out the competition without slowing down with Spectra." -> "The lightest beam: low damage, quick cycle, long reach."
design name: "FastBlast+-" -> "Cell Blaster"
design "Cell Blaster" text: "Do less damage, faster!" -> "Rapid energy bursts fed by cells that cut its reactor draw."
design name: "ColdFire" -> "Long Laser"
design "Long Laser" text: "Operates by taking advantage of the wave/particle duality in a way that has other scientist tearing their hair out. Fact: Adrasteia's R&D department is the reason Einstein's hair looked like that." -> "Heavy small beam: slow, hard-hitting, long reach."
design name: "CShot RainbowLite Lazer" -> "Heavy Beam Laser"
design "Heavy Beam Laser" text: "Light up the cold, infinite void with more than your smile! Our precision full spectrum lasers are both combat and concert ready." -> "The heaviest beam: nine cells, enormous damage and reach."
design name: "ChargeBlast+-" -> "Charged Blaster"
design "Charged Blaster" text: "A knockout weapon, the arms equivalent of downing 9 shots of Tollgan Swamp liquor." -> "One charged knockout shot."
design name: "ChargeBlast SG" -> "Charged Scatter Blaster"
design "Charged Scatter Blaster" text: "For those days when you really want to spread the pain around." -> "Charged energy shotgun."
design name: "plight" -> "Lightning Projector"
design "Lightning Projector" text: "Chain lightning has your enemies chain smoking while you enjoy a relaxing DragOnBreath vapepop, now available in 17! flavours (manufactured by DragOnBreath, a subsidary of Lucent Media)" -> "Charged chain lightning."
design name: "GT 3K" -> "Guided Missile Launcher"
design "Guided Missile Launcher" text: "Smaller, faster, and more determined than ever. The little missile that could!" -> "Fires guided missiles."
design name: "LRMM72" -> "Long-Range Missile Rack"
design "Long-Range Missile Rack" text: "" -> "Large missile rack for long engagements."
design name: "SRMM72" -> "Short-Range Missile Rack"
design "Short-Range Missile Rack" text: "" -> "Extra-large rack of short-range missiles."
design name: "pswarm" -> "Swarm Rocket Pod"
design "Swarm Rocket Pod" text: "let's set the stars on fire/ there's nothing left on earth to burn/ we're just killing time while we're filling space/ and catacombs in turn" -> "Dumbfire rockets in a swarm."
design name: "scorched void policy" -> "Incendiary Rocket Pod"
design "Incendiary Rocket Pod" text: "let's set the stars on fire/ there's nothing left on earth to burn/ we're just killing time while we're filling space/ and catacombs in turn" -> "Incendiary dumbfire rockets."
design name: "Arctica" -> "Adaptive Radiator"
design "Adaptive Radiator" text: "Go blazingly fast, without burning up." -> "Radiator whose output scales widely with conditions."
design name: "Iapyx" -> "Radiator"
design "Radiator" text: "" -> "Standard two-cell radiator."
design name: "Skiron" -> "Long Radiator"
design "Long Radiator" text: "" -> "Four-cell radiator with a large surface."
design name: "OK Disperser" -> "Heat Pump"
design "Heat Pump" text: "Adapted from the patented cooling system Lucent uses in their professional gaming setups. A high quality cooler, built to withstand even the most aggressive of rage quits." -> "Moves heat to where it can be shed."
design name: "cold like my heart" -> "Cryogenic Heat Pump"
design "Cryogenic Heat Pump" text: "nothing can touch me/ i'm an agent of entropy/ heat death in a bottle like a molotov cocktail" -> "Heavy heat pump that drives temperatures far down."
design name: "Core Power" -> "Compact Reactor"
design "Compact Reactor" text: "It gets the job done, but that's all." -> "Small reactor."
design name: "Manhattan" -> "Heavy Reactor"
design "Heavy Reactor" text: "Because might is right and finesse is for fools" -> "Large three-by-three reactor."
design name: "Notorious" -> "High-Yield Reactor"
design "High-Yield Reactor" text: "A reactor for those who want the biggest reaction" -> "Large reactor with a wide output range."
design name: "Vulcan" -> "Ring Reactor"
design "Ring Reactor" text: "High output, high heat. Blaze across the cosmos like the comet you were meant to be." -> "Ring-shaped reactor, four by four."
design name: "MoveOnPro" -> "Muon Fusion Reactor"
design "Muon Fusion Reactor" text: "The only muon catalyst fusion reactor on the market! Always gets a great reaction." -> "Compact fusion reactor that draws a reservoir."
design name: "Core Power Station Reactor" -> "Station Reactor"
design name: "Manhattan Station Reactor" -> "Heavy Station Reactor"
design "Heavy Station Reactor" text: "A reactor sized for a station's core." -> "A heavy reactor sized for a station's core."
design name: "Notorious Station Reactor" -> "High-Yield Station Reactor"
design "High-Yield Station Reactor" text: "A reactor sized for a station's core." -> "A high-yield reactor sized for a station's core."
design name: "Vulcan Station Reactor" -> "Ring Station Reactor"
design "Ring Station Reactor" text: "A reactor sized for a station's core." -> "A ring reactor sized for a station's core."
design name: "MoveOnPro Station Reactor" -> "Muon Fusion Station Reactor"
design "Muon Fusion Station Reactor" text: "A reactor sized for a station's core." -> "A muon fusion reactor sized for a station's core."
design name: "RevvITup 2.0" -> "High-Thrust Drive"
design "High-Thrust Drive" text: "" -> "Large drive: huge thrust, little torque."
design name: "deep space burnout" -> "Burn Drive"
design "Burn Drive" text: "go go go go go/ get the drift/ you'll burn before you stop " -> "Large drive that runs very hot."
design name: "Talaria" -> "Rugged Drive"
design "Rugged Drive" text: "" -> "Two-cell working drive."
design name: "Victoire" -> "Racing Drive"
design "Racing Drive" text: "The most responsive thruster on the market, for the most irresponsible of racing pilots." -> "Feather-light two-cell drive with extreme response."
design name: "The Bat" -> "Sensor Array"
design "Sensor Array" text: "" -> "Two-by-two long-range sensor array."
design name: "not if i see you first" -> "Compact Sensor"
design "Compact Sensor" text: "[TODO]" -> "One-cell sensor."
design name: "PotaT+-" -> "Capacitor"
design "Capacitor" text: "[TODO]" -> "Stores charge for burst draw."
design name: "SafeStuff II" -> "Secure Cargo Bay"
design "Secure Cargo Bay" text: "Your stuff is safe stuffed in the new, bigger SafeStuff" -> "Three-by-three secured hold."
design name: "Store-All Enterprise Edition" -> "Large Cargo Bay"
design "Large Cargo Bay" text: "A large amount of storage" -> "Six-by-six hold."
design name: "Store-All Plus" -> "Cargo Bay"
design "Cargo Bay" text: "Take your cargo wherever your shipgo" -> "Two-by-two hold."
design name: "Store-All Plus Mini" -> "Small Cargo Bay"
design "Small Cargo Bay" text: "Take some of your cargo wherever your shipgo" -> "One-cell hold."
product of "Autocannon" by Zhestokost: "Autocannon" -> "Hammerfall"
product "Hammerfall" text: "" -> "The rounds fall steadily, and they do not stop until the work is done."
design "Autocannon" text: "" -> "Belt-fed cannon: a steady stream of rounds at long reach."
product of "Cockpit 2x2" by Lightsail: "Cockpit 2x2" -> "Wheelhouse"
product "Wheelhouse" text: "" -> "Every Lightsail captain's favourite chair. The view comes standard."
design "Cockpit 2x2" text: "" -> "Two-by-two crew cockpit."
product of "Industrial Thermostatic Heater" by Lightsail: "Industrial Thermostatic Heater" -> "Hearth"
product "Hearth" text: "" -> "A warm hold, a warm crew, and a warm welcome at the cold end of the run."
design "Industrial Thermostatic Heater" text: "" -> "Turns reactor power into heat to keep the hull warm."
product of "Medium Drive" by Lightsail: "Medium Drive" -> "Steady Haul"
product "Steady Haul" text: "" -> "Keeps you going and going and going..."
design "Medium Drive" text: "Keeps you going and going and going..." -> "Mid-sized working drive."
product of "Small Drive" by Lightsail: "Small Drive" -> "Short Hop"
product "Short Hop" text: "" -> "Dock to dock, and not a metre further than the manifest says."
design "Small Drive" text: "" -> "Small working drive."
product of "Turret Control Module" by AU: "Turret Control Module" -> "Watchtower"
product "Watchtower" text: "" -> "Point it at the dark and get back to work. It keeps watch."
design "Turret Control Module" text: "" -> "Control module for a station turret."
product of "Medium Docking Bay" by AU: "Medium Docking Bay" -> "Open Berth"
product "Open Berth" text: "" -> "Room for a friend, a customer or a tug. We don't ask which."
design "Medium Docking Bay" text: "" -> "Docking bay for mid-sized ships."
product of "Mine Launcher" by DME: "Mine Launcher" -> "parting gift"
product "parting gift" text: "Lays a drifting proximity mine that arms after two seconds." -> "we were never here/ but we left you something/ it's waiting where we were"
design "Mine Launcher" text: "" -> "Lays a drifting proximity mine that arms after two seconds."
design "Large Drive" text: "Keeps you going and going and going..." -> "Large working drive."
design "Tractor Beam" text: "Trap your enemies in gravity wells. Haul thousands of tons of hay through space!" -> "Holds a target in a gravity well and hauls it in."
design "Smoke Proximity" text: "A Spectra that bursts by proximity, blast 8. For scenarios; no one builds it." -> "A Light Laser that bursts by proximity, blast 8. For scenarios; no one builds it."
design "Smoke Delayed" text: "A Spectra that bursts after two cells of metal, blast 4. For scenarios; no one builds it." -> "A Light Laser that bursts after two cells of metal, blast 4. For scenarios; no one builds it."
design "Smoke Refused" text: "A Spectra whose blast is wider than its reach, so it never fires. For scenarios; no one builds it." -> "A Light Laser whose blast is wider than its reach, so it never fires. For scenarios; no one builds it."
design "Smoke Refused Charge" text: "A ChargeBlast SG whose blast is wider than its reach, so it never charges. For scenarios; no one builds it." -> "A Charged Scatter Blaster whose blast is wider than its reach, so it never charges. For scenarios; no one builds it."
product "Victoire" text: "" -> "The most responsive thruster on the market, for the most irresponsible of racing pilots."
product "DragOnBreath" text: "The subsidiary plight's own description already namechecks: DragOnBreath, a Lucent Media property." -> "Chain lightning has your enemies chain smoking while you enjoy a relaxing DragOnBreath vapepop, now available in 17! flavours (manufactured by DragOnBreath, a subsidary of Lucent Media)"
product "Steadfast" text: "Rated for continuous operation. "Continuous" does not imply "uninterrupted." (new text)" -> "It gets the job done, but that's all. Rated for continuous operation. "Continuous" does not imply "uninterrupted.""
product "Steadfast Station" text: "" -> "Rated for continuous operation at station scale. The fine print scales too."
product "Manhattan Station" text: "" -> "A fortress needs a furnace. This is the furnace."
product "Notorious Station" text: "" -> "The biggest reaction in the system, and everyone docked here will hear about it."
product "Vulcan Station" text: "" -> "Keeps the lights on and the forges hot."
product "MoveOnPro Station" text: "" -> "The club never closes. Neither does the reactor."
product "LRMM72" text: "" -> "Engages targets at up to seventy-two kilometres!* *In a vacuum, against a cooperative target."
product "SRMM72" text: "" -> "seventy-two reasons/ to keep your distance/ all of them leaving at once"
product "Iapyx" text: "" -> "Two cells of honest fin. Bolt it on and forget it's there."
product "Skiron" text: "" -> "When the job runs hot, run a bigger fin."
product "Talaria" text: "" -> "Takes a knock, keeps pushing. Built for crews who work for a living."
product "RevvITup 2.0" text: "" -> "Floor it. The bass drops when you do."
product "PotaT+-" text: "[TODO]" -> "Holds the charge until the drop, then lets it all go at once."
product "not if i see you first" text: "[TODO]" -> "eyes in the dark/ you were seen before you looked/ say goodnight"
product "Space Tractor" text: "It's a tractor. In space." -> "It's a tractor. In space. Trap your enemies in gravity wells. Haul thousands of tons of hay through space!"
product "Panopticon" authoring note stripped: "Sees everything, judges nothing. Well, mostly nothing. (new text)" -> "Sees everything, judges nothing. Well, mostly nothing."
product "ClapBack" authoring note stripped: "Your target won't clock it until the killcam. (new text)" -> "Your target won't clock it until the killcam."
product "Panopticon Prime" authoring note stripped: "The upgrade nobody at Finch will admit they needed until they had it. (new text)" -> "The upgrade nobody at Finch will admit they needed until they had it."
product "LockOn Pro" authoring note stripped: "Same promise, sharper lock. (new text)" -> "Same promise, sharper lock."
product "ClapBack Ultra" authoring note stripped: "Now with main-character energy built in. (new text)" -> "Now with main-character energy built in."
product "LockOn" authoring note stripped: "Keeps the lights on your target. (new text)" -> "Keeps the lights on your target."
product "True North" authoring note stripped: "Gets your cargo there. Getting you there is your problem. (new text)" -> "Gets your cargo there. Getting you there is your problem."
Dry run. Pass "apply" to write 89 records.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous sweep commit put the ExpectUnchanged batch in RolesMigrate's commit (first match of the anchor) and left GenericDesigns on a plain commit. RolesMigrate is restored to its base text; GenericDesigns now lands its batch onto the file as it is. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Produced by a Yggdrasil job (dotnet run AetherDb -- generic-designs apply) on the catalog at the base (1746e8bd, sha256 1a2cb55b63f77a113dea0571716994ef8311d5da572a4fc41cbd3a7d2561fb68). The batch carries ExpectUnchanged: 89 records written, every other record byte-identical to the base. A second run answers 'Already applied; nothing to do.' Dry-run text:
design name: "ClearPath" -> "Light Machine Gun"
design "Light Machine Gun" text: "Defend your precious cargo and have fun doing it. Comes with optional sound effects." -> "Light ballistic hose: many small rounds at short range."
design name: "6k Shooter" -> "Slug Rifle"
design "Slug Rifle" text: "The fastest gun in Elysium" -> "Single heavy slugs, fired as fast as the trigger."
design name: "Earp" -> "Long Slug Rifle"
design "Long Slug Rifle" text: "Bringing the Wild West to the New Frontier" -> "A lighter slug rifle with the longest ballistic reach."
design name: "DeathCluster" -> "Cluster Cannon"
design "Cluster Cannon" text: "Because why kill two birds with one stone when you could annihilate them with one hundred?" -> "Medium shotgun: a heavy volley of pellets."
design name: "pretty pretty bang bang" -> "Charged Scattergun"
design "Charged Scattergun" text: "make your last stand/ it's a pretty pretty mess you've got us into/ it's a pretty pretty pretty day to die/ locked and loaded we're going out with a bang" -> "Ballistic shotgun that charges for a heavier volley."
design name: "Spectra" -> "Light Laser"
design "Light Laser" text: "Travelling light, for those travelling light. Take out the competition without slowing down with Spectra." -> "The lightest beam: low damage, quick cycle, long reach."
design name: "FastBlast+-" -> "Cell Blaster"
design "Cell Blaster" text: "Do less damage, faster!" -> "Rapid energy bursts fed by cells that cut its reactor draw."
design name: "ColdFire" -> "Long Laser"
design "Long Laser" text: "Operates by taking advantage of the wave/particle duality in a way that has other scientist tearing their hair out. Fact: Adrasteia's R&D department is the reason Einstein's hair looked like that." -> "Heavy small beam: slow, hard-hitting, long reach."
design name: "CShot RainbowLite Lazer" -> "Heavy Beam Laser"
design "Heavy Beam Laser" text: "Light up the cold, infinite void with more than your smile! Our precision full spectrum lasers are both combat and concert ready." -> "The heaviest beam: nine cells, enormous damage and reach."
design name: "ChargeBlast+-" -> "Charged Blaster"
design "Charged Blaster" text: "A knockout weapon, the arms equivalent of downing 9 shots of Tollgan Swamp liquor." -> "One charged knockout shot."
design name: "ChargeBlast SG" -> "Charged Scatter Blaster"
design "Charged Scatter Blaster" text: "For those days when you really want to spread the pain around." -> "Charged energy shotgun."
design name: "plight" -> "Lightning Projector"
design "Lightning Projector" text: "Chain lightning has your enemies chain smoking while you enjoy a relaxing DragOnBreath vapepop, now available in 17! flavours (manufactured by DragOnBreath, a subsidary of Lucent Media)" -> "Charged chain lightning."
design name: "GT 3K" -> "Guided Missile Launcher"
design "Guided Missile Launcher" text: "Smaller, faster, and more determined than ever. The little missile that could!" -> "Fires guided missiles."
design name: "LRMM72" -> "Long-Range Missile Rack"
design "Long-Range Missile Rack" text: "" -> "Large missile rack for long engagements."
design name: "SRMM72" -> "Short-Range Missile Rack"
design "Short-Range Missile Rack" text: "" -> "Extra-large rack of short-range missiles."
design name: "pswarm" -> "Swarm Rocket Pod"
design "Swarm Rocket Pod" text: "let's set the stars on fire/ there's nothing left on earth to burn/ we're just killing time while we're filling space/ and catacombs in turn" -> "Dumbfire rockets in a swarm."
design name: "scorched void policy" -> "Incendiary Rocket Pod"
design "Incendiary Rocket Pod" text: "let's set the stars on fire/ there's nothing left on earth to burn/ we're just killing time while we're filling space/ and catacombs in turn" -> "Incendiary dumbfire rockets."
design name: "Arctica" -> "Adaptive Radiator"
design "Adaptive Radiator" text: "Go blazingly fast, without burning up." -> "Radiator whose output scales widely with conditions."
design name: "Iapyx" -> "Radiator"
design "Radiator" text: "" -> "Standard two-cell radiator."
design name: "Skiron" -> "Long Radiator"
design "Long Radiator" text: "" -> "Four-cell radiator with a large surface."
design name: "OK Disperser" -> "Heat Pump"
design "Heat Pump" text: "Adapted from the patented cooling system Lucent uses in their professional gaming setups. A high quality cooler, built to withstand even the most aggressive of rage quits." -> "Moves heat to where it can be shed."
design name: "cold like my heart" -> "Cryogenic Heat Pump"
design "Cryogenic Heat Pump" text: "nothing can touch me/ i'm an agent of entropy/ heat death in a bottle like a molotov cocktail" -> "Heavy heat pump that drives temperatures far down."
design name: "Core Power" -> "Compact Reactor"
design "Compact Reactor" text: "It gets the job done, but that's all." -> "Small reactor."
design name: "Manhattan" -> "Heavy Reactor"
design "Heavy Reactor" text: "Because might is right and finesse is for fools" -> "Large three-by-three reactor."
design name: "Notorious" -> "High-Yield Reactor"
design "High-Yield Reactor" text: "A reactor for those who want the biggest reaction" -> "Large reactor with a wide output range."
design name: "Vulcan" -> "Ring Reactor"
design "Ring Reactor" text: "High output, high heat. Blaze across the cosmos like the comet you were meant to be." -> "Ring-shaped reactor, four by four."
design name: "MoveOnPro" -> "Muon Fusion Reactor"
design "Muon Fusion Reactor" text: "The only muon catalyst fusion reactor on the market! Always gets a great reaction." -> "Compact fusion reactor that draws a reservoir."
design name: "Core Power Station Reactor" -> "Station Reactor"
design name: "Manhattan Station Reactor" -> "Heavy Station Reactor"
design "Heavy Station Reactor" text: "A reactor sized for a station's core." -> "A heavy reactor sized for a station's core."
design name: "Notorious Station Reactor" -> "High-Yield Station Reactor"
design "High-Yield Station Reactor" text: "A reactor sized for a station's core." -> "A high-yield reactor sized for a station's core."
design name: "Vulcan Station Reactor" -> "Ring Station Reactor"
design "Ring Station Reactor" text: "A reactor sized for a station's core." -> "A ring reactor sized for a station's core."
design name: "MoveOnPro Station Reactor" -> "Muon Fusion Station Reactor"
design "Muon Fusion Station Reactor" text: "A reactor sized for a station's core." -> "A muon fusion reactor sized for a station's core."
design name: "RevvITup 2.0" -> "High-Thrust Drive"
design "High-Thrust Drive" text: "" -> "Large drive: huge thrust, little torque."
design name: "deep space burnout" -> "Burn Drive"
design "Burn Drive" text: "go go go go go/ get the drift/ you'll burn before you stop " -> "Large drive that runs very hot."
design name: "Talaria" -> "Rugged Drive"
design "Rugged Drive" text: "" -> "Two-cell working drive."
design name: "Victoire" -> "Racing Drive"
design "Racing Drive" text: "The most responsive thruster on the market, for the most irresponsible of racing pilots." -> "Feather-light two-cell drive with extreme response."
design name: "The Bat" -> "Sensor Array"
design "Sensor Array" text: "" -> "Two-by-two long-range sensor array."
design name: "not if i see you first" -> "Compact Sensor"
design "Compact Sensor" text: "[TODO]" -> "One-cell sensor."
design name: "PotaT+-" -> "Capacitor"
design "Capacitor" text: "[TODO]" -> "Stores charge for burst draw."
design name: "SafeStuff II" -> "Secure Cargo Bay"
design "Secure Cargo Bay" text: "Your stuff is safe stuffed in the new, bigger SafeStuff" -> "Three-by-three secured hold."
design name: "Store-All Enterprise Edition" -> "Large Cargo Bay"
design "Large Cargo Bay" text: "A large amount of storage" -> "Six-by-six hold."
design name: "Store-All Plus" -> "Cargo Bay"
design "Cargo Bay" text: "Take your cargo wherever your shipgo" -> "Two-by-two hold."
design name: "Store-All Plus Mini" -> "Small Cargo Bay"
design "Small Cargo Bay" text: "Take some of your cargo wherever your shipgo" -> "One-cell hold."
product of "Autocannon" by Zhestokost: "Autocannon" -> "Hammerfall"
product "Hammerfall" text: "" -> "The rounds fall steadily, and they do not stop until the work is done."
design "Autocannon" text: "" -> "Belt-fed cannon: a steady stream of rounds at long reach."
product of "Cockpit 2x2" by Lightsail: "Cockpit 2x2" -> "Wheelhouse"
product "Wheelhouse" text: "" -> "Every Lightsail captain's favourite chair. The view comes standard."
design "Cockpit 2x2" text: "" -> "Two-by-two crew cockpit."
product of "Industrial Thermostatic Heater" by Lightsail: "Industrial Thermostatic Heater" -> "Hearth"
product "Hearth" text: "" -> "A warm hold, a warm crew, and a warm welcome at the cold end of the run."
design "Industrial Thermostatic Heater" text: "" -> "Turns reactor power into heat to keep the hull warm."
product of "Medium Drive" by Lightsail: "Medium Drive" -> "Steady Haul"
product "Steady Haul" text: "" -> "Keeps you going and going and going..."
design "Medium Drive" text: "Keeps you going and going and going..." -> "Mid-sized working drive."
product of "Small Drive" by Lightsail: "Small Drive" -> "Short Hop"
product "Short Hop" text: "" -> "Dock to dock, and not a metre further than the manifest says."
design "Small Drive" text: "" -> "Small working drive."
product of "Turret Control Module" by AU: "Turret Control Module" -> "Watchtower"
product "Watchtower" text: "" -> "Point it at the dark and get back to work. It keeps watch."
design "Turret Control Module" text: "" -> "Control module for a station turret."
product of "Medium Docking Bay" by AU: "Medium Docking Bay" -> "Open Berth"
product "Open Berth" text: "" -> "Room for a friend, a customer or a tug. We don't ask which."
design "Medium Docking Bay" text: "" -> "Docking bay for mid-sized ships."
product of "Mine Launcher" by DME: "Mine Launcher" -> "parting gift"
product "parting gift" text: "Lays a drifting proximity mine that arms after two seconds." -> "we were never here/ but we left you something/ it's waiting where we were"
design "Mine Launcher" text: "" -> "Lays a drifting proximity mine that arms after two seconds."
design "Large Drive" text: "Keeps you going and going and going..." -> "Large working drive."
design "Tractor Beam" text: "Trap your enemies in gravity wells. Haul thousands of tons of hay through space!" -> "Holds a target in a gravity well and hauls it in."
design "Smoke Proximity" text: "A Spectra that bursts by proximity, blast 8. For scenarios; no one builds it." -> "A Light Laser that bursts by proximity, blast 8. For scenarios; no one builds it."
design "Smoke Delayed" text: "A Spectra that bursts after two cells of metal, blast 4. For scenarios; no one builds it." -> "A Light Laser that bursts after two cells of metal, blast 4. For scenarios; no one builds it."
design "Smoke Refused" text: "A Spectra whose blast is wider than its reach, so it never fires. For scenarios; no one builds it." -> "A Light Laser whose blast is wider than its reach, so it never fires. For scenarios; no one builds it."
design "Smoke Refused Charge" text: "A ChargeBlast SG whose blast is wider than its reach, so it never charges. For scenarios; no one builds it." -> "A Charged Scatter Blaster whose blast is wider than its reach, so it never charges. For scenarios; no one builds it."
product "Victoire" text: "" -> "The most responsive thruster on the market, for the most irresponsible of racing pilots."
product "DragOnBreath" text: "The subsidiary plight's own description already namechecks: DragOnBreath, a Lucent Media property." -> "Chain lightning has your enemies chain smoking while you enjoy a relaxing DragOnBreath vapepop, now available in 17! flavours (manufactured by DragOnBreath, a subsidary of Lucent Media)"
product "Steadfast" text: "Rated for continuous operation. "Continuous" does not imply "uninterrupted." (new text)" -> "It gets the job done, but that's all. Rated for continuous operation. "Continuous" does not imply "uninterrupted.""
product "Steadfast Station" text: "" -> "Rated for continuous operation at station scale. The fine print scales too."
product "Manhattan Station" text: "" -> "A fortress needs a furnace. This is the furnace."
product "Notorious Station" text: "" -> "The biggest reaction in the system, and everyone docked here will hear about it."
product "Vulcan Station" text: "" -> "Keeps the lights on and the forges hot."
product "MoveOnPro Station" text: "" -> "The club never closes. Neither does the reactor."
product "LRMM72" text: "" -> "Engages targets at up to seventy-two kilometres!* *In a vacuum, against a cooperative target."
product "SRMM72" text: "" -> "seventy-two reasons/ to keep your distance/ all of them leaving at once"
product "Iapyx" text: "" -> "Two cells of honest fin. Bolt it on and forget it's there."
product "Skiron" text: "" -> "When the job runs hot, run a bigger fin."
product "Talaria" text: "" -> "Takes a knock, keeps pushing. Built for crews who work for a living."
product "RevvITup 2.0" text: "" -> "Floor it. The bass drops when you do."
product "PotaT+-" text: "[TODO]" -> "Holds the charge until the drop, then lets it all go at once."
product "not if i see you first" text: "[TODO]" -> "eyes in the dark/ you were seen before you looked/ say goodnight"
product "Space Tractor" text: "It's a tractor. In space." -> "It's a tractor. In space. Trap your enemies in gravity wells. Haul thousands of tons of hay through space!"
product "Panopticon" authoring note stripped: "Sees everything, judges nothing. Well, mostly nothing. (new text)" -> "Sees everything, judges nothing. Well, mostly nothing."
product "ClapBack" authoring note stripped: "Your target won't clock it until the killcam. (new text)" -> "Your target won't clock it until the killcam."
product "Panopticon Prime" authoring note stripped: "The upgrade nobody at Finch will admit they needed until they had it. (new text)" -> "The upgrade nobody at Finch will admit they needed until they had it."
product "LockOn Pro" authoring note stripped: "Same promise, sharper lock. (new text)" -> "Same promise, sharper lock."
product "ClapBack Ultra" authoring note stripped: "Now with main-character energy built in. (new text)" -> "Now with main-character energy built in."
product "LockOn" authoring note stripped: "Keeps the lights on your target. (new text)" -> "Keeps the lights on your target."
product "True North" authoring note stripped: "Gets your cargo there. Getting you there is your problem. (new text)" -> "Gets your cargo there. Getting you there is your problem."
Dry run. Pass "apply" to write 89 records.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pull-progress input came from FieldTester.PullProgress, so a mutant of that function agreed with itself. The smoke now spells the pull start from Extend and Envelop independently.
Builds; both smokes green; Unity mutant u-f killed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scenario Briefs (Crossing, Duel, FusedRounds, LauncherAngles, StarvedReactor, WeaponFeel), scenario and test comments and test locals say the design names (Lightning Projector, Light Laser, Cell Blaster, Guided Missile Launcher, Swarm Rocket Pod, Muon Fusion Reactor, Cluster Cannon, Charged Scatter Blaster, Rugged Drive, Racing Drive) instead of the retired brand names; Crossing.PlightRange is ProjectorRange.
generic-designs: the batch carries ExpectUnchanged so every record it does not author keeps its bytes (the plain commit re-encoded four records, dropping SensorData key 7). DesignTexts rewrites Large Drive, Tractor Beam, Smoke Proximity, Smoke Delayed, Smoke Refused and Smoke Refused Charge as tech-node text; the Tractor Beam's old line moves to its product Space Tractor. smoke-rounds' authored text names Light Laser and Charged Scatter Blaster to match.
Tests: NoScenarioBriefNamesRetiredGear, TheCapacitorDesignCarriesItsRoleMap, NoDesignTextNamesAProduct, and a command test that widens every payload and checks apply changes only the three records it authors and restores the rewritten texts.
Not built or run at this commit; GameData/Aetheria.cc is regenerated in the next commit, so NoDesignTextNamesAProduct and the command test fail here.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PickupTests: TheBeamTakesFromWhicheverBayCanStore is a theory over which bay is full.
CapabilityPresentationSmoke: tracked ship's hull hit is drawn; a beam above the held point stays on the ground plane; a grab at the beam keeps its aim; the other entity's grab reaches its own bound beam and not this one.
FloatingItemsDrawnSmoke: a held item's label comes up; the test scene's stand-in pose equals the zone's held pose.
Zone.HeldPull is the one smoothstep of the held pose; FieldDriver's tendril and FieldTester's stand-in call it (FieldTester.PulledPosition, PullProgress), deleting the two copies.
All commits build (Unity compile and both smokes green).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces the allocated*1.2 ceiling, which scaled with Reach; a reach inflated 1.1x or 3x now fails on the Djinni half too. Fixes the stale comment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A gun worn to half its range falls under the fuse's arming distance and FireControl refuses it, so the fixture never fired. At 95% the gun fires and its live range (38) still differs from its rated 40.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regenerated from e99cfbed's catalog by the tool (apply twice, second writes nothing); the seven new records carry fresh guids.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Output of `dotnet run --project tools/AetherDb -- launch-suppressor apply` at 18fa8a5e on the catalog at fa6108de (sha256 of the base 1a2cb55b63f77a113dea0571716994ef8311d5da572a4fc41cbd3a7d2561fb68, of the result f1642a12c3f7b1184a159985944f128704bd489151f8fb9077a057b9c0fa7347). Decoded against the base: 258 -> 266 records, header equal, 8 added, no base record changed (the previous blob re-encoded four). The dry run printed:
/src/Assets/Scripts/ServerShared/Behaviors/LockWeapon.cs(49,25): warning CS0067: The event 'LockWeapon.OnLocked' is never used [/src/Aetheria.Shared/Aetheria.Shared.csproj]
/src/Assets/Scripts/ServerShared/Behaviors/LockWeapon.cs(51,25): warning CS0067: The event 'LockWeapon.OnLockLost' is never used [/src/Aetheria.Shared/Aetheria.Shared.csproj]
/src/Assets/Scripts/ServerShared/Behaviors/LockWeapon.cs(50,25): warning CS0067: The event 'LockWeapon.OnBeginLocking' is never used [/src/Aetheria.Shared/Aetheria.Shared.csproj]
New designs:
Launch Suppressor 1x1 Tool mass 60 price 40000 durability 50 roles [baffles, heat sink]
Factor 0.08-0.03 (baffles)
HeatPerUnit 1.4-0.8 (heat sink)
RatedTemperature 310-330 (heat sink)
"Baffles and a heat sink remove nearly all of a weapon's launch flash, hiding the shot from its target. The removed light is dissipated as heat in the device's own cell; above its rated temperature it passes the full flash."
Launch Baffle 1x1 Tool mass 60 price 25000 durability 50 roles [baffles, heat sink]
Factor 0.4-0.2 (baffles)
HeatPerUnit 0.8-0.5 (heat sink)
RatedTemperature 310-330 (heat sink)
"Baffles dim a weapon's launch flash to a fraction and a heat sink absorbs the difference. The target still sees the shot; distant observers do not. The sink sustains the effect through a long fight."
New products:
Launch Suppressor -> "Velvet Rope" by NiteLife, every role mean 0.55 dev 0.12: Keeps the flash on the inside. Nobody sees who you let out.
Launch Suppressor -> "Hush" by Finch, every role mean 0.65 dev 0.2: Small, quiet, and the first shot is a secret. The second, probably.
Launch Suppressor -> "lights out" by DME, every role mean 0.55 dev 0.2: no flash/ no name/ nobody saw a thing
Launch Suppressor -> "Plausible Deniability" by R&D, every role mean 0.6 dev 0.15: Hides 100% of launches!* *Launches defined as the ones nobody saw.
Launch Baffle -> "Low Profile" by Lightsail, every role mean 0.55 dev 0.1: Quiet on the manifest, quiet on the scope. A hauler nobody notices is a hauler that gets paid.
Launch Baffle -> "Fast Pass Dim" by Alakrita, every role mean 0.6 dev 0.14: Takes the glare off every launch and keeps the schedule. Anyone close enough to mind was already your problem.
Dry run. Pass "apply" to author 2 designs and 6 products.
The full Aetheria.Shared.Tests suite passed on this catalog (1714 passed, 0 failed) at 18fa8a5e.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- VolumeSampling registers FLOW_GLOBAL / FLOW_SLOPE with GlobalKeyword.Create (the constructor only looks up).
- FlowField.Scroll folds each Tri3D octave's phase modulo 1 in double (float2, [0,1)); Zone.Flow evaluates at Zone.FlowScroll.
- FlowField.Cycle: the nebula's flow cycle and Stardust's particle lifetime phase come from zone time (_FlowCycle, phase uniform), not _Time.y / Time.time.
- FlowFieldParityVerify reaches the keyword-gated flow(), fails on logged errors, tolerance 3e-4; mutants updated.
Not built or run when committed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Command: the batch carries ExpectUnchanged, so CultCache lands it onto the file as it is. A plain commit re-encoded every record through today's types (two sensors lost a retired key-7 payload and gained a trailing nil, LRMM72 and GT 3K gained a trailing nil); this one leaves every record it did not author byte-identical. A conflicting writer now fails the apply instead of being overwritten.
Tests: the mild baffle stores exactly flash x Factor and pays exactly its predicted heat for the first 30 launches of a 300 flash or 8 of a 1000 flash, then is either hidden or bright; the rating is pinned to half a kelvin either side at three heat-sink qualities; suppression and heat are checked at six flash strengths; the observer fits the named sensor 'not if i see you first'; the command test widens every payload and checks apply leaves all other records byte-identical.
This commit's GameData/Aetheria.cc pointer is the previous apply's output; the regenerated catalog follows in its own commit. Not built or run at this commit yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous commit ticked before firing and the gun never lit; the wear is attached, the gun fires, then the live range is asserted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Enemy and own schematic rows, and the rebuild key's Item half, are pinned by fixtures whose live range differs from the rated range and whose guns share a claimed design.
- Entity.Claim refuses a hull class on a non-hull item as it refuses a non-hull class on the hull.
- ChargedWeapon live reads (DamagePerSecond, RangeDamagePerSecond) start from the uncharged damage so the charge multiplier is applied once and a nominal weapon reads its rated figure at every charge.
- RevealedWeapons orders reveals of gear the subject no longer carries after all fitted gear, then by lot.
Builds and tests unverified at this commit; verification follows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The probe steps a Zone, lowers its flow through VolumeSampling.LowerFlow, dispatches the HLSL
globalFlow and slopeFlow, and compares with FlowField (max error 1.0e-4 of amplitude, tolerance
1e-3). All commits build; the Unity side was verified from this tree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lock's Strength rises from its first paint to its last; it is not monotonic. Builds: yes (test-only change after 038628e9, which built and failed this assertion).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The command's authoring-note suffix is a public constant so the catalog test reads the one literal.
Not built yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scenarios, tests, AetherDb templates and name-keyed maps now resolve designs by the generic names (231 literals in 30 files);
TargetingCatalog's product texts lose the authoring note, Steadfast carries its r3 text, DragOnBreath plight's old design text.
Not built yet; tests that depend on the generic-designs command and new tests follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Output of `dotnet run --project tools/AetherDb -- launch-suppressor apply` on the catalog at fa6108de (sha256 of the base 1a2cb55b63f77a113dea0571716994ef8311d5da572a4fc41cbd3a7d2561fb68, of the result 3c2f3649762d35f66f3f220dbd4aa289d1711f05f8b7195a4cc3e5cc43085fa8). The dry run printed:
New designs:
Launch Suppressor 1x1 Tool mass 60 price 40000 durability 50 roles [baffles, heat sink]
Factor 0.08-0.03 (baffles)
HeatPerUnit 1.4-0.8 (heat sink)
RatedTemperature 310-330 (heat sink)
"Baffles and a heat sink remove nearly all of a weapon's launch flash, hiding the shot from its target. The removed light is dissipated as heat in the device's own cell; above its rated temperature it passes the full flash."
Launch Baffle 1x1 Tool mass 60 price 25000 durability 50 roles [baffles, heat sink]
Factor 0.4-0.2 (baffles)
HeatPerUnit 0.8-0.5 (heat sink)
RatedTemperature 310-330 (heat sink)
"Baffles dim a weapon's launch flash to a fraction and a heat sink absorbs the difference. The target still sees the shot; distant observers do not. The sink sustains the effect through a long fight."
New products:
Launch Suppressor -> "Velvet Rope" by NiteLife, every role mean 0.55 dev 0.12: Keeps the flash on the inside. Nobody sees who you let out.
Launch Suppressor -> "Hush" by Finch, every role mean 0.65 dev 0.2: Small, quiet, and the first shot is a secret. The second, probably.
Launch Suppressor -> "lights out" by DME, every role mean 0.55 dev 0.2: no flash/ no name/ nobody saw a thing
Launch Suppressor -> "Plausible Deniability" by R&D, every role mean 0.6 dev 0.15: Hides 100% of launches!* *Launches defined as the ones nobody saw.
Launch Baffle -> "Low Profile" by Lightsail, every role mean 0.55 dev 0.1: Quiet on the manifest, quiet on the scope. A hauler nobody notices is a hauler that gets paid.
Launch Baffle -> "Fast Pass Dim" by Alakrita, every role mean 0.6 dev 0.14: Takes the glare off every launch and keeps the schedule. Anyone close enough to mind was already your problem.
Dry run. Pass "apply" to author 2 designs and 6 products.
Builds and passes the full suite (1687 passed, 0 failed) at this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Produced by a KEEP-style Yggdrasil job (dotnet run AetherDb -- generic-designs apply) on the
catalog at 0ee04f20. A second apply answers "Already applied; nothing to do." Dry-run text:
design name: "ClearPath" -> "Light Machine Gun"
design "Light Machine Gun" text: "Defend your precious cargo and have fun doing it. Comes with optional sound effects." -> "Light ballistic hose: many small rounds at short range."
design name: "6k Shooter" -> "Slug Rifle"
design "Slug Rifle" text: "The fastest gun in Elysium" -> "Single heavy slugs, fired as fast as the trigger."
design name: "Earp" -> "Long Slug Rifle"
design "Long Slug Rifle" text: "Bringing the Wild West to the New Frontier" -> "A lighter slug rifle with the longest ballistic reach."
design name: "DeathCluster" -> "Cluster Cannon"
design "Cluster Cannon" text: "Because why kill two birds with one stone when you could annihilate them with one hundred?" -> "Medium shotgun: a heavy volley of pellets."
design name: "pretty pretty bang bang" -> "Charged Scattergun"
design "Charged Scattergun" text: "make your last stand/ it's a pretty pretty mess you've got us into/ it's a pretty pretty pretty day to die/ locked and loaded we're going out with a bang" -> "Ballistic shotgun that charges for a heavier volley."
design name: "Spectra" -> "Light Laser"
design "Light Laser" text: "Travelling light, for those travelling light. Take out the competition without slowing down with Spectra." -> "The lightest beam: low damage, quick cycle, long reach."
design name: "FastBlast+-" -> "Cell Blaster"
design "Cell Blaster" text: "Do less damage, faster!" -> "Rapid energy bursts fed by cells that cut its reactor draw."
design name: "ColdFire" -> "Long Laser"
design "Long Laser" text: "Operates by taking advantage of the wave/particle duality in a way that has other scientist tearing their hair out. Fact: Adrasteia's R&D department is the reason Einstein's hair looked like that." -> "Heavy small beam: slow, hard-hitting, long reach."
design name: "CShot RainbowLite Lazer" -> "Heavy Beam Laser"
design "Heavy Beam Laser" text: "Light up the cold, infinite void with more than your smile! Our precision full spectrum lasers are both combat and concert ready." -> "The heaviest beam: nine cells, enormous damage and reach."
design name: "ChargeBlast+-" -> "Charged Blaster"
design "Charged Blaster" text: "A knockout weapon, the arms equivalent of downing 9 shots of Tollgan Swamp liquor." -> "One charged knockout shot."
design name: "ChargeBlast SG" -> "Charged Scatter Blaster"
design "Charged Scatter Blaster" text: "For those days when you really want to spread the pain around." -> "Charged energy shotgun."
design name: "plight" -> "Lightning Projector"
design "Lightning Projector" text: "Chain lightning has your enemies chain smoking while you enjoy a relaxing DragOnBreath vapepop, now available in 17! flavours (manufactured by DragOnBreath, a subsidary of Lucent Media)" -> "Charged chain lightning."
design name: "GT 3K" -> "Guided Missile Launcher"
design "Guided Missile Launcher" text: "Smaller, faster, and more determined than ever. The little missile that could!" -> "Fires guided missiles."
design name: "LRMM72" -> "Long-Range Missile Rack"
design "Long-Range Missile Rack" text: "" -> "Large missile rack for long engagements."
design name: "SRMM72" -> "Short-Range Missile Rack"
design "Short-Range Missile Rack" text: "" -> "Extra-large rack of short-range missiles."
design name: "pswarm" -> "Swarm Rocket Pod"
design "Swarm Rocket Pod" text: "let's set the stars on fire/ there's nothing left on earth to burn/ we're just killing time while we're filling space/ and catacombs in turn" -> "Dumbfire rockets in a swarm."
design name: "scorched void policy" -> "Incendiary Rocket Pod"
design "Incendiary Rocket Pod" text: "let's set the stars on fire/ there's nothing left on earth to burn/ we're just killing time while we're filling space/ and catacombs in turn" -> "Incendiary dumbfire rockets."
design name: "Arctica" -> "Adaptive Radiator"
design "Adaptive Radiator" text: "Go blazingly fast, without burning up." -> "Radiator whose output scales widely with conditions."
design name: "Iapyx" -> "Radiator"
design "Radiator" text: "" -> "Standard two-cell radiator."
design name: "Skiron" -> "Long Radiator"
design "Long Radiator" text: "" -> "Four-cell radiator with a large surface."
design name: "OK Disperser" -> "Heat Pump"
design "Heat Pump" text: "Adapted from the patented cooling system Lucent uses in their professional gaming setups. A high quality cooler, built to withstand even the most aggressive of rage quits." -> "Moves heat to where it can be shed."
design name: "cold like my heart" -> "Cryogenic Heat Pump"
design "Cryogenic Heat Pump" text: "nothing can touch me/ i'm an agent of entropy/ heat death in a bottle like a molotov cocktail" -> "Heavy heat pump that drives temperatures far down."
design name: "Core Power" -> "Compact Reactor"
design "Compact Reactor" text: "It gets the job done, but that's all." -> "Small reactor."
design name: "Manhattan" -> "Heavy Reactor"
design "Heavy Reactor" text: "Because might is right and finesse is for fools" -> "Large three-by-three reactor."
design name: "Notorious" -> "High-Yield Reactor"
design "High-Yield Reactor" text: "A reactor for those who want the biggest reaction" -> "Large reactor with a wide output range."
design name: "Vulcan" -> "Ring Reactor"
design "Ring Reactor" text: "High output, high heat. Blaze across the cosmos like the comet you were meant to be." -> "Ring-shaped reactor, four by four."
design name: "MoveOnPro" -> "Muon Fusion Reactor"
design "Muon Fusion Reactor" text: "The only muon catalyst fusion reactor on the market! Always gets a great reaction." -> "Compact fusion reactor that draws a reservoir."
design name: "Core Power Station Reactor" -> "Station Reactor"
design name: "Manhattan Station Reactor" -> "Heavy Station Reactor"
design "Heavy Station Reactor" text: "A reactor sized for a station's core." -> "A heavy reactor sized for a station's core."
design name: "Notorious Station Reactor" -> "High-Yield Station Reactor"
design "High-Yield Station Reactor" text: "A reactor sized for a station's core." -> "A high-yield reactor sized for a station's core."
design name: "Vulcan Station Reactor" -> "Ring Station Reactor"
design "Ring Station Reactor" text: "A reactor sized for a station's core." -> "A ring reactor sized for a station's core."
design name: "MoveOnPro Station Reactor" -> "Muon Fusion Station Reactor"
design "Muon Fusion Station Reactor" text: "A reactor sized for a station's core." -> "A muon fusion reactor sized for a station's core."
design name: "RevvITup 2.0" -> "High-Thrust Drive"
design "High-Thrust Drive" text: "" -> "Large drive: huge thrust, little torque."
design name: "deep space burnout" -> "Burn Drive"
design "Burn Drive" text: "go go go go go/ get the drift/ you'll burn before you stop " -> "Large drive that runs very hot."
design name: "Talaria" -> "Rugged Drive"
design "Rugged Drive" text: "" -> "Two-cell working drive."
design name: "Victoire" -> "Racing Drive"
design "Racing Drive" text: "The most responsive thruster on the market, for the most irresponsible of racing pilots." -> "Feather-light two-cell drive with extreme response."
design name: "The Bat" -> "Sensor Array"
design "Sensor Array" text: "" -> "Two-by-two long-range sensor array."
design name: "not if i see you first" -> "Compact Sensor"
design "Compact Sensor" text: "[TODO]" -> "One-cell sensor."
design name: "PotaT+-" -> "Capacitor"
design "Capacitor" text: "[TODO]" -> "Stores charge for burst draw."
design name: "SafeStuff II" -> "Secure Cargo Bay"
design "Secure Cargo Bay" text: "Your stuff is safe stuffed in the new, bigger SafeStuff" -> "Three-by-three secured hold."
design name: "Store-All Enterprise Edition" -> "Large Cargo Bay"
design "Large Cargo Bay" text: "A large amount of storage" -> "Six-by-six hold."
design name: "Store-All Plus" -> "Cargo Bay"
design "Cargo Bay" text: "Take your cargo wherever your shipgo" -> "Two-by-two hold."
design name: "Store-All Plus Mini" -> "Small Cargo Bay"
design "Small Cargo Bay" text: "Take some of your cargo wherever your shipgo" -> "One-cell hold."
product of "Autocannon" by Zhestokost: "Autocannon" -> "Hammerfall"
product "Hammerfall" text: "" -> "The rounds fall steadily, and they do not stop until the work is done."
design "Autocannon" text: "" -> "Belt-fed cannon: a steady stream of rounds at long reach."
product of "Cockpit 2x2" by Lightsail: "Cockpit 2x2" -> "Wheelhouse"
product "Wheelhouse" text: "" -> "Every Lightsail captain's favourite chair. The view comes standard."
design "Cockpit 2x2" text: "" -> "Two-by-two crew cockpit."
product of "Industrial Thermostatic Heater" by Lightsail: "Industrial Thermostatic Heater" -> "Hearth"
product "Hearth" text: "" -> "A warm hold, a warm crew, and a warm welcome at the cold end of the run."
design "Industrial Thermostatic Heater" text: "" -> "Turns reactor power into heat to keep the hull warm."
product of "Medium Drive" by Lightsail: "Medium Drive" -> "Steady Haul"
product "Steady Haul" text: "" -> "Keeps you going and going and going..."
design "Medium Drive" text: "Keeps you going and going and going..." -> "Mid-sized working drive."
product of "Small Drive" by Lightsail: "Small Drive" -> "Short Hop"
product "Short Hop" text: "" -> "Dock to dock, and not a metre further than the manifest says."
design "Small Drive" text: "" -> "Small working drive."
product of "Turret Control Module" by AU: "Turret Control Module" -> "Watchtower"
product "Watchtower" text: "" -> "Point it at the dark and get back to work. It keeps watch."
design "Turret Control Module" text: "" -> "Control module for a station turret."
product of "Medium Docking Bay" by AU: "Medium Docking Bay" -> "Open Berth"
product "Open Berth" text: "" -> "Room for a friend, a customer or a tug. We don't ask which."
design "Medium Docking Bay" text: "" -> "Docking bay for mid-sized ships."
product of "Mine Launcher" by DME: "Mine Launcher" -> "parting gift"
product "parting gift" text: "Lays a drifting proximity mine that arms after two seconds." -> "we were never here/ but we left you something/ it's waiting where we were"
design "Mine Launcher" text: "" -> "Lays a drifting proximity mine that arms after two seconds."
product "Victoire" text: "" -> "The most responsive thruster on the market, for the most irresponsible of racing pilots."
product "DragOnBreath" text: "The subsidiary plight's own description already namechecks: DragOnBreath, a Lucent Media property." -> "Chain lightning has your enemies chain smoking while you enjoy a relaxing DragOnBreath vapepop, now available in 17! flavours (manufactured by DragOnBreath, a subsidary of Lucent Media)"
product "Steadfast" text: "Rated for continuous operation. "Continuous" does not imply "uninterrupted." (new text)" -> "It gets the job done, but that's all. Rated for continuous operation. "Continuous" does not imply "uninterrupted.""
product "Steadfast Station" text: "" -> "Rated for continuous operation at station scale. The fine print scales too."
product "Manhattan Station" text: "" -> "A fortress needs a furnace. This is the furnace."
product "Notorious Station" text: "" -> "The biggest reaction in the system, and everyone docked here will hear about it."
product "Vulcan Station" text: "" -> "Keeps the lights on and the forges hot."
product "MoveOnPro Station" text: "" -> "The club never closes. Neither does the reactor."
product "LRMM72" text: "" -> "Engages targets at up to seventy-two kilometres!* *In a vacuum, against a cooperative target."
product "SRMM72" text: "" -> "seventy-two reasons/ to keep your distance/ all of them leaving at once"
product "Iapyx" text: "" -> "Two cells of honest fin. Bolt it on and forget it's there."
product "Skiron" text: "" -> "When the job runs hot, run a bigger fin."
product "Talaria" text: "" -> "Takes a knock, keeps pushing. Built for crews who work for a living."
product "RevvITup 2.0" text: "" -> "Floor it. The bass drops when you do."
product "PotaT+-" text: "[TODO]" -> "Holds the charge until the drop, then lets it all go at once."
product "not if i see you first" text: "[TODO]" -> "eyes in the dark/ you were seen before you looked/ say goodnight"
product "Panopticon" authoring note stripped: "Sees everything, judges nothing. Well, mostly nothing. (new text)" -> "Sees everything, judges nothing. Well, mostly nothing."
product "ClapBack" authoring note stripped: "Your target won't clock it until the killcam. (new text)" -> "Your target won't clock it until the killcam."
product "Panopticon Prime" authoring note stripped: "The upgrade nobody at Finch will admit they needed until they had it. (new text)" -> "The upgrade nobody at Finch will admit they needed until they had it."
product "LockOn Pro" authoring note stripped: "Same promise, sharper lock. (new text)" -> "Same promise, sharper lock."
product "ClapBack Ultra" authoring note stripped: "Now with main-character energy built in. (new text)" -> "Now with main-character energy built in."
product "LockOn" authoring note stripped: "Keeps the lights on your target. (new text)" -> "Keeps the lights on your target."
product "True North" authoring note stripped: "Gets your cargo there. Getting you there is your problem. (new text)" -> "Gets your cargo there. Getting you there is your problem."
Dry run. Pass "apply" to write 82 records.
This commit builds (a data file); tests that name old design literals fail until the sweep commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entity.SchematicRows(subject) owns which weapon rows a schematic shows, each row's item and
claimed design, and its range (own ship: live; any other: the claimed design's rated range).
SchematicDisplay renders SchematicRow and decides nothing. Headless tests cover default-to-truth,
a weapon claimed as another weapon, a weapon claimed as a non-weapon, own rows, and the rebuild key.
Builds on the Shared side not yet verified at this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds the command, its rename/brand/product-text tables, and the ToolRolesByDesign
Capacitor key. The catalog and the old-name literal sweep follow in later commits;
this commit has not been built yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Update(0) checks could not fail and the 40..200 tick window was tuning. The test now asks the hostile's
PassiveInfo for the player's signature against the detection threshold after the first real tick, asserts the
hostile has no track then, pins the facing, and asserts the Track paint's Strength rises. The hostile is staged at
(1500, 3700) facing the player. The unsound comment about the player's sight is corrected.
Builds: not yet verified (verify job follows).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fitted Djinni's measured motion sits further below its reach than the generated one did (nose-on 1.69), so the
fixed 1.6 ceiling over measured motion was the old ship's number. The ceiling is the allocated reach with 20% to
spare, as the Longinus's is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The catalog tests need the applied catalog; this commit does not pass them until the next commit lands the apply output.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Longinus the reach test measures is fitted with the drive train the generated one carries
(EvLonginusDrive, which EvLonginus now extends); the Duel's guns and stores added mass the
reach ceilings were not measured with.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FlowField (ServerShared) is the flow law; Zone.Flow/FlowScroll read PlanetSettings.Flow at zone
time. The Flow block moves from DefaultEnvironment to PlanetSettings (ZoneEnvironment key 1 is
retired), VolumeSampling's frame-clock scroll accumulator and the Stardust leftover field are
deleted, and slopeFlow returns 0 for a zero gradient. Unity parity probe follows in the next
commit; this commit has not been built in Unity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Body facts R1-R3 for ruling range-ramps-not-curve: no hit path scales damage by
range, DamageCurve feeds only AI estimates and a UI chart, and 26 of 27 shipped
weapons carry one copied default curve. Options with cost, a recommendation to
delete the curve, and cut-sequence lines damage-curve-delete,
sensor-bearing-shape and consumable-effectiveness.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ReachIsWhatTheShipCanDo and TheThrustersReadLiveThrust built their ships with the
generator, whose draw follows the run's item stream and so the catalog's census; the
applied sensor catalog shifted it. They now fit the Djinni (a Talaria on each thruster
hardpoint, reactor, cockpit) and the Longinus (EvLonginus) by name. AConsumableListenerHearsAtItsMultiple
pins Entity.HearingMultiple over an active consumable's Listener.
Not yet verified.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Entity: delete _hullClaim; the hull's claim is the EquippedHull entry of _itemClaims, so ClaimOf(EquippedHull)
and ClaimedHull read one slot (hull-claim-split). Claim(HullData) writes it; a non-hull claim of the hull throws.
- RevealedWeapons orders by the subject's equipment order, not dictionary slot order (tie-order-unpinned).
- InstantWeaponData.PerRound is the one burst divisor for UpdateStats and the rated read (burst-divisor-twice).
- Tests: falling-curve, Range-span, MinRange>0 fixtures and a charged weapon in the rated tests (rated-figure-range-blind);
Combat ranks claims at the target's distance, ignores a worn target gun's live figure (live-state-unpinned),
and takes ties in equipment order.
Not yet run; may not build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 1b line leaves the cut sequence. The spec is blocked on question ew-suppressor-strength
(what the suppressor hides from: its target or only distant observers), recommended
hides-from-the-target. Probes ran on Yggdrasil from a scratch commit never pushed to origin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GameData/Aetheria.cc regenerated by `AetherDb sensor-stat-set apply` on master's catalog
(sha256 6f79cf44...); a second apply writes nothing. Binary: never hand-merge, rerun on
master's catalog if another catalog cut lands first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The catalog sold only two sensor designs (DME and Miss Terri's), so brand flavour had
nothing to vary: the command adds one Lucent and one Finch product per design. Adds
TheCatalogCarriesSensorStats. Catalog not yet regenerated; not built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hostile starts 4000 m ahead with no track of the player; stepped headlessly it
sees the player at tick 81 and paints a Track lock at tick 83. LockOnSituation pins
the staging, the absence of a paint at Update(0), and the lock's bearing and timing.
LockOn.cs.meta is provisional until the Unity compile on Starfire writes its own.
Builds: yes (not yet run; verification follows).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entity.HearingMultiple is now the largest of 1 (any active sensor) and each active
listener's Multiple. sensor-stat-set authors every sensor design's InterceptFactor
as a quality-ranged emission-discipline stat plus the Passive Array and its
products. Not yet built or run; the catalog is not yet regenerated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Imagination pass (imagination-aeth-fauna, self-2026-10-13-ag2). Target: fauna are
simulated props seen through the one gain rule, each class with a legible motion
law (ruling disguise-held-by-flying). Map: body facts B1-B14 with a cost probe on
Yggdrasil, nine draft questions, first cut fauna-krill-swarms as a draft spec,
later cuts one line each. Docs only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IGrabPresenter.Grab(GrabEvent) only; the binder's Transform and velocity resolvers go. TractorBeam
presents a running grab (aims at Started's TargetPosition, emits at Power, dark otherwise). FieldDriver
draws its tendril from GrabEvents alone with a real-time phase clock over each event's PhaseDuration and
writes no transform and destroys nothing; its grab times move to FieldTester, now the scene's stand-in sim
that owns its pickup's timeline and pose. ItemPickup keeps its label lit while held. ImpactPresenterSmoke
becomes CapabilityPresentationSmoke with a grab section; its two untracked-ship expectations follow
DrawsAt since contact-hud. Builds (Unity 6000.3.24f1 batchmode: smoke ok, scene opens, no missing script).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous commit's public RatedRange property had no Key, so MessagePack refused to build the formatter for every weapon design and the catalog failed to load (869 tests red). Marked [IgnoreMember]. The previous commit (76aeeabf) compiles but does not run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spawn takes bays (default 1) and equips that many crates before Zone.Admit. The new test fills the
first bay and expects the beam to claim and land the ore in the second, pinning ruling
beam-skips-unstorable-items over bays (Select's Any over bays). Builds; test not yet run on Yggdrasil.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signature, ActiveEmission and Reveal carry Claimed (the class the ship's EW claims for the item, the true item's data by default); Entity gains the claim slots and one write port; Contact gains Profile, derived from the ship's claimed hull once Identified; Entity.RevealedWeapons is the one classifying read for weapons. Combat and the enemy schematic read it; their reads of the target's live gear are cut. Weapons rate by their claimed design (ew-claim-weapon-measure-ruled, rated-design): WeaponData.RatedRangeDamagePerSecond runs the same per-kind formula as the live weapon. Aim, damage and physics keep the truth.
Not yet compiled or run at this commit; the Unity-only SchematicDisplay is unverified.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: Behaviors.cs keeps Union(48, PickupData) and Union(49, LaunchSuppressorData);
Zone.cs keeps the departure record on _entities with master's Visibility (emissions-heard);
ContactRevealTests.cs keeps master's removal of the emission-departure test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes finding shield-ripple-draws-on-untracked. ShieldManager already reaches
ActionGameManager (Clock), so DrawsAt stays the one owner. Unity code; compile
verified separately.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
beam-corner-claim-loop: InBeam is the one region predicate; Select claims from it and the pre-pull cancel holds the item only while it stays in it (an item at 74 along, 20 off is collected, not claimed and dropped every other tick).
full-hold-regrab-pins-and-blocks: Select skips an item no cargo bay can store whole, asked of the bay's own TryFindSpace (the test TryStore opens with), per ruling beam-skips-unstorable-items; a hold that fills mid-grab still cancels at the end.
pickup-test-gaps: smoothstep pose, spin-down while the gear is inactive, Remove freeing only the removed holder's items.
phase-ticks-not-rate-invariant: the claim is corrected in the tests; each phase fires on the first tick at or after its boundary, pinned at 1/60, 1/600 and an uneven dt cycle.
Builds; not yet run (the verify job follows).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A failing Assert on a collection of Entity prints the whole entity graph and aborted the test host, so two hand
mutants read as a crash rather than a named failing test. Builds (test-only change).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A warning lives one tick, so the end-state read could not see a paint selection wrote; a hand mutant that paints
on SetTarget survived. Builds (test-only change).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Selection is nothing the selected ship can sense: TargetedBy, TargetedByCount, their subscriptions and the music
read of them are deleted. Indicator fills, the target panel, the compass, music and the enemy schematic rows now
read CurrentEntity's ContactOf / RevealsOf / RevealedSubjects (new, the book's keys); the schematic rows are built
by one builder from the book and rebuilt when the revealed set changes. Impact hull effect and projectile hit
effects at another ship's position draw only through ActionGameManager.DrawsAt (the player's own ship or a Tracked
contact). Builds untested in Unity at this commit; tests NothingReadsSelection and RevealedSubjectsListsTheBook.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by the mutation table: two mutants read 'Test Run Aborted' instead of naming the failing test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by the mutation table: a mutant that never claims hung the test host, and a mutant that doubled the
range survived because the observation landed between two attempts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The old pin (d4a27def) is exactly what this tree leaves with EquipPickup's call removed (run: passes), so the
new values differ from it only by the one product draw each generated ship makes for its beam.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tests only; production unchanged. Fixture gear SuppressorBright (Factor 3) and SuppressorNegative (Factor -1).
Maps Soul findings clamp-unpinned and rated-boundary-unpinned. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AetherDb pickup-catalog apply on the previous catalog (5efc5225...): result 1a2cb55b... (12730143 bytes), the
Tractor Beam's price 0 -> 25000; a second dry run printed "already authored", dangling reports zero. The ship
generator now requires a sold tractor beam, so the five synthetic catalogs that generate ships (LoadoutTests,
RunSaveTests, FireControlCut6cTests, TargetingSystemTests, ConsumableSupplyTests) each sell one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by the full suite on the previous catalog: every generated ship threw 'no compatible tractor beam'.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pins the cancel rules (inactive gear, out of range before the pull and not after it), TryHold refusing a held
item, the next-item selection, and the held pose as a function of progress.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Applied on Yggdrasil from the base catalog (sha256 ce5c8be5...), result 5efc5225... (12730141 bytes); a second
dry run printed "already authored" and dangling reports zero. Builds; PickupGenerationTests needs this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7e0b950d builds; its reveal test failed on a KeyNotFound for the control gun after later ticks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GenerateShipLoadout equips one gear whose behaviours include PickupData after the shared outfitting
(stations and turrets get none). The pickup-catalog command (retired in the next commit after apply) edits
the existing Tractor Beam. PickupTests: two fixes found by group A's run (equip is refused once a ship is
deployed, and the freeing tick takes one step). PickupGenerationTests needs the applied catalog and is
red until the catalog commit; the catalog itself is not in this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LaunchSuppressorData (union key 49; key 48 is held by cut loot-2 on eureka/aetheria-release-loot-2)
and LaunchSuppressor: Entity.SetSignature folds each light signature through the equipped
suppressors in equipment order. Only LaunchFlash is scaled, only while the item is Active and at
or below its rated temperature; each pays (removed x HeatPerUnit) through Behavior.AddHeat.
Fixture gear and LaunchSuppressorTests added. Not yet built or run (verify job follows).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GrabEvent carries a FloatingBodyId; Zone gains TryHold/SetHeldProgress/Free and places a held item instead of
integrating it; Entity.TractorPower is derived from Pickup.Power and TractorCommand is the pilot's input
(ActionGameManager writes it undamped, the Time.deltaTime ramp is deleted). Binder and FieldTester change the
delegate type only. PickupTests and a save round-trip pin for the player's zone index.
Group A builds; no beam is on any generated ship until group B.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Imagination imagination-aeth-loot-2's map edit, ported from the operator's
checkout (where it was written against aac2cf91) onto master. loot-2 r3 is
mapped on loot-1-present and alone restores pickup in play.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous push (07854ed5) came from `git pull --rebase`, which linearized the merge and dropped the
adaptation of master's ContactTests and LootTests to the deleted TargetArmorInfoThreshold, so master did not
compile its tests. This commit joins the reveals branch history and sets the tree to the verified merge
(c7edbf0b: Yggdrasil full suite 1624 passed, 0 failed) plus master's two docs-only commits since.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Master's feedback tests aimed at a marker the shooter's book never held; the reveals
rule (book holds the item, fitted, not destroyed) refuses that. Same fixture step the
FireControlCut12 tests use (RecordReveal via LaunchFlash).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first form let the Combat-side IsRevealed check be removed unseen,
because TrySelectTargetItem refuses an unrevealed pick anyway. A destroyed gun
left in the raw aim field distinguishes Combat's own choice. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IsRevealed is now the observer's book (Entity.HasReveal) plus the live filter
(still on its ship, not IsDestroyed). The tier ladder and
GameplaySettings.TargetArmorInfoThreshold are deleted, with the 19 fixture
initializers and the Settings.asset line. Fixtures that aim at an item record
its reveal through RecordReveal; Aetheria.Shared exposes internals to
Aetheria.Shared.Tests for that. Unbuilt and untested at this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Target document for the proposed aetheria-ew campaign (rulings ew-is-envelope-shaping,
ew-owns-ambiguity-and-decoys). No cut specified.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflict: ContactRevealTests Locker fixture. Kept the branch's Locker(name, interceptFactor) helper and added master's five munition parts to it (as master's Locker carries them since missile-stats).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Builds: Unity batchmode compile green (smoke run unity-smoke5 compiled and passed at this commit).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ZoneRenderer draws one ItemPickup per FloatingItem (load, ObserveAdd, ObserveRemove, ClearZone); ItemPickup.Bind places it
from the sim body and spins it for show. EntityInstance's drop loops, ShieldManager's pickup branch (the whole
OnCollisionEnter, which did nothing else), ZoneRenderer.DropItem/DestroyLoot/_loot, TractorBeam's pull, and
GameSettings' three loot fields are deleted; WireFloatingItems stripped GridObject from the five pickup prefabs and
reassigned ZoneRenderer's four pickup fields in ARPG.unity; FloatingItemsDrawnSmoke (play session, batchmode) pins the
lowering. GridObject and TimedDestroy were deleted in fa911f2d, so fa911f2d and 2565e2c5 do not build in Unity.
Assets/Resources/Settings.asset keeps three stale serialized GameSettings fields Unity ignores (LFS object, not rewritten).
Builds: Unity batchmode compile green at this commit; dotnet green at 2565e2c5.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first form let the Combat-side IsRevealed check be removed unseen,
because TrySelectTargetItem refuses an unrevealed pick anyway. A destroyed gun
left in the raw aim field distinguishes Combat's own choice. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Builds: with fa911f2d this is the tests project at the head of the Zone encapsulation; fa911f2d alone does not build
(FireControlCut8Tests still called the removed Entities.Add).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three tests failed at 8be15c6a: TheBeamWidthIsKey12OfUnion5 read a 13-slot sensor record (14 now, key 13 added);
TheInterceptFactorKeys serialized a float4 guidance curve to JSON; ASearchPaintFromAPingerThatHasLeftCreatesNoReveal
heard the ping while its pinger stood in the zone. The paint tests (revealed-unseen, search-paint reveal, departed
pinger and departed locker) now observe with no active sensor so the reveal they pin is the paint's alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entities is a MemberView with no mutating member; Admit installs the death watch with the
membership and refuses a member already present, Remove takes it away with the membership.
The Reset and duplicate-Add bypass paths of the death watch cannot be spelled any more.
Tests move to Admit/Remove; Admit also activates, so the explicit Activate calls that followed
a raw Add are dropped. Unity call sites route through Zone.Remove.
Builds: unverified at this commit (verified in the job at the head).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The merged tree needs this: loot-1 added the SignatureOn reader after the cut's base.
Does not build without it; builds with it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IsRevealed is now the observer's book (Entity.HasReveal) plus the live filter
(still on its ship, not IsDestroyed). The tier ladder and
GameplaySettings.TargetArmorInfoThreshold are deleted, with the 19 fixture
initializers and the Settings.asset line. Fixtures that aim at an item record
its reveal through RecordReveal; Aetheria.Shared exposes internals to
Aetheria.Shared.Tests for that. Unbuilt and untested at this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow-up ping-signature-test-gaps (departure-hull-light-unpinned, guard-null-item-unpinned,
ping-test-never-asserts-it-fired). Tests only; unverified until the suite runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entity.Emit posts an ActiveEmission (item, range / InterceptFactor) from Sensor's sweep and LockWeapon's
Track paint; Sense hears every other ship's emissions within HearRange x HearingMultiple x obscuration
through RecordReveal, bearing only, feeding no track. SensorData key 13 and LockWeaponData key 34 carry
InterceptFactor (absent reads 1). Unverified at this commit: the headless suite has not run yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Orphan images holding the pair's names are freed at the commit instead of forcing .001; a bake never repoints a slot Source shares through mesh data; the operator's execute is the only place a bake error becomes report text (Refusal marks the module's own fixed text, everything else goes to the console); the copied-material count is asserted. Builds (Blender add-on, no compile step).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Master's Read returns (Hull, Visual, Products); the branch's row-roundtrip
test deconstructed two. Mechanical; builds on the merged tree (the merge
commit before this does not build the test project).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Shared material (shared-material-source-rewritten): a target slot holding a material a Source mesh uses is given a copy before the bake; a replaced image is removed only when unused.
UV0 (bake-follows-active-uv-not-uv0): every target's active UV layer is held at 0 for the bake and put back.
Relief (normal-tolerance-accepts-near-flat): smoke_bake bounds the normal's median tilt at 3 degrees (copy of Source 0.32, hull 16.59).
Pair names (pair-names-suffixed): a name held by an image the bake would not free refuses with fixed text.
Fixed text (refusal-passes-plain-runtimeerror-text): any foreign exception inside the bake becomes BakeFailed with the console pointer.
All builds; smoke_bake.py passes on Blender 5.2.2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruling schematic-and-model-are-separate-spaces: no model dimension from schematic
cells; sockets fix shape and proportion with scale per mount; radiator panels read
a region marked on the model.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruling hardpoint-work-held-for-walkthrough: mounts are named and hardpoint data
addresses them by name; the add-on-reads-cells premise is withdrawn.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rulings hardpoints-and-floating-greebles, hardpoint-sizes-are-footprint-palette,
radiator-panels-molded-by-geonodes and caliber-is-presentation (2026-10-13).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by hand mutants that survived: a failed bake restored a Base Color link only when a prior non-image link
existed (the smoke now has one), the offenders were already the selection, the bake's RuntimeError text was not
checked, and the cage/ray windows allowed a constant offset. All commits build; no C# touched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WatchDeath returns when the entity already holds a watch, so a duplicate
Entities.Add neither throws nor leaves a second live subscription;
Entities.Clear (ObserveReset) disposes every watch. Adds
AMemberAddedTwiceStillDropsEachItemOnce, AClearedZoneDropsNothingWhenAFormerMemberDies
(finding duplicate-add-leaks-second-watch) and
APickUpOfExactlyTheRoomLeftFitsAndOneMoreChangesNothing (finding
pickup-room-arithmetic-unpinned, from Soul's boundary probe). Unverified
until the Yggdrasil job; no commit here is known not to build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Source gets a linked Metallic and a Transmission Weight of 1, the bake is forced to fail at the
wiring and replace steps and with meshes that have no material, and the cage extrusion and max ray
distance preferences must move the normal map. All commits build; no C# touched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds aetheria.bake_from_source: bakes the Source meshes onto the selected render meshes as
'<ship id>.albedo' (sRGB) and '<ship id>.normal' (Non-Color), packed, wired into every material
(a shared '<ship id>.hull' material for a mesh with none). One routine serves both maps and every
target. The Source's Metallic and Transmission Weight are neutralised for the bake (the glTF importer
sets Metallic 1.0, which bakes black); engine, samples, device, selection and Source are restored in
a finally, and a failure removes everything made and puts every changed link back. Three bake
preferences, a panel button, the tripo-hull.glb LFS fixture and smoke_bake.py (4 passes).
_refuse_loose_meshes now shares the select-offenders refusal with the bake.
All commits build; no C# touched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ping test never pinged (a sensor's cooldown opens on its first tick), so a restored ping bump survived. ADepartureCountsLightOnEveryChannel pins the departure to the subject's whole Visibility. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Builds (tests only). The previous commit unset the gun's joint, which bind then refused against the model.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Master's catalog (2e2d927a, with missile-stats) plus the five smoke weapons the command authors; not a hand merge of the two branches' binaries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Catalog taken from master (2e2d927a); the branch's catalog change is replayed by the next commit. Scenarios.cs: both lists unioned.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Replaces the tests that pinned nested acceptance with refusal tests plus disjoint controls; gun arcs 10..360 against rows
of 1..5 thrusters; 360.01 and 360.5 against the thruster cap; a plan test that a thruster row adds no gun rig.
Findings nested-actuators-accepted and gun-conflict-pinned-at-fixture-sizes. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RigChains.Actuates/Drives/Actuator are the one definition of actuator kinds; ValidateRig, BindRig and ShipModPlan consume
it, and the hand-derived gun-versus-row check and the chain stop are deleted. A nested actuator is now refused, as ruling
one-joint-one-actuator says. Findings nested-actuators-accepted and actuator-kinds-spelled-in-four-places.
Builds; the tests of the previous batch that pinned nesting are red until the next commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mechanical merge adaptation: contact-reveals landed on master after missile-stats' base, and its fixture built a LauncherData without the
munition parts ValidateMunitionParts now demands (63 failures at Upsert). Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds the Impact Feedback object (TinyExplosion, the weapons' own hit effect, as the pooled hull effect) and a
CinemachineImpulseListener on the follow camera. Additions only (188 lines, 0 removed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ImpactPresenter (bound on every EntityInstance by SetEntity through the one CapabilityPresentationBinder) renders
feedback-1's absorbs: Feedback.Cue decides intensity and shake, DrawAhead.Carried the drawn point; a hull absorb
spawns ImpactFeedback's pooled effect under the instance, and a cue with shake raises a Cinemachine impulse.
ShieldManager ripples on a Shield-layer absorb. MainMenu gains a Screen Shake slider that persists on Back.
Editor: WireImpactFeedback (scene wiring, scene diff in the next commit) and ImpactPresenterSmoke.
Unity batchmode compile 0 error CS; ImpactPresenterSmoke exit 0. The scene is not yet wired in this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mount fires inside an arc about the shooter's facing; the first version fired backwards and found no hit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A direct absorb's heading and damage type are the shot's, on both layers (non-Kinetic weapon, both heading
components negative); a blast absorb carries the blast's damage type and the target's height on both layers;
DrawAhead.Impact sits at the hull's height. Tests only; every commit builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SetSignature refuses Emission; Sensor.PingVisibility (key 7) is retired and
Ping() writes no signature; Sense's Emission skip, the departure subtraction
and SignatureOn are deleted. Tests: SetSignatureRefusesEmission,
APingNoLongerBrightensThePinger; fixtures drop PingVisibility.
Not yet built or tested at this commit (verify job follows).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EquippedCargoBay.TryStore(SimpleCommodity) now returns false before touching anything when
TryFindSpace says the whole quantity does not fit (its documented contract: true only when ALL
is stored), so Zone.TryPickUp keeps "first accepting bay, otherwise nothing changes" with no
rollback in Zone. Tests: refused pickup of a stackable item changes nothing; a stack plus free
cell stores whole; first accepting bay; headings cover the circle; the loot stream is not the
mine stream; dock/undock and jump drop once and only in the zone the ship is in.
Untested on this commit: not yet built (verify job follows).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One conflict, Zone.cs death subscription against contact-reveals' _departures insert: kept both.
The resolution already routes the death subscription through WatchDeath/UnwatchDeath (next
commit's fix target is the same line, so this commit resolves it in that shape).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two-minute run let a hostile placed ten times farther still arrive; the hostile closes in about 15 s from 700 m (about 50 m/s),
so thirty seconds separates the arena's geometry from a mutant of it. Both controls fire within a second.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Their specs were withdrawn under map-only-what-runs once their deferred
findings moved to follow-ups.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test compares type and message with SingleFileMessagePackBackingStore.pull_all on the same
file; the add-on's shape refusal (also a ValueError) no longer passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ShipAuthoringStore.ValidateLoaded is the one composition of hull Validate and ValidateProducts;
Read and the draft branch of ship-authoring validate both call it (ValidateProducts is private).
Tests: a draft with an unnamed hull is refused; blank product name and key collision with the
offending product not first (both makers in turn); a negative hull price refused.
Builds untested here; the verify job runs at the end.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- finding joint-moved-by-two-actuators (ruling one-joint-one-actuator): RigChains.Driven names every actuator's joint (weapon-mount and
thruster-emitter anchors), so YawReach's chain stops at a thruster row's joint as it does at a gun's; Validate refuses a gun and a
thruster row on one joint. The test that pinned the mixed joint as accepted is replaced.
- finding row-of-one-arc-uncapped: Validate refuses any Thruster hardpoint with FiringArc above 360, row or not; the named-row check keeps only
the above-0 rule.
- finding row-rules-pinned-at-fixture-sizes: tests at row sizes 3 to 5, arcs 10 to 360, hair-width arc gaps, every non-thruster type, case-distinct rows.
All three findings land in this one commit (the edits share hunks in ShipAuthoring.cs and ShipRigTests.cs). Not yet built here; verified on Yggdrasil after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Builds. The previous commit's test survived the hand mutant munition-thruster-joins-ship-thrusters because the ship was never stepped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two fused rounds draw 4 energy a shot, so the Smoke Test Hull's one reactor carries the Fused Rounds fit and the rounds fire
(sha256 b60cefa2). New tests: every weapon aboard fires in the stepped Fused Rounds arena with the DeathCluster as the control,
the round's flight is seconds and bursts on the first hull, the DeathCluster kills the fragile hull before the round lands (A.3),
the AI holds fire against its two controls, New Game lists the three arenas, and the command's replay writes the shipped records.
Not run at this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A stepped Fused Rounds arena showed no round fired from any weapon aboard, the DeathCluster included: the Smoke Test Hull's one
2x2 reactor (Core Power, charge 100) cannot carry two Spectra clones at 20-40 energy a shot, overloads, reaches 445 K within a
second and goes offline. The catalog is regenerated in the next commit; this commit's command no longer matches the committed
catalog, so SmokeRoundsCommandTests/stepped tests are not meaningful at this commit alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Test-only; builds. Ports Soul's feedback-1 s2 probes as behavioural tests that
observe where each rule is decided (the published AbsorbEvent, the handler's
view of hull and shield, Feedback.Cue, DrawAhead.Carried). The blast shield
Magnitude is read against an unshielded twin's totalShare so a scale mutant
fails. Covers findings shield-layer-fields-unpinned, blast-order-unpinned,
height-fixtures-at-zero and cue-saturation-edges-unpinned.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Station() activates the entity and TryEquip refuses while active; the second Crate at (2,2) failed for that reason, not a hull rule. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous commit's ContactTests did not build a Mid hull, so ten tests failed on the missing hull (full run at
806641ae: 1481 passed, 10 failed). The commit builds; those ten tests are red until this one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruling ship-bounce-restitution-setting: j = -(1+e)(v_rel.n)/(1/m1+1/m2), applied only while closing. The old
exchange formula (PostCollisionVelocity) is deleted. ContactTests now pin momentum, frame shift, restitution,
no impulse while separating, slow closing, tangent of both bodies, inactive hulls, the contact point radius
and the three-body pile. Not yet built or run; verification follows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first version left the package's GLB in place, so validate judged records and model through ReadPackage and the test failed in the mutation baseline. Test-only; builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Findings of verdict cut-ships-addon-guards.s1: guard-not-import (the version now comes from the checkout the imported
cultcache_py lives in, one owner, no pyproject-beside-a-path), prefix-compare-survives (0.3.0.1, 0.3.0rc1, 0.3.00
refused), cap-boundary-unpinned and group-restore-unpinned (smoke_package texture_boundary_cases: at the cap, one over,
height-long, non-square rounding, file/packed/node-group images, failed export restores). A copy whose scale raises is
removed. All commits build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ship_cc.read lets cultcache-py refuse a zero-byte file (CultLib pins that); the test expects the refusal. New behavioural tests: ValidateJudgesProductsOnADraftAndInspectListsThem (through ShipAuthoringCommands.Run), AProductKeyAlreadyInTheCatalogExcludesThePackage, and price 1 in ASoldPackageHullNeedsAPrice. Tests only plus the add-on deletion; all commits build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With equal masses the first exchange stops the visitor and a second contact with the docked hull cannot show.
Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A docked hull shares its carrier's velocity, so the pair alone never closes; a visitor does expose a second contact.
Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pair moved 0.1 each before the contact step and so closed inside the 14.14 bound; start them 15 apart.
Builds; the first commit's only red test was this one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Imagination imagination-aeth-refresh-visibility, session self-2026-10-11-ag2. The model page
amendments are stated in this section; folding them into the Visibility rework rows is owed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moving the target after Fire never changed the cell: the lateral is drawn in the hull's frame about the aimed point. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contacts.Step exchanges normal velocity between touching hull discs (PostCollisionVelocity moved verbatim
from ShieldManager) and publishes Zone.Contacted; ShieldManager presents it; HullCollider.OnCollisionEnter
and ShieldManager's ship-on-ship branch are deleted. Not yet built or tested (verification follows).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The untested-cultcache test now refuses 0.4.0, 0.3.1 and 0.3 as well as 9.9.9 (a major-only comparison survived).
smoke_package's hull map is 4096x1000, so the scaled size (2048x500, 512x125) is not what a clamp of both sides or of
the other axis would give. Builds: yes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Passes 7 and 8 run mirror_matrix on ships whose grids are 14 (even) and 15 (odd) wide; the expected twin cells are
grid_w - a - w computed in the matrix itself. Pass 6 (5 wide) now runs through the same matrix_pass function. This is
the commit that adds the cases for follow-up mirror-wide-grid-test. Builds: yes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_export_glb points each Image Texture node of the exported objects' materials whose image is longer than the Max texture
preference (default 2048) at a scaled copy (longest side at the cap, aspect kept) and restores the originals and removes
the copies in its finally block, so the .blend's images are never modified. smoke_package.py reads the GLB's PNG
headers: a 4096x1024 map exports at 2048x512 and at 512x128 under a 512 cap, a 256x256 map exports as it is, the .blend
images keep their size and no copy is left in bpy.data.images. Builds: yes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_libraries reads [project].version from the pyproject.toml beside the cultcache-py source before importing it and raises
ValueError naming the version found and the packages path when it differs from TESTED_CULTCACHE_PY (0.3.0) or the file is
missing. Builds: yes. test_refuses_a_file_with_no_record fails as before (cultcache-py unpack error on a 0-byte file).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Survivor of the top-speed-any-behavior-counts hand mutant: the cleared fixture hull held no behavior at all. Test-only; builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The committed font is not rebuilt yet, so test_fe0f_twins.py's first test is red against it; the font lands next. Builds nothing in Unity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OpacityAt is already saturated to 0..1 by the sim, so a 0.3 vent reads thinner than a full
cloud and nothing above full draws thinner. Builds (Unity compile run after).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keycaps and the other body-font-based sequences now collapse: 25 failures become 13. The 13 FE0F+ZWJ sequences still show three pieces. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test asked a second cache for the handle of an object loaded by the first (identity lookup, no handle); it now asks the reopened cache for its product. cultcache-py raises an Unpack error on a zero-byte file, so ship_cc.read names the empty file through its own shape refusal. Both commits build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every prefab and scene was re-serialized by Unity 6, so each file also carries the editor's own migrations (m_RootOrder, m_ConstrainProportionsScale, m_enableWordWrapping to m_TextWrappingMode, name quoting). Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hull_grid.mirror_position gives the twin Position as grid_w - a - w, so an even-width footprint mirrors to its true
reflection and the cell lookup's tie rule no longer decides a mirror; the twin's object stands in the cell that Position
puts it in, so Package derives the same Position back. test_hull_grid pins the reflection on odd and even grids and
rows of 4 or more cells; smoke_mounts runs footprint widths 1 to 4 at every cell of an even and an odd grid, the
boundary tie, the armour and arc copy, the muzzle's local X, the centre-line threshold, the grid edge and the parent
check, and keeps a mirrored pair of two-cell footprints through Package and the validator. Every commit builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LockWarningTests builds launcher designs, which the new gate (ValidateMunitionParts) refuses without parts. The golden test
selects the role's Quality term among the stat's heat and durability terms, and the item-read test settles the ship at the
item's optimal temperature so only role quality moves the stat.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds tools/emoji/emoji-sequences.txt (2760 fully-qualified multi-code-point sequences from the pinned emoji-test.txt), written by fetch_emoji_font.py and recorded in the lock. Not compiled yet (first Unity pass follows); the asset changes land in a later commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The five munition designs are written by the post-cut schema, so keys 29-31 and 24-26 are gone from the file. The catalog loads
through AetherDb (census) and every munition design carries its five parts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
smoke_package's hand-tagged ship has a weapon-mount object, no Ship Root and no loaded
buffer; the first mounts commit refused it. A ship with no frame has no grid to derive
from, so Package leaves its rows alone as before. A framed ship with a mount and an
unloaded buffer is refused with 'Load Layout first'. All commits build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Thrust, DodgeFrequency and MissileVelocity are gone from LauncherData (keys 29-31) and GuidedWeaponData (keys 24-26), keys
reserved. GuidedProjectileManager flies the round from MunitionParts.Of: Acceleration, MaxSpeed and EvasionFrequency,
evaluated through the item. munition-parts-migrate is deleted with the fields it read.
The shipped catalog still carries the dead keys in this commit; the next commit re-saves it. The Unity assembly was not compiled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Zone.Release is the one adder, StepFloatingItems and TryPickUp the only removers; DropLoot runs from the one
death subscription before the entity leaves the zone. Three C# default settings. Unity's drop path is untouched
(loot-1-present deletes it). Not yet built or run at this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GT 3K, LRMM72, SRMM72, pswarm, scorched void policy. Acceleration, Lateral and MaxSpeed copy Thrust and MissileVelocity with
their thruster role; EvasionFrequency is DodgeFrequency; the rest are authored flat.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MunitionHullData, MunitionThrusterData, MunitionSeekerData, MunitionControlData (with Cognition) and MunitionFuelData
(Behaviors union 43-47), read through MunitionParts.Of. StatValidation.ValidateMunitionParts refuses a launcher design
without exactly one of each and any other design carrying one, at catalog load and Upsert. AetherDb gains
munition-parts-migrate; LauncherRoles names the part's thruster stats by type and field (Range and Visibility also name
the seeker's and plume's flat stats, which RolesBackfill must not range).
Does not pass tests on its own: the shipped catalog is migrated in the next commit, and until then it fails the new gate at load.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbsorbEvent gains AbsorbLayer. Entity.Capabilities, four publish sites in ApplyDirectHit and Detonate (each before the damage it reports), Feedback.Cue, DrawAhead.Carried (Impact delegates), PlayerGraphicsSettings.ScreenShake. Not yet built or tested; tests follow in the next commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ShipAuthoringStore.Load/Read return (Hull, Visual, Products); Open homes the third kind;
WriteProduct/DeleteProduct are the only product writers; ValidateProducts and the ship
top-speed rule; compose checks makers, keys and product names; ship-authoring product and
price commands. Unbuilt until the verify job (Python, pin test and docs follow in the next commit).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces "The missile's stats" paragraph and appends the refresh's premise
check, body facts, rationale and downstream notes (Imagination
imagination-aeth-refresh-missile-stats, session self-2026-10-11-ag2).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wired through the Editor in batchmode (component, root pose reset, field assignment); the scene diff is the one
CloudPrefab line, the Editor's unrelated field churn reverted. Builds (Unity compile 0 error CS).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hull_grid gains mounted_position, facing_yaw, mirror_rotation and mirror_footprint. The
mount helpers edit the layout buffer and create a tagged weapon-mount object with its row
as a pair; Package derives each mounted row's Position from its object and each mount's yaw
from its row's Rotation, then saves the buffer through _save_layout. The panel gets a
Mounts box and greys a mounted row's x and y. smoke_mounts.py pins the gunboat flank,
derive and remove paths. All commits build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Imagination imagination-aeth-refresh-package-product, session self-2026-10-11-ag2:
ShipAuthoringStore registers no product type, the product writer, sequencing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A weapon's reach and a sensor's gain are zero until the ship's stats settle, so at the first instant no rock is in reach.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Thruster hardpoints state a gimbal arc in FiringArc and join a row by Row (key 7);
the thruster design carries its slew (key 6). The judge refuses a Row on a
non-thruster, rows whose members disagree on Rotation or FiringArc, and a fixed
thruster, a mixed or split row, or a joint too short for the arc on a joint. Bind
treats a thruster emitter's joint as it treats a gun mount's. Not yet verified:
this commit has not been built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec cut-scenarios-belt.r1. Scenarios/Belt.cs stages the Longinus at the ring's edge nearest the origin facing along it; BeltConditions in
BeltScenarioTests.cs pins it. Built and tested through ygg-verify in the next commit's verification.
The catalog gains Smoke Proximity, Smoke Delayed, Smoke Refused, Smoke Refused Beam and Smoke Refused Charge (AetherDb smoke-rounds apply), none with a product. Seeds 108, 109 and 110 stage the demo's cast with the Smoke Test Hull; SmokeRoundsScenarioTests pins their conditions and that no smoke weapon is offered. Design and arenas land together so the census never sees an uncarried design. Not yet run: the verification job.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous commit (93d5a0c6) does not build: two string literals in SmokeRoundsCatalog carry a raw line break. This commit builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ZoneRenderer loads and subscribes Zone.Clouds like mines and destroys a bank on removal; VapourCloudInstance
places a Fog Patch brush at DrawAhead.Position(cloud.Body, Lead) and thins its _Depth with OpacityAt. The prefab
and scene wiring follow in the next commit; this one compiles but CloudPrefab is unassigned.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Smoke Proximity, Smoke Delayed and Smoke Refused are Spectra clones with the operator's fuse values; Smoke Refused Beam is a constant weapon and Smoke Refused Charge a charged one cloned from ChargeBlast SG, each fused with a Range below its BlastRadius. No products. SmokeRoundsCommandTests covers dry run, apply and the no-op second apply.
The catalog and the arenas that carry the designs follow in the next commit. This commit builds; its command test needs no catalog change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Restores the Identified item-end pass and its tests (AnIdentifiedObserverSeesGearDestroyed, the flashing-gun tick case,
the Identified part of SenseAllocatesNothingOnceWarm) that 49ace47f held for this batch, and extends
AnActIsRevealedOnItsOwnTickWhateverTheListOrder to a moving subject (two velocities, both observer orders).
Builds: all commits build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GenerationOffersExactlyTheSoldGearSomeHullCanFit compares against LoadoutGenerator.SoldHulls; GearNoHullCanFitIsOfferedOnlyOnceAHullTakesIt
gives its scratch hull a product, since a hull no product sells is no home. The previous commit (2a943370) left both red (found by the
full run); this one is the head. Commit 2a943370 does not pass the full suite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LoadoutGenerator.SoldHulls is the one definition of the hulls HasHome considers (a hull some product sells); AvailableProducts and the
tests read it, so the product-less test hull gives no gear a home. TheSoldHardpointGearWithNoHomeToday describes production again.
SmokeHullCommandTests pins each added hardpoint's firing arc, armour and shape; ArcsFlanks asserts the flank hulls abeam from the
player's facing within 5 degrees; Arcs' Turret moves to (600,300). Not yet built or run at this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RecordReveal gates on zone membership and EndReveals is the only way out; the observer's Zone.Entities handler no longer wipes the book and PublishInbox no longer decides membership. Zone records each removal as a one-tick Departure that each observer's Sense judges under the light-act gate; a departed subject's entries are pruned once older than BearingMemory. Unverified build at this commit (first test run follows).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Amends the model page to Defaulted ruling reveal-ends-on-perception (Soul
findings cut-contact-reveals.s2.perceived-destruction-never-ends-reveal and
unseen-departure-clears-contact): zone departure no longer empties books.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Output of `AetherDb smoke-hull apply` on this tree's base catalog, run on Yggdrasil: one HullData,
Smoke Test Hull, 17 hardpoints, no product.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 1x1 Ballistic side mount tripped IsFilledByAgreesWithACellOracle (Shape.FitsWithin never places a
design whose only cell is not its origin, so Takes and the oracle disagree on a 1x1 hardpoint); the
side mounts are 2x2 now, which needs two columns of margin on each side, so every Longinus cell and
hardpoint moves right by two. The two catalog-wide tests that listed every hull now list sold hulls.
This commit does not pass the suite: GameData/Aetheria.cc still holds the previous hull until the
next commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The catalog is the output of `AetherDb smoke-hull apply` on this tree's catalog, run on Yggdrasil.
It adds one HullData, 17 hardpoints (the Longinus's 13 and four new ones), and nothing else.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AetherDb smoke-hull [apply] adds a product-less Longinus copy with a 2x2 Energy and a 2x2 Ballistic
hardpoint past the bow and a 1x1 side mount on each flank. Weapon Feel (seed 107) stages it with
ChargeBlast SG, Autocannon and FastBlast+-; Arcs flies it with a mount on each flank and a Port and
Starboard hull. This commit does not build a passing suite: the scenarios name the hull and
GameData/Aetheria.cc does not hold it until the next commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Amends the model page to Defaulted ruling contact-reads-book-aim-reads-live
(Soul finding cut-contact-reveals.s1.unseen-destruction-leaks-into-contact).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- RevealsOf returns the book and nothing the subject's gear or Durability says (ruling contact-reads-book-aim-reads-live); FireControl.IsRevealed stays the live aiming read.
- PublishInbox records a paint's reveal only for an emitter still in the zone.
- Sense moves from Entity.Update to a Zone.Update phase after all entities have updated, so reveals do not depend on list order.
- ChunkInfo calls PassiveInfo(position, brightness) in the original multiplication order (PassiveInfoPerSignature becomes PassiveInfo with the signature argument).
- Tests: unseen destruction, ghost paint, Search paint reveal, no self-sense, quiet tracked ship levels, list-order independence.
Not built or run locally; verified on Yggdrasil after the next commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Crossing (seed 112, demo cast): the Arcs Longinus fit with plight and FastBlast+- under the Fire Control Array, a piloted
hostile Djinni crossing the bow inside 400 m, an unpiloted neutral one at 80% of plight's Range. Djinni Shakedown gains
bare Longinus targets Ahead (inside the generated Djinni's ballistic mount arc) and Abeam (outside it). CrossingConditions
and DjinniShakedownTargets read what each arena staged. Not yet verified on a full-suite run at this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ScenarioStage.Cargo resolves one name across equippable and consumable
designs; a consumable is built from its first available product in key
order. Pursuit (seed 111, demo cast) lists last in Scenarios.Tests.
All commits build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Body facts SC1-SC16, the table of every operator check against the
scenario that stages it, and the cut order for scenarios-smoke r4,
smoke-rounds, pursuit, crossing, belt and lock-on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The observer's Entity owns a reveal book keyed (subject, item). Sense records a
light act when its strength times PassiveInfoPerSignature clears the detection
threshold; PublishInbox records an emitter whose paint arrived. ReceivePaint takes
the emitter and its item. ContactOf derives Detected, Tracked, Classified and
Identified from track, reveals, the zone clock and GameplaySettings.BearingMemory.
ChunkInfo's arithmetic moves into PassiveInfoPerSignature; ActiveSensors fills one
buffer instead of allocating per call.
Not yet built or tested: verify follows with the tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The idle _reached.Clear() at the end of Execute is deleted: Ping() is the one per-ping reset
(ASecondPingCrossesTheSameShipAgain). A sweeping tick drops reached subjects that left the zone,
and a ping ends when its pinger is in another zone than it began in. New tests: the ending step's
band, every return credited, an inactive ally posted none, Deactivate clearing the inbox, a ping
ending on a zone change, and a leave-and-return subject crossed again.
Not yet verified: no verify job has run on this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opacity falls linearly over the cloud's lifetime and the receive leg takes a square root, so a thousand-second cloud turned an opaque leg's 3e-4 into a .004 credit. The finished ping's radius is 300 within float rounding, not above it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cloud fades linearly over its lifetime, so an opaque cloud's two legs read 3e-5 a few ticks in. The zero cases compare within a tolerance.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deletes the stateless interval crossing (_previousRadius, PreviousRadius, Crossing) and the allied-pinger scan over
other ships' Sensors. The pinging Sensor remembers the subjects its wavefront has reached (_reached), in the beam
or not, and a subject's first sweeping tick inside the radius is its one crossing. On it the pinger paints, credits
itself and posts a PingReturn to each ally; Entity publishes returns with paints (PublishPaints is now
PublishInbox) and the ally's own sensors credit them for one tick at the geometric mean range, inside
Rt x Rr < PingRange^2, through the geometric mean of both legs' obscuration (ruling ally-echo-bistatic-oval).
This commit builds and replaces the sensor the previous commit's tests were written against.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eight new tests (moving subjects, list order, stopped illuminator, bistatic oval, cloud legs, drift behind the
front, late arrival, BeamWidth key 12 and its JSON name) and the ally credit of AnAllyHearsTheReturnSilently
restated as bistatic (.21213). This commit builds; the moving-subject, list-order, stopped-illuminator, oval, cloud
and ally-echo tests are expected to fail against the Sensor of c1d5487b, which the next commit replaces.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Map R2/R3 rewritten after Soul failed paint-modes h1: the pinging sensor
keeps a per-ping reached set and posts PingReturn to allies through the
zone inbox; ally credit gated by the bistatic oval (R_t * R_r < range^2).
Research section 7: bistatic radar, cooperative engagement, game gaps.
Serves cut_spec paint-modes r3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LockWarning becomes PaintWarning with a PaintMode (Search for a ping sweep, Track for a lock);
Entity.IncomingLocks becomes IncomingPaints. Sensor replaces the _pingedEntities set with a stateless
crossing rule over the previous and current wavefront radius, limited to a new BeamWidth stat (key 12,
absent reads 360), and credits allied pings from the receiving sensor's own sensitivity and distance.
The HUD lock indicator follows Track paints only. Folds in the radiator sampling-instant pin.
Unverified at this commit (first build still to run).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Findings decay-channel-baseline-vacuous (expected DrivePlume, Emission, LaunchFlash per source, not a post-tick
baseline), radiator-temperature-factor-unpinned (T^2 x multiplier x emissivity at two temperatures) and
one-writer-castable (the exposed view is not a Dictionary, is the same object each read, refuses writes).
Not built locally; verified on Yggdrasil.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mutant decay-item-from-last-entry survived dc244686: the gun was the last-inserted entry, so
Values.Last().Item was its own, and every other entry was rewritten by its Execute after the
decay loop. The fixture now stops the drive and pings once, so the gun, the drive and the ping
are touched by decay alone and their three items differ. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Decay with a gun, a drive and two Visibility gears checks every entry keeps its own item.
Key 3 of union 22 by golden bytes, the old three-slot record reads Thermal, the five ordinals,
json "channel", and a reopened catalog file. The radiator's strength is HeatRadiationMultiplier
x Emissivity at exponent 0; the ping's is its authored PingVisibility at two values. Gun,
beam and thruster channels hold at a strong and a sub-threshold strength.
Not yet built or run at this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dictionary is private; the public member is an IReadOnlyDictionary over it, so a write
around SetSignature is a compile error, not a grep. Readers (Thruster, tests) use only the
read members. Builds; the test project at this commit still compiles (no reader writes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soul held (aetheria-release:verdict:cut-cultmath-0-4-1-pin.s1); master
moved only in docs since the branch point, so the branch's verify stands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Map: hull layout and aim point (V15, V16), model rows for layout, located
item and the scan, rules R8-R11, and cuts layout-aim and inspection-scanner,
for ruling quiet-gear-identifiable-hull-layout-known. Research section 6:
game scanners and identification of non-emitting equipment, serving
questions scanner-reveals-its-user and scanner-held-or-pulse.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VisibilitySources holds Signature (channel, strength, item); the eight direct writes
go through the one writer; Visibility stays the sum; SignatureOn(channel) splits it.
VisibilityData gains Key(3) Channel. No number changes. Not yet built or tested at this
commit (verification follows).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Body facts, model page, authority map, rules, rationale and cut order for
ruling visibility-per-capability-reveal folded with sensor-stat-set; seven
cuts pinned at 93b7ff8d.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The finding's fixture (Synthetic(8, 7005), the seeded random walk through its
eighth intent) compares a warm allocator with a fresh one at every step. Fails
on the 0.4.0 pin, passes on 0.4.1. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CultMathRevision, the manifest tag and the lock hash move together to
cultmath-unity-v0.4.1 (9c1b06cd). CultLibRevision is unchanged. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Finding cut-thrust-allocator.s3.longinus-envelope-unbounded. The allocated reach carries the envelope's turn terms, which now include the
drives' yaw, so the measured broadside lower bound moves from .9 to .85 of it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eyes pass for the operator's lock and visibility rethink: how Aetheria
represents visibility today, LOBL/LOAL and loitering munitions, warning
receivers, provocation, graded contacts in games, and the novella passages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Zone.Update publishes every entity's paints before any entity updates (Entity.PublishPaints), so a broken lock clears on the same tick whatever the entity order.
- ActionGameManager.ApplyLockProgress is the one lock-indicator curve, called by the outgoing and the incoming indicator.
- LockWarningTests drive the real LockWeapon through Zone.Update; pins on strength across 0.15..1.0, the direction and sensor progress terms, and both entity orders.
Not yet run: this commit's tests have not been built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stale sweep identified generated anchors by collection membership and role, so an author's hull-collider in
Generated was deleted. Generated anchors now carry aetheria.generated; the sweep removes only those. New smoke
pass package_asymmetric_pass: an L-shaped render hull pins tractor X and shield centre to the bounds centre,
a Source-only ship pins that generation never reads the Source fallback, and a collider placed in Generated
survives Package. All commits build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hand mutants (floor dropped; recentre before generation) survived the first Package pass; both now fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AetherDb ship-authoring centre prints Shape.CenterOfMass; Package asks it and moves Ship Root so that point sits
at the world origin, after regenerating the unowned map icon, shield and tractor from the render meshes (Keep
leaves one alone). Package never makes a hull collider (ruling package-frame-defer-collider). smoke_package's
bound-collection rollback cases now rebind to a different path so a rollback that restores keys but not values fails.
C# not yet built when committed; the Blender smokes and Python unit tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_bind_collection restores every aetheria.* key if the ID-property store
refuses a value, so a bad ship id or pending leaves the collection as it
was. smoke_package.py gains default-run cases: relpath raising ValueError
(the cross-drive signal) must store the absolute path and finish; a refused
id or pending must leave bound and unbound collections unchanged.
All commits build (Python only).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lock stops being an intent rewrite in Ship.Update: ThrustAllocator.Allocate takes forwardFloor, which ignores the
stick, asks full forward and holds every column with y > 0 at full (ruling overdrive-forward-floor). Ship allocates in
Entity's new UpdateThrottles phase, after every item's UpdatePerformance and before the power bus and Execute, so
liveness and thrust are the ones Execute acts on (finding liveness-read-before-performance).
Tests follow in the next commits; the test project still compiles, new behaviour is untested here.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LockWeapon.Execute paints its target with its own Lock (the one value the outgoing indicator also reads);
Entity publishes the one-tick inbox as IncomingLocks; the HUD shows one incoming indicator on the strongest
warning with the outgoing indicator's curves. Unity HUD code not compiled here; tests not yet run at this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two Lows from Soul s1 (follow-up catalog-merge-replay-order). The thermal constants are placeholders
(-1) in this commit: it builds and fails by design, the next commit pins the measured values.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Master's catalog blob (f5645e0a, grammar global and seven verbs), then `consumables-first-set apply`.
Dry run on that tree, verbatim:
Vent radius Min 33.03 (Djinni 14x17 cells at 2 units: half diagonal 22.02, x1.5), Max 49.55
New designs:
Thruster Overdrive 1x1 mass 10 price 18000 stackable False duration 6 roles [propellant, regulator]
ThrottleLockData
Multiplier ThrusterData.Thrust 3-4.5 (propellant)
Multiplier VelocityLimitData.TopSpeed 2-3 (propellant)
Multiplier ThrusterData.Heat 5-3 (regulator)
Multiplier ThrusterData.Visibility 5-3 (regulator)
"Opens every drive wide and holds it there. The ship is thrown forward on a bright, hot plume while the pilot keeps the wheel."
Coolant Vent 1x1 mass 15 price 12000 stackable False duration 15 roles [nozzle, coolant]
VapourDump radius 33.03-49.55 (nozzle), opacity 0.9-0.98 (nozzle), lifetime 14-20 (coolant)
Multiplier RadiatorData.Emissivity 0.3-0.5 (coolant)
"Dumps the coolant loop into space as a spreading bank of vapour. Sensors lose whoever is inside it, and the radiators pay for the loss."
New products:
Thruster Overdrive -> "send it" by DME, every role mean 0.55 dev 0.2: no brakes/ no regrets/ one good burn
Thruster Overdrive -> "Updraft" by Finch, every role mean 0.65 dev 0.2: Light as a feather and, for six seconds, about as fast as one in a gale.
Coolant Vent -> "Smoke Machine" by NiteLife, every role mean 0.55 dev 0.12: Fills the room. Nobody can say who came in with you.
Coolant Vent -> "Morning Fog" by Lightsail, every role mean 0.55 dev 0.1: Rolls in thick, lifts slowly, and leaves the bills with the other fellow.
Dry run. Pass "apply" to author 2 designs and 4 products.
A second run answers "already authored; nothing to do". This commit builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GameData/Aetheria.cc is master's blob, not this branch's; the consumables-first-set
apply is replayed in the next commit. The AetherDb command list takes both new
commands. This merge commit does not build until the apply and test seeding follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The command mutated the cache's own VerseGrammar before the batch upsert, so the upsert was redundant and two Soul
mutants (update branch dropped; Same ignoring description or render path) survived: the replace test judged the result
with the same Same() the command uses. The test now reads the fields. Unrun until the full job.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Longinus the fixture builds has no heatsinks: its drives overheat and shut down at
about seven seconds (measured: drift -0.006 deg to tick 360, then the thermal shutdown),
so the 600-tick flight of the spec reads the shutdown, not the allocator.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The allocator reads Item.Active every tick, so fixtures that fire a thruster on their
first update warm its performance first; flight fixtures take the thrusters' energy draw
to zero so a brownout does not scale the turn; the Duel drives are mismatched after they
heat apart, and degraded by heat (the Large Drive has no durability term); the
consumer sits at (2,1), the Skiff's interior centre column. ReachIsWhatTheShipCanDo's
Longinus ceiling is 1.3 (measured 1.195 nose-on, 1.249 broadside).
Previous test commits do not build or pass; this one is unverified until the full run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ValidateAll (one owner, called by AetheriaStores.Open) refuses duplicate verb names; Validate refuses the reserved role
name actor. Tests pin the name-class edges (a z 0 9 _), a null role element, the starter verb replaced when only its
description or render path differs, the grammar global updated in place, and the fixture's second allegiance and zero-role
speak. The test's scoped registry is TestCatalog.Registry(). Builds; unverified until the full run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Catalog sha256 193a4d25... (origin/master) -> f5645e0a... Output of 'dotnet run --project tools/AetherDb -- verse-grammar' (the dry run, before this apply), CultLib git-45c2f40:
Grammar: new, revision 1.
Verbs a Ghostlight-simulated Aetheria world may use: ship actions the game films in-engine, and speech rendered as Aetheria conversation.
Starter verbs (7):
mine(site: Place, ore: Cargo) [ShipAction] new
The actor works an ore body at a place and takes the ore into its hold.
haul(cargo: Cargo, destination: Place) [ShipAction] new
The actor carries cargo to a destination place.
tow(target: Faction) [ShipAction] new
The actor tows a station or hull belonging to the target faction.
defend(protected: Faction, at: Place) [ShipAction] new
The actor guards a protected faction's ships and holdings at a place.
attack(target: Faction) [ShipAction] new
The actor attacks the target faction's ships and holdings.
explore(region: Place) [ShipAction] new
The actor surveys a region and charts what is there.
speak() [Conversation] new
The actor says something to whoever is in earshot. Shown as Aetheria dialogue: face to face when docked, by radio with the speaker's portrait over their ship when undocked.
Dry run. Pass "apply" to land the grammar global and 7 verb(s).
A second dry run printed 'Nothing to do.' Data only; does not itself pass the suite until the validation commit's tests are in (builds: yes, the code is unchanged by this commit).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Takes master's catalog pointer (GameData/Aetheria.cc) and its TestCatalog.Registry fixture path;
VerseGrammar is the global scratch catalogs author (TestCatalogGlobal stays deleted: the shipped
registry now has a real global, so a test-only second one would have to be authored beside it).
StatResolverTests, TestCatalogTests and AetheriaStoresTests author VerseGrammar.
Does not pass the suite: the catalog is master's and holds no grammar until the verse-grammar apply commit follows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EvasionTerm: the aggregate reads go; SteeringTests: ATurnIsThatFractionOfTheHullsTurn;
Brownout/ConditionRatio seat the lone thruster on the centre column; ThrottleLockTests
compare a locked ship to an unlocked twin through the same ticks; ThrustAllocationTests
(Duel flight, lost/degraded/repaired drives, dead thrusters, full turn, arena-stepped
flight); ThrustAllocatorTests.AMicroIntentIsServed.
Not yet verified green at this commit; the test project builds only against the
deleted aggregates' removal in the previous commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ship.Update hands the intent (the throttle lock's one line) and every thruster's live
Column(NominalThrust) to one ThrustAllocator and writes each Axis from its throttle.
Thruster gains NominalThrust and Column; the .01 throttle gates become > 0.
Does not build the test project: EvasionTermTests still read the deleted aggregates
(next commit rewrites them). Not compiled in Unity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TurnTime has no caller; IAnalogBehavior's only implementer is Thruster. Pure
deletes, no behaviour change. Not compiled in Unity (the operator's); the
Shared tests build at the end.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Imagination passes (session self-2026-10-10-ag): the Duel oscillation was a
probe artifact (orbits unplaced before the zone's first tick); wiring and
envelope specs refreshed at 08d064c8; the ship-owned incoming-lock warning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New Ship places each mesh by the matrix an object can hold (_held: location, rotation, scale), so a sheared parent no
longer makes the drafted cells differ from the cells Rasterise reads from the hull left behind. smoke_frame pins a failed
Save of a saved ship (write failure and stale revision) leaving its ship.cc, New Ship refusing a vertical wall before
anything changes (twice), the sheared-parent equality, and Ctrl+Z past a first Save: the refusals name the way forward
(Bind Ship Collection, Load Layout) and the route works. All commits build.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
New Ship baked rotation and scale into the mesh data after checking the cells on the evaluated mesh, so a Mirror
modifier acted in a changed frame (a half hull, length 10, got 3x5 cells). The meshes now keep their data and carry the
placement as their own matrix; the cells and grid_origin are the ones computed before the bind and are placed by the one
_place_grid writer, which Rasterise shares; no call that can refuse follows the bind. The single-user refusal, which
protected the baking, is deleted. One _triangles_xy reads the evaluated meshes for both New Ship and Rasterise.
smoke_frame: a Mirror half hull gets the cells of the hand-mirrored solid, and the drafted cells equal Rasterise's of the
hull left behind. Every commit builds (Python only).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Package refuses loose meshes and a pending ship; Package reads the .cc rows cut to the seven known members and only
compares a buffer that belongs to the bound ship; Flip Nose turns the hull back when its Rasterise fails; New Ship
refuses meshes in child collections; the Grid is not drawn from a buffer short of cells; Rasterise refuses a ship with
no Ship Root. Eight hand mutants, all caught. Every commit builds (Python only).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One frame predicate: _loose_meshes no longer exempts Source, so a mesh Rasterise reads is a mesh Flip Nose turns (it
turns Ship Root's descendants) or the operator refuses and selects it. Package: the glTF exporter's RuntimeError is
printed to the console and reported as fixed text; the validator's verdict goes to the console and the panel reports
accepted or refused with the anchor count only (no ship ID, no anchor ID). smoke_frame and smoke_package pin each, five
hand mutants caught. Every commit builds (Python only).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pass 7: a pending Save over an existing ship.cc is refused and leaves it; a failed Save keeps the files that were in
the ship folder; New Ship takes no hardpoints from the ship before it; a buffer that belongs to another ship is neither
saved nor rasterised. Pass 8: operators called with undo=True push a native step in background Blender only when they
carry UNDO, so Rasterise, Flip Nose and New Ship undo as one step and Save does not. Nine hand mutants, all caught.
Closes no finding; every commit builds (Python only).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sixteen gear picks drawn with replacement fill a shipped station hold, so a station whose roll hit
usually stored nothing (29 of 400 held one against the ruled quarter). The consumable step now runs
before the gear fill; with no consumable on offer it still draws nothing, so generation is unchanged.
Ships keep their step after OutfitEntity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Station and ship rates are measured per hull on two station hulls and two ship hulls with holds that
sixteen gear picks fill, on a station whose first bay refuses, and on the shipped catalog's own hulls.
The ship band is 3000 seeds so chances of .12, .15 and .05 fall outside it. These tests fail on the
generator as it stands (consumable stocked after the gear); the next commit fixes the generator.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Builds: yes. The previous commit's pin test failed on '.' (a dot last is refused by its own rule).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Regex form could not be mutated on the verification path (the mutant runner collapsed its backslashes), and a
character loop with named length constants states the same rule without the dependency. Adds refusals for a capital
inside a name, a hyphen, and a direct empty-name case. Builds (not yet run when committed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One chooser (_ship_collection) and one predicate (_is_ship). _save_layout is the only
replace_layout caller and creates a pending ship's file, removing what it made on failure.
Rasterise writes the layout buffer and aetheria.grid_origin = [x, y, width, height]; Flip Nose
and Package refuse meshes outside Ship Root. New Ship checks first and writes nothing to disk.
Operator reports are fixed text; AetherDb output goes to the system console. smoke_frame covers
undo, hardpoints, Save, cleanup, loose meshes and an empty Game folder preference.
Builds: yes (needs the previous commit's ship_cc).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
layout_revision(shape, hardpoints) is the revision rule replace_layout and Package share.
SHIP_ID_CHARS and RESERVED_SHIP_IDS mirror ShipAuthoringStore.RequireShipId; ShipSchemaPinTests
sweeps every character and every candidate stem against the C# rule. ship_cc errors no longer
carry the path, mount id, anchor id or object name. C# tests: create judges the ID not the
display name, FindRoot answers the working directory itself, short trailing-dot IDs.
Builds: yes (C# test edits and Python only).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous commit (8bfce189) does not build: CS0104 ambiguous Random in ThrustAllocatorTests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing asked, nothing fired is a fact about the intent: when all three clamped intents are exactly zero Allocate returns the zeroed buffer before the solve, so a warm solve that stops at the iteration cap can no longer fire thrusters. The output flush is gone. The warm start is left at the last solved point. Tests: the two 40-thruster hulls Soul found, and a small intent still answered at any column scale. Builds untested locally; verified on Yggdrasil.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Off-centre joint limits (20..50, -10..50, -50..0) so Max-Min is distinguishable from |Min|+|Max| and 2*max/2*Max; Bind walking past tagged
nodes that are not joints; Rigs and the lead mount follow hull order where it differs from name order. Tests only; builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Flushing an empty cache writes no file, so the previous fixture still hit the new missing-file refusal.
Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mutant that changed a constant moved the test's expectation with it and survived (station chance 1,
ship chance 0 and 1, station units 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a good call the warm start left 1e-34..1e-44 throttles on a zero-intent call. Throttles at or below ThrottleResidue return 0 on the one general path; TA-R3's warm start is kept. The test NothingAskedAfterACallFiresNothing arrived in the previous commit (22cb76e8), which therefore fails that test; this commit makes it pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A cost row pulling the errors the other way lowers the pre-onset drive throttle by about 20 percent (1.30e-4 to 1.06e-4 at turn .5); the bounds sit 8 percent under the measured value.
Commit builds: yes (tests/Aetheria.Shared.Tests not yet run on this commit; the full run is the closing job).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The turn leaves a forward error of 2, the top of TA-R2's error box; a bound of 1 or 1.5 gives yaw back.
Commit builds: yes (tests/Aetheria.Shared.Tests not yet run on this commit; the full run is the closing job).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
128 torque-free forward thrusters from a cold start reach the 100-iteration cap through the public surface; the call must keep the iterate it reached, not zero it.
Commit builds: yes (tests/Aetheria.Shared.Tests not yet run on this commit; the full run is the closing job).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A ship cannot be equipped once active, so the radiator has to be fitted inside the one builder.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The gated-versus-plain comparison alone cannot see an unconditional draw (both catalogs draw).
Values measured on origin/master d4a27def with the same fixture. The roll-before-availability
mutant survived the full suite without this pin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Imagination pass (session self-2026-10-10-ag): the row gimbal is sim data,
one shared-throttle actuator per row in the allocator's single solve;
specs vector-rows-record r2 and vector-rows-allocator r1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The game treats a directory without ship.cc as not a package (TheBootCatalogIsTheShippedOneUntilAModIsInstalled), so da4d8c6c's
Compose exclusion broke that test. Reverts the Compose and ResolveCatalog change and moves the check to the one place that
holds the first-party tree to a stricter rule: TestCatalog.Resolve throws, naming each directory under GameData/Mods that
holds no ship.cc (missing-ship-cc-skipped). The GLB case change and the registry parameters stand.
Builds; not yet run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test assembly registers a catalog [CultGlobal], so the global check already threw on a missing
file there and AStoreRefusesAMissingCatalog passed against the old Open (run at fe9da2e: 12 passed).
The refusal is now a FileNotFoundException naming the path, and the test pins that type, which the
global check never throws. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test relied on a read-only open of a nonexistent file yielding an empty catalog, which
AetheriaStores.Open now refuses; the fixture writes the empty catalog first. Builds; the suite is green with it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pass 4 gains the case that kills the mutant dropping the Aetheria.cc check on the Game folder preference. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous commit's _aetherdb did not parse (unterminated string literal), so 63d16e81 and 6971ff70 cannot import the add-on; this one can.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestCatalogGlobal existed only to give the test host one [CultGlobal] routed to the catalog. VerseGrammar is the
game's first, so the test-only type is deleted and every fixture authors a VerseGrammar instead (63 call sites; the
missing-global tests assert on aetheria.verse_grammar). Stale comments about the test type are rewritten.
Builds (not yet run when committed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous commit did not compile in Unity (CS0117 on PlayerSettings.bundleVersion); this one does,
and a batchmode run of AetheriaBuild.Build succeeded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AetheriaBuild (Assets/Editor) sets the version, builds Addressables, builds the
Win64 player, stages GameData through PlayerStaging, publishes AetherDb to
ModTools and removes the Burst debug folder; any failure exits 1 naming the step.
PlayerStaging is Unity-free and linked into Aetheria.Shared.Tests. A read-only
AetheriaStores.Open of a nonexistent catalog throws.
Builds on the headless side; the Unity .meta files and the batchmode run follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FindRoot keys on GameData/Aetheria.cc. ShipAuthoringStore.RequireShipId is the one ID rule
(Validate and create), refusing Windows device names and a trailing dot, naming no value.
The add-on's one preference is Game folder; _aetheria_repo, the repo preference and the
dotnet-run invocation are deleted. Smokes take SMOKE_GAME_FOLDER. Not yet built or tested
at this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deletes the direct GameData/Aetheria.cc opens (FireControlCut124Tests x3, FireControlCut7Tests, MiningCut3Tests x2,
RestoredHullsTests x11) in favour of TestCatalog.Repo, and the eight copies of the shipped-assembly registry in favour of
TestCatalog.Registry() (real-catalog-readers-bypass). Pirates and Roles command tests keep copying the shipped catalog (they
rewrite a copy through AetherDb) and take only the registry.
Builds; not yet run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AetheriaStores.Open, ShipModCatalog.Compose and ResolveCatalog take an optional registry (the game passes none and keeps the
process-wide one). TestCatalog.Registry() is the shipped assembly's registry, so the test assembly's TestCatalogGlobal is no
longer a catalog global the real catalog must hold (fixture-fails-on-real-package). TestCatalog.ForRepo(root) is the one
binding Repo uses, pinned by a test over a real catalog plus a package (repo-wiring-unpinned). A package directory without
ship.cc is excluded with a reason, in Compose and so in ResolveCatalog's pre-check (missing-ship-cc-skipped). The loader takes
only a lowercase .glb, matching the case-sensitive LFS rule (lfs-glb-case).
All commits build; not yet run (verification job follows).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Serves follow-ups vectored-thruster-rows and gimballed-thrusters: virtual
actuators, nonlinear and cascaded allocation, rate and gimbal limits as
per-tick bounds, the zero-thrust singularity, games, shared-angle rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Output of 'dotnet run --project tools/AetherDb -- verse-grammar' (the dry run, before this apply), CultLib git-45c2f40:
Grammar: new, revision 1.
Verbs a Ghostlight-simulated Aetheria world may use: ship actions the game films in-engine, and speech rendered as Aetheria conversation.
Starter verbs (7):
mine(site: Place, ore: Cargo) [ShipAction] new
The actor works an ore body at a place and takes the ore into its hold.
haul(cargo: Cargo, destination: Place) [ShipAction] new
The actor carries cargo to a destination place.
tow(target: Faction) [ShipAction] new
The actor tows a station or hull belonging to the target faction.
defend(protected: Faction, at: Place) [ShipAction] new
The actor guards a protected faction's ships and holdings at a place.
attack(target: Faction) [ShipAction] new
The actor attacks the target faction's ships and holdings.
explore(region: Place) [ShipAction] new
The actor surveys a region and charts what is there.
speak() [Conversation] new
The actor says something to whoever is in earshot. Shown as Aetheria dialogue: face to face when docked, by radio with the speaker's portrait over their ship when undocked.
Dry run. Pass "apply" to land the grammar global and 7 verb(s).
Then 'verse-grammar apply' on that catalog (sha256 193a4d25... -> b5b3db48...), and a second run printed no changes.
Run on Yggdrasil; the catalog copied back and committed here. Builds: this commit changes data only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CultCache mints a random record key and a wall-clock storedAt per write, so two writes never match byte for byte.
The writer now fixes every record key and the test compares what a reader consumes (schema, key, content) across two
writes. Builds; the shipped-catalog test still fails at this commit because the catalog lands in the next one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous commit, 51b8ecf7, does not build (AetheriaStores.Validate does not exist); this one does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Output of `dotnet run --project tools/AetherDb -- consumables-first-set apply` on the catalog at e71540be (sha256 of the
result 7e56a2ba4f9a8146ca785c326fd42228d011ffb5ac3e7e6a999e0e4e3f5cd0e2). The dry run printed:
-rw-r--r-- 1 root root 12718840 Oct 10 04:22 Aetheria.cc
-rw-r--r-- 1 root root 1603 Oct 10 04:22 apply.txt
-rw-r--r-- 1 root root 1612 Oct 10 04:22 dryrun.txt
Vent radius Min 33.03 (Djinni 14x17 cells at 2 units: half diagonal 22.02, x1.5), Max 49.55
New designs:
Thruster Overdrive 1x1 mass 10 price 18000 stackable False duration 6 roles [propellant, regulator]
ThrottleLockData
Multiplier ThrusterData.Thrust 3-4.5 (propellant)
Multiplier VelocityLimitData.TopSpeed 2-3 (propellant)
Multiplier ThrusterData.Heat 5-3 (regulator)
Multiplier ThrusterData.Visibility 5-3 (regulator)
"Opens every drive wide and holds it there. The ship is thrown forward on a bright, hot plume while the pilot keeps the wheel."
Coolant Vent 1x1 mass 15 price 12000 stackable False duration 15 roles [nozzle, coolant]
VapourDump radius 33.03-49.55 (nozzle), opacity 0.9-0.98 (nozzle), lifetime 14-20 (coolant)
Multiplier RadiatorData.Emissivity 0.3-0.5 (coolant)
"Dumps the coolant loop into space as a spreading bank of vapour. Sensors lose whoever is inside it, and the radiators pay for the loss."
New products:
Thruster Overdrive -> "send it" by DME, every role mean 0.55 dev 0.2: no brakes/ no regrets/ one good burn
Thruster Overdrive -> "Updraft" by Finch, every role mean 0.65 dev 0.2: Light as a feather and, for six seconds, about as fast as one in a gale.
Coolant Vent -> "Smoke Machine" by NiteLife, every role mean 0.55 dev 0.12: Fills the room. Nobody can say who came in with you.
Coolant Vent -> "Morning Fog" by Lightsail, every role mean 0.55 dev 0.1: Rolls in thick, lifts slowly, and leaves the bills with the other fellow.
Dry run. Pass "apply" to author 2 designs and 4 products.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run against the shipped catalog once the next commit lands; this commit alone fails them (it builds).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds the aetheria.verse_grammar global and aetheria.verse_verb documents (one render path per verb, typed
participant roles), VerseGrammarValidation run by AetheriaStores.Open over every verb, the AetherDb
'verse-grammar [apply]' command (starter set: one ship-action verb per TaskType plus speak; question
verse-social-verbs default speak-only), and VerseGrammarTests with a deterministic fixture writer.
The shipped catalog does not carry the grammar yet: it lands in the next commit, so
The_shipped_catalog_holds_revision_one_and_the_seven_starter_verbs fails at this commit. Not yet built when committed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dry-run by default, validated and committed in one batch like pd-gear. Overdrive (1x1, mass 10, price 18000, duration 6,
ThrottleLock then Thrust 3-4.5 and TopSpeed 2-3 on propellant, Heat and Visibility 5-3 on regulator); Coolant Vent
(1x1, mass 15, price 12000, duration 15, VapourDump radius from the Djinni diagonal x1.5, opacity .90-.98, lifetime
14-20, then Emissivity .3-.5). Builds; the catalog is not yet applied.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Behaviors.cs union list: keep Union(41, ThrottleLockData) from master and
Union(42, VapourDumpData) from this cut; no key renumbered. All commits build
as a merge result only after the verify job.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
LoadoutGenerator.AvailableProducts and RandomProducts now range over CraftedItemData; only
equippable gear needs a hull home. StockConsumables is the last step of GenerateShipLoadout and
GenerateStationLoadout: it draws nothing when no consumable is on offer. Unbuilt/untested at
this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first commit's gear modifiers never executed (test gear has no heat response, so it read offline). Builds; the first commit's new tests fail without this.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pins ruling boost-stacking-multiply through the production path (gear and consumable StatModifierData on a ship, read via EquippedItem.Evaluate): TwoScalesOnOneStatMultiply, AConsumableBoostMultipliesWithGear, TwoConstantsOnOneStatAdd. Tests only; no production change. Not yet run; the commit is unverified.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces TheLockHoldsThroughMostOfItsDurationAndEndsAfter with a theory over Duration 1 and 2 at 0.25 s ticks. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deletes the articulation anchor role. Adds ShipJoint (key 5 Joints), ShipAnchor.Joint (key 5),
the judge's joint-shape, mount-on-joint and yaw-reach checks over RigChains, Bind's GLB checks
(ReadNodeIds returns the nearest tagged ancestor), and ShipModPlan.Rigs. Presentation only:
arc stays HardpointData.FiringArc and traverse WeaponData.Tracking (ruling mount-arc-gun-traverse).
Not yet built or run when committed; the verification commit follows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Imagination r2 re-map (session self-2026-10-10-ag): the sim already carries
per-hardpoint arc and weapon traverse; links are rig only. Target doc moves
articulated weapon mounts into scope under ruling mount-arc-gun-traverse.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every entry of A is now assigned each call or never written, so the clear was a second
owner of the same state (hand mutants a-never-cleared and a-cleared-by-division survived
it as equivalent).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stryker survivors 12390 (initial _columns +1 skips the first resize at n = 1) and
12399/12400 (A not cleared between same-count calls) get tests. Array.Clear on b was an
equivalent mutant: rows 0-6 are assigned every call and the ridge rows are never written.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eyes pass before the hull shader fork: DECALmachine 2.17 export paths,
forward-renderer decal practice, parallax for panel lines, layered wear
on the built-in pipeline, and this project's rendering constraints.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removes the scratch probe. The spec pinned 1e-4; the optimum leaks 1.2e-4 (Duel) and
1.6e-4 (halved Talaria) from the first touch of Turn. Builds; ran 18 tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rules TA-R1 to TA-R4 of cut-thrust-allocator-core.r2, tests on synthetic columns
(Duel, damaged Duel, symmetric hull). First draft; not yet run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ambientCG 2K sets under Assets/Materials/PBR (GlowFade .mat per material,
packed metallic/smoothness), and Asset Sources/PBR Materials.blend as a
UV-mapped Blender asset library linking the same textures. Sources and
licence in Assets/Materials/PBR/SOURCES.md. Unity import unverified until
the project is opened.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 asset-marked materials, UV mapping with a scalar Tiling node, images
linked by relative path to Assets/Materials/PBR, previews generated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2K-JPG Color/Roughness/NormalGL (+Metalness, AO where the set has them),
generated MetallicSmoothness.png (R metallic, A 1-roughness), texture and
material .meta files, SOURCES.md. Unity side not opened yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CultMathRevision 252e226c (cultmath-unity-v0.4.0), manifest tag and lock entry.
Generated content that samples noise may move; fixtures follow in later commits.
Builds as a pin; the suite may fail on re-rolled content until then.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Expiry fires when the remaining duration reaches zero (<= 0), so behaviours execute on
Duration/dt ticks and not one more. AConsumableRunsForExactlyItsDurationInTicks pins it
with 0.25 s ticks. Builds; verified in the next run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four tests for Soul's survivors lock-intent-length, lock-ignores-ccw-turn, lock-any-consumable and
lock-ends-at-half-duration. Tests only; not yet run on Yggdrasil.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first draft activated the second effect so late it was not yet attached when the
first detached, so the shared-entry guard mutants survived it. Builds; tests green
status is in the next verification run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FireControl.RoundEnd and RoundAt leave FireControl (RoundEnd to DrawAhead verbatim, RoundAt as DrawAhead's private Line);
DrawAhead.Round takes the published outcome, steers a hit or burst from its frozen line at the commit tick onto its
end (burst point, else the impact cell on the target's drawn pose), and RoundStop is deleted. Projectile caches the
last published ShotOutcome and calls DrawAhead only. BallisticFlightTests move to the new signatures; the frozen-line
moving-target test is replaced by impact-cell, steer and no-drawing-function tests. Unverified: not yet built or run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entity.Activate re-initialises the running consumable effects through the same
InitializeBehaviors that initialises gear and ActivateConsumable, so a consumable
active across deactivate/refit/activate retargets to the new items.
ConsumableTests gains the stackable overlap/multiply, shared-entry detach,
expiry-boundary, per-design cooldown and refit facts. StatResolverTests' expiry
test builds its consumable through ItemManager.CreateInstance.
Not yet built or run; verification follows in a later commit message.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The contact-burst fixture cannot tell RoundStop's burst point from the line at
arrival (CommitBurst puts both at the same distance), so a stop that ignored the
burst point survived. A targeted proximity round at a target receding faster
than the round closes bursts at the pulled-back range point while arriving at
the fired-at distance; this test pins the stop to the sim's burst point.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deletes At, the end formula (Unity restating the range) and Resolve's own stop
arithmetic. Launch binds ShotCommitted (refresh the record, learn the outcome)
and ShotResolved; Draw is DrawAhead.Round plus RoundOver; a hit or burst stops at
DrawAhead.RoundStop. Projectile names no flight quantity.
Builds headless (Projectile is not in the test project); the Unity compile is
run separately at the final head.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec r2 said 30 against a round speed of 20: no first-order intercept exists
there (the target outruns the round sideways), so the round did not lead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RoundEnd(shot, known) is the one statement of when a ballistic round's drawn
flight ends: arrival for a hit, burst or unpublished outcome, the frozen range
(never short of arrival) for a miss. RoundAt takes the known outcome and clamps
at RoundEnd. DrawAhead gains Round, RoundStop, RoundOver (pure, no state) so the
blend, stop and done rules are headless-testable. Seven new tests (ten cases).
Unity build: Projectile.cs still calls the old RoundAt, so this commit does not
compile in Unity (the headless test project does not include Projectile). The
next commit moves Projectile onto the statics.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mutation table let the guard's removal survive: with saturate's NaN rule
(max(0, NaN) = 0) a zero-lifetime cloud has no opacity without it, and
ACloudWithNoLifetimeObscuresNothing still pins that behaviour. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pairs mirrored across the ship's width had equal torque and the compensation still cancelled one; the previous commit's AnUnlockedShipFollowsIntent was red. Mirror across its length instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A lone off-axis strafe thruster cancels its own torque in Ship.Update, so its axis read 0 unlocked; pairs leave the strafe axis plain. The previous commit's AnUnlockedShipFollowsIntent was red for this.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The settled ratio carries the seed's last residue (.4007 against .4); two
places still separates .4 from the squared .16. Test-only; builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes the fixtures the first verify run falsified: the sensor's start-up seed
made one-tick ratios meaningless (compare settled infos and the settled ping
extra), launch drag moves a cloud less than its velocity, and the vent's own
velocity is read when the cloud is added, not after it has drifted. Test-only;
builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FireRange is the distance to the engaged target and 0 for a round with none, so a no-lock burst round and a
round that misses never flew. MaxRange = weapon.Range is frozen at Fire beside Speed. Unity compile not yet run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first commit's tests failed in setup (border cells are not interior); it builds, its three new tests were red.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 1x1 interior fixture could not store the second consumable the
non-stackable test needs. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pins the cloud's drift and fade-from-vent-time, the ping branch of Gain, the
dump's authored radius/opacity/lifetime and its union 42 keys 1-3. Test-only;
builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gain stays the one detection rule: both branches are scaled by the fraction
of the signal that survives the clouds between observer and target
(Zone.Obscuration, exactly 1f with none, so the pre-cloud trace is
bit-identical). The entity loop passes it for pings and passive ticks and
PassiveRate for the chunk position. Entity.cs is untouched: a lock falls by
the existing detection-threshold rule when info does. Adds the sensor-level
facts to VapourCloudTests. Builds; not yet run (verify job follows).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Zone.Vent is the one adder, StepClouds the one remover, Zone.Obscuration the
one reader: the product of (1 - opacity) over clouds whose disc the segment
touches (closest point of the segment to the centre within Radius), 1f when
none. Derived from live clouds at each call, never cached. VapourDump vents
once on its first run, drifting with its venter. Nothing reads Obscuration
yet (Sensor.Gain takes it in the next commit); this commit builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deletes the Time.deltaTime gravity/drag/position integration and the range-distance kill from Projectile and the
UnityEngine.Random jitter, barrel direction and inherited velocity from ProjectileManager. A round holds a copy of
its PendingShot and draws at RoundAt(Zone.Time + Clock.Lead) with a barrel offset decaying over BlendTime; ShotResolved
ends a hit or burst at the outcome's BurstPoint, a miss flies to the FireRange clamp. Unity compile not yet run at
this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ActivateConsumable runs Initialize on the effect's behaviours; expiry disposes
them before Resolver.Forget, so a consumable's modifiers exist exactly while
its effect is active. ItemManager.CreateInstance returns a ConsumableItem for a
consumable design. StatResolver.DetachModifier drops an entry left with
nothing attached, so ModifierEntryCount returns to baseline. LoadoutTests'
hand-built ConsumableItem goes through CreateInstance. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PendingShot freezes the weapon's Velocity as Speed at Fire; RoundAt(shot, time) reads the frozen origin,
direction, speed and fire time, clamped to the FireRange point. Step, Commit and Apply do not call it.
BallisticFlightTests pin the line against the shooter position, aim and authored velocity.
Builds: yes. Presenters still fly their own rounds at this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ThrottleLockData (union 41) marks the lock; Entity.ThrottleLocked is derived from the
active effects; Ship.Update reads one `move` in place of MovementDirection. Turn is
untouched (ruling overdrive-keep-steering). Not yet verified by a build when committed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds AConsumableModifierBoostsUntilExpiry, ABoughtConsumableActivates and
ANonStackableConsumableRefusesASecondActivation. This commit builds; its tests
fail until the host cut lands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soul s1-s2 hold (aetheria-release:verdict:cut-cultmath-0-3-pin.s2). The generated galaxy re-rolls with the noise kernel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tutorial-seed scan for a zone with no genuine Lagrange candidate ran
once per test; it now runs once behind a Lazy. The leak test regains
Assert.NotSame(galaxy.Entrance, otherZone), and the scan's miss path uses
Assert.Fail (xUnit2020).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Body facts RF1-RF8, model rows, booster and consumable tables, and rationale for the
revisions admitted under follow-up rulings-2026-10-09-spec-refresh. Sweeps the stacking
and hoarding lines the 2026-10-09 rulings made stale.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the target's fit written out the jinking price is .383 against .440
coasting, but 200 rolled shots gave 93 hits jinking against 78 coasting: the gap
between the prices is smaller than the noise of two 200-shot volleys. 2000 shots
put the expected gap near four standard deviations; the assertion is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EvRangeLaunch placed stage.Generated("Djinni"), whose fit follows the galaxy; at
CultMath 0.3.0 the generated target gave 95 hits jinking against 78 coasting.
The target now carries an explicit Talaria/cockpit/reactor fit, so the hit
comparison does not depend on generated content.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EAC-7089 and EAC-2733 were generated-content names that CultMath 0.3.0 re-rolled
away. The no-Lagrange-candidate zone is now found by its property (planets that
are all rosette members, so no genuine candidate) by scanning seeds, and the
zero-natural-station zone by its isTutorial: false roll. Every assertion keeps
its claim; the candidate predicate is shared with the leak test's fixture check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The noise kernel changed at cultmath-unity-v0.3.0, so the fixed seed gives a
different tutorial. TutorialGalaxy_unchanged keeps its claim (a fixed seed
gives a fixed tutorial) against the regenerated shape. Does not pass the whole
suite: the RestoredHullsTests and the evasion test follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Directory.Build.props, Packages/manifest.json and the lock move to
cultmath-unity-v0.3.0 (614fd445) together. VerifyCultLibRevision matched the
CultMath ProjectReference by a backslash-spelled identity and was skipped on
Linux; it now matches by project file name. Does not pass tests: generated
content re-rolls with the new noise kernel, fixtures follow in later commits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The backfilled Large Drives differ in quality, so a held forward thrust is a net torque and TurnRate never settled to zero. Release the movement demand with the turn demand.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
AetherDb roles-backfill apply on master's blob 32a2e401: 12 designs and 6 products authored; a second apply is a no-op.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
AetherDrive.cs: master deleted the behaviour (2cfdc728); the branch only added a
TurnRate line to it. Deletion kept. Thruster remains the one rotation actuator
and feeds Entity.TurnRate. No sim stepping outside the clock on master
(mines presenter, Pirates AI, lot-product all step through Zone.Update).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Large Drive and Small Drive shipped with unnamed Quality terms on their flat stats; the command keeps those exponents. The reset stripped them, so the new exponent check read the Thruster precedent instead.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A non-flat stat must carry exactly one Quality term, named, declared, with a positive exponent; a flat stat may not name a role. The check is one function with its own synthetic-stat test.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ExponentFor drew its pool from items.Except(targets), so growing the targets from six to twelve changed the exponents a rerun on master's catalog writes. The pool is now snapshotted once, from every design that declares roles and names a Quality term, before the loop mutates anything. Describe prints each named term's exponent, one test pins Autocannon's and LRMM72's exponents against the shipped blob, and one clears a single role of a two-role seller and expects only that role re-authored.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ShippedCatalogRolesAreLive checks every design of every kind: each non-flat stat reads one declared role,
declared roles are read, sellers author each once, role-mapped designs are not all flat (the tool answers
which, via Program.HasRoleMap). RolesBackfillIsAuthored pins ratios and roles, with the Autocannon
Penetration, Mine Range and LRMM72 LockAngle asserts. RolesBackfillCommandTests resets the backfilled designs
on a scratch catalog, applies, and expects the shipped catalog back, then replays as a no-op.
EveryShippedProductHasAMaker. TheEnvelopeComesFromTheGear bounds each drive by the catalog's Large Drive
Thrust range; ShippedWeaponOperabilityTests reads the catalog's DefaultEntitySettings.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
RoleMapFor answers for the Mine Launcher (MineLayerRoles) so roles-migrate and roles-backfill no longer
disagree on it; BackfillMapFor is deleted. Count, MinRange, DamageSpread and the reactor Modifier join their
kind maps, and the backfill points a varying stat's unnamed Quality term at its role without touching its
range. DeathCluster, Flak Gun, GT 3K, plight and both MoveOnPros join the backfill set (CB-R1 forces them).
RolesBackfill(apply, root) mirrors PiratesFaction so tests can replay it on a scratch catalog.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
DrawAhead leaves a ship in a wormhole animation where the step put it. The clock is
one static instance; a zone entered inside a step ends that frame's stepping and the
clock resets between frames (SimClock.Reset throws inside Advance). DrawAhead.Time had
no consumer.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
EmojiFont.Build leaves TMP's emoji fallback list empty. EmojiCheck fails when that list is non-empty,
when the global fallback lacks Emoji.asset, or when a glyph is served by a font other than Emoji.asset
or the body font; reads the Pirates product names from the shipped catalog; renders the
emoji-test.txt corpus and reports what Twemoji lacks; counts only used atlas pages (TMP doubles the
array capacity, so trailing slots are null by design).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1400 single code points (the 2 and 3 digit ones included), Twemoji lacks none; font sha256
unchanged (reproduced in python:3.13-slim on Yggdrasil).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Each fitted Talaria has its own lot's thrust, so Left and Right are the two flanks'
accelerations, not both the larger one.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ShippedCatalogRolesAreLive skips gear with no PerformanceStat at all (Tractor Beam is a
thruster kind with none). TheEnvelopeComesFromTheGear fits the Longinus by name: the
generator's picks follow the item stream and every authored ProductRole draws from it, so
the generated hull stopped carrying Large Drives (Medium Drive and RevvITup instead). Its
forward bound is the drives' own thrust, since a Large Drive now rolls 0.8x to 1.2x.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Probes B1-B9, the owner answer (published AetherDb), the r5 authority map
and rationale from the imagination-addon-frame pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Output of `AetherDb roles-backfill apply` on origin/master 325ef4bc's catalog, run on
Yggdrasil at 54999a64. Dry run: Autocannon, LRMM72, SRMM72, Mine Launcher, Large Drive
and Small Drive gain roles and quality ranges; Autocannon (Zhestokost, Pirates),
LRMM72 (R&D), SRMM72 (DME), Mine Launcher (DME) and True North (Lightsail) author their
ProductRoles. A replay prints "already authored". At merge, take master's catalog and
rerun `AetherDb roles-backfill apply`.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
AetherDb roles-backfill gives Autocannon, LRMM72, SRMM72, Mine Launcher, Large Drive
(and Small Drive, which declared roles no stat read) live roles: each mapped flat
stat becomes a 0.8x..1.2x range read through a Quality term naming its role, and
every seller authors a ProductRole per role from its maker profile. MineLayerRoles
is new; the maker profile table is hoisted so pd-gear and this command share it.
ShippedCatalogRolesAreLive and RolesBackfillIsAuthored read the shipped catalog.
The catalog blob lands in its own commit.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Body facts CC1-CC12, rules CS-R1..CS-R4 and rationale from the
imagination-consumables pass; specs and questions are in the mind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The corpus now derives from emoji-test.txt (sha256 pinned), keeps single code points of any digit
count, and lists the emoji Twemoji lacks. The script runs only on linux, Python 3.13, image
python:3.13-slim and records those in the lock; identical pins with a different font exit 1 and
leave the committed font and lock untouched.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A StatReference naming WeaponData.Damage validated and attached to nothing,
because TargetsOf compared exact types. All three filters now use
targetType.IsAssignableFrom; no exact-match path remains.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Body facts CB1-CB12, rules CB-R1..CB-R5 and rationale from the
imagination-catalog-breadth pass; specs and questions are in the mind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Master's catalog (LFS 5ecf375f: PD gear, Mine Launcher, retirements) differs from this
branch's base, so the branch's blob was not taken. AetherDb pirates-faction apply was rerun
on master's catalog (merged tool, Yggdrasil container): the Pirates faction and eight
emoji-named products authored; catalog 5ecf375f -> 32a2e401 (12712398 bytes); a second run
reports the faction already exists and the blob is byte-identical.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
AetherDb Program.cs: both sides appended a command (pd-gear/mine-launcher, pirates-faction);
kept all, one usage line. GameData/Aetheria.cc: master's blob kept here, pirates-faction
apply replayed on it in the next commit (follow-up catalog-blob-merge-order).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
DrawAhead.Rotation delegates to Turned(pose, angle) so the turn is testable on a tilted pose, where the flat Duel poses make the quaternion product's off-axis terms vanish. Unverified at this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Master's catalog (LFS adba0389: Mine Launcher present, LonginusX and Traction retired)
differs from this branch's base, so the branch's catalog was not picked. AetherDb
pd-gear apply was rerun on master's catalog (merged tool): 3 designs and 11 products
authored, a second run reports it already authored, dangling reports 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The catalog is regenerated from master's catalog with pd-gear apply (next commit).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
# GameData/Aetheria.cc
# tools/AetherDb/Program.cs
Master's catalog (LFS 9e75d9a4, carrying the Mine Launcher and the gear-without-presence
merge base) differed from this branch's base, so the branch's catalog was not picked.
AetherDb retire-longinusx apply was rerun on master's catalog (tool as of 8038b46a plus
origin/master): the dry run listed the same four records (HullData LonginusX, GearData
Traction, their two FactionProductData), a second apply is refused, dangling reports 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New Ship creates the ship .cc, binds through the one shared binding, moves the meshes into Source
under a Ship Root, turns them nose to -Y and sizes them. Rasterise is the only writer of
aetheria.grid_origin; Load, Save and Rasterise redraw the Grid.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cell_centre and cell_at map a cell to and from the Ship Root frame through a stored grid_origin;
rasterise turns top-down triangles into column-major cells (4x4 samples, half occupies, 32 cells
per axis at most).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assets/Fonts/Emoji/Twemoji.ttf is fetch_emoji_font.py's output for jdecked/twemoji
v17.0.3 (nanoemoji 0.16.0, glyf_colr_0), reproduced bit-identically by two
independent runs on Yggdrasil; the lock pins source and font sha256. EmojiFont.Build
wrote Emoji.asset (dynamic, COLOR, multi-atlas) and TMP Settings' emoji fallback list,
global fallback entry and empty default sprite asset. EmojiCheck.Run: 1425 single
code points and the eight Pirates names render with no missing code point. CC BY 4.0
attribution ships in Assets/Fonts/Emoji/LICENSE.txt. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stryker's one survivor in the first sweep was the guard's `true ?` mutant: the
cache's Get(ref) extension already returns null for an unset reference, so the
guard was a second path. Adds the removed-product fact the spec names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lands retire-longinusx on master after gear-without-presence. The catalog
is regenerated from master's catalog with retire-longinusx apply (next commit).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
# GameData/Aetheria.cc
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>
tools/emoji/fetch_emoji_font.py builds Assets/Fonts/Emoji/Twemoji.ttf from the
pinned jdecked/twemoji release with nanoemoji (glyf_colr_0); EmojiFont.Build is
the one writer of Emoji.asset and TMP Settings' emoji routing; EmojiCheck.Run
renders every single-code-point emoji and the Pirates names through TMP. The
generated font, lock and asset land in the next commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first verification showed the rotation sense wrong and no shot resolving in 30 s of Duel; both tests now pin what they claim. Unverified at this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fixture resolves GameData/Mods through ShipModCatalog.ResolveCatalog once per test process and throws on any
excluded package, so a broken first-party package fails the suite. With no GameData/Mods it is the shipped catalog,
unchanged. Builds and tests not yet run on this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings gear-without-presence up to master ahead of landing it, with
eureka/aetheria-release-retire-longinusx following.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Operator approved the dry run as shown (ruling pirates-record-apply). Dry run output:
New faction "Pirates": influence 3, names "pleiades", allegiance 1.0 to 12 factions.
colours: primary (0.42, 0.40, 0.37) primer grey, secondary (1.00, 0.45, 0.05) hazard orange
A loose coalition of crews and hidden docks moving people and goods outside official control. No command, no common law: possession has a maintenance schedule.
New products (8):
🫳🔫 [U+1FAF3 U+1F52B]
design WeaponItemData "Autocannon" (made by Zhestokost), roles []
"Serial number filed off. Still shoots."
🪦🤠 [U+1FAA6 U+1F920]
design WeaponItemData "Earp" (made by AU), roles [barrel, feed mechanism]
"The previous owner had no further use for it."
🧾🚫 [U+1F9FE U+1F6AB]
design WeaponItemData "FastBlast+-" (made by NiteLife), roles [focusing array, power coupling]
"Warranty void where prohibited, which is everywhere."
🎨🙈 [U+1F3A8 U+1F648]
design WeaponItemData "6k Shooter" (made by Zhestokost), roles [barrel, feed mechanism]
"The logo underneath is somebody else's problem."
🔥 [U+1F525]
design WeaponItemData "scorched void policy" (made by DME), roles [guidance system, thruster, warhead]
"Turns out we did start the fire after all."
👀🎯 [U+1F440 U+1F3AF]
design GearData "Targeting Computer" (made by Finch, Lucent, NiteLife), roles [processor, array]
"Somebody else's eyes. They still work."
🔑🏃💨 [U+1F511 U+1F3C3 U+1F4A8]
design GearData "Small Drive" (made by Lightsail), roles [injector, nozzle]
"Hotwired. Do not ask about the ignition."
📦🤫 [U+1F4E6 U+1F92B]
design CargoBayData "Store-All Plus" (made by Lightsail), roles []
"Contents not as declared."
Dry run. Pass "apply" to land the Pirates faction and 8 products.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
SimClock (engine-free) turns real frame seconds into whole fixed steps; ActionGameManager.Update feeds it outside the pause gate and Zone.Update(Time.deltaTime) is gone, with IntroCutscene (dead, Unity-driven pose writes). Entity.TurnRate is the sum of the rotation actuators' applied turn per sim second, zeroed each Update; DrawAhead reads it so EntityInstance and ShipInstance draw the latest sim state ahead by Lead. Unverified at this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sixteen faces nothing references but TMP Settings (E3); EmojiOne art is not
openly licensed. TMP Settings still names the asset until EmojiFont.Build
rewrites its emoji routing, so this commit leaves a dangling reference and the
next commits land the font that replaces it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
The first commit's new fact built a second Faction and Game() requires exactly
one; that commit's ALotsProductSurvivesTheRunSave failed (1004 of 1005 passed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brand no longer re-derives the product from maker and design (key-first pick);
CreateLot(product) is the one writer of Lot.Product. The census duplicate-brand
block and the key-order tie-break test go with the derivation they policed.
Unverified at this commit; build and tests follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entity.UpdatePerformance sets ThermalOnline only when performance exceeds EntitySettings.ShutdownPerformance.
A weapon at 0.006 performance at a hull's starting temperature passed the old "> 0" test but could never fire.
The threshold is read from production (new EntitySettings().ShutdownPerformance).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kills the five Stryker survivors of the first pass on LoadoutGenerator.cs
availability and preference. Tests only; builds against the prior commit.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
PdGearStatsAreAuthored reads the shipped catalog and pins every authored value of the three designs against the spec
derivation (ammo draw, velocity, visibility, range, pellet count, heat, descriptions and the rest). PdDesignsCarryNoMakerName
enumerates the makers from the catalog and rejects any in a PD design's name or description. PdGunHoldsASpeedDemon now sets
the catalog design's authored Tracking on the fixture weapon and asks FireControl.TrackingRate, instead of recomputing it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Folds the catalog-growth addendum and the emoji-pirates product facts into a
new section: probes CG1-CG12 and LP1-LP9, the maker profile, PD product,
generic-name and weapon product-line tables, the model page rows (Title is
the one owner of an item's title; DisplayName is not a thing) and rationale.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Folds the munitions-autofire addendum into Missiles and point defense:
probes MA1-12, the munition Track and autofire-subject model page row
changes, and the rationale for the evasion core, the split cuts and the
close-crosser divert test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Folds the faction-prelude addendum: faction-play probes BS1-3 and FP1-14
under Faction play, prelude probes PR1-11 and their rationale under Demo
scope, and the changed model page rows in both sections.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Folds the emoji section of the emoji-pirates addendum into Demo scope:
TextMesh Pro probes E1-E10, the font-asset rationale and rejected
alternatives, and the emoji and product model page rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Folds the demo-hulls and emoji-pirates (Pirates record r2, several package
products per hull) addenda into Demo scope: body facts H1-H11, the Pirates
record and product tables, the boss-hull rationale and the model page rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every item-title display site now routes through ItemManager.Title. The grep
for design-name reads under Assets/Scripts outside ServerShared leaves only
log lines, exception text, console parsing and the Type row.
Does not build-verified yet; the Unity compile runs at the end of the batch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Authored by AetherDb mine-launcher apply from the 532be3a5 catalog; the new blob is 9e75d9a4 (LFS).
Fixes the zero performance at a hull's starting 280 K; ShippedWeaponOperabilityTests is green from here.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AMineLayerLaysWithTheAimOutsideItsArc: an unfused mine layer passes ArcPermitsFire and lays with the aim outside
its arc. SolveFliesToTheTargetItIsGiven: Solve flies to the decoy it is passed, not the designated target.
ATargetOutOfArcGivesNoSolution also asserts the subject of a designated target out of arc.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AetherDb mine-launcher now corrects an already authored launcher to 173.15..473.15 K (optimum 249.65, plateau 36,
the sibling weapon's) and authors new ones the same way. Entity.StartingCellTemperature names the 280 the hull
cells start at, and ShippedWeaponOperabilityTests reads it against the shipped catalog. The catalog blob follows
in the next commit, so the test is red at this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It served its single run; Mine.prefab and the scene wiring are committed and reference plain materials only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deletes the AddPirates stand-in and its 21 calls. DemoTerminus_missing_cast_refused removes the shipped record from its scratch
copy first. LoadoutGenerator.HasHome is public so the catalog test asks production's own home rule. These tests are red on
a catalog without the Pirates: the catalog commit (pirates-faction apply) follows separately.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Pins the authored PD designs (hardpoint, shape, Tracking 40/40/20, at least three
products each, no maker twice, none by Miss Terri's), that Djinni and Longinus
hardpoints take them, and that the PD gun follows a 150 m/s crosser at 200 m
where ClearPath does not. Built and run in the next job.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Applied with: AetherDb pd-gear apply (dry-run text below), on Yggdrasil against a copy of the base catalog.
New designs:
Point Defense Gun Ballistic 1x1 mass 40 price 80000 durability 60 modifiers RapidFire
damage 1.5-4 count 1-1 range 300-550 cooldown 0.06-0.025 spread 1-0.3 velocity 1100-1600
energy 0.4-0.2 heat 40-15 visibility 400-150 ammo Ammo magazine 250 reload 3 tracking 40 roles [barrel, feed mechanism]
"Close-in defence gun: a hose of light rounds."
Point Defense Laser Energy 1x1 mass 30 price 110000 durability 30 modifiers None
damage 5-12 count 1-1 range 400-750 cooldown 0.2-0.1 spread 0.05-0 velocity 0-0
energy 6-10 heat 300-150 visibility 300-100 ammo none magazine 1 reload 1 tracking 40 roles [focusing array, power coupling]
"Close-in defence beam: quick, light, instant."
Flak Gun Ballistic 1x2 mass 120 price 180000 durability 80 modifiers Cluster
damage 30-70 count 6-8 range 350-800 cooldown 0.6-0.35 spread 4-2 velocity 900-1300
energy 1-0.5 heat 300-150 visibility 800-300 ammo Ammo magazine 40 reload 4 tracking 20 roles [barrel, feed mechanism]
"Small flak gun: a short-range pellet volley."
New products:
Point Defense Gun -> "Waykeeper" by Lightsail, every role mean 0.55 dev 0.1: Freight first, questions later. Keeps a hauler's paint unscratched through a full salvo.
Point Defense Gun -> "Rampart" by Zhestokost, every role mean 0.5 dev 0.08: A column does not dodge. It does not need to.
Point Defense Gun -> "Swatter" by AU, every role mean 0.5 dev 0.12: Keeps the rocks and the rockets off your back while you work.
Point Defense Gun -> "Catch-All" by R&D, every role mean 0.6 dev 0.15: Guaranteed to stop the missile!* *Missile defined as whatever it stops.
Point Defense Laser -> "Glare" by Lucent, every role mean 0.6 dev 0.15: Every missile gets its moment in the spotlight. A short one.
Point Defense Laser -> "Nightlight" by NiteLife, every role mean 0.55 dev 0.12: Lights out for anything with a warhead. Shares the bus with your blasters, so mind the meter.
Point Defense Laser -> "Wren" by Finch, every role mean 0.65 dev 0.2: Small, quiet and quicker than it looks, most days.
Point Defense Laser -> "Gnat" by Alakrita, every role mean 0.6 dev 0.14: Weighs less than your conscience and keeps the fast pass fast.
Flak Gun -> "MFer" by Zhestokost, every role mean 0.5 dev 0.08: Our shrapnel turns your enemies into scrapnel.
Flak Gun -> "Hailstorm" by AU, every role mean 0.5 dev 0.12: A wall of steel for crews who would rather not count missiles.
Flak Gun -> "sky full of teeth" by DME, every role mean 0.55 dev 0.2: light it up/ shake it loose/ nothing gets through tonight
Dry run. Pass "apply" to author 3 designs and 11 products.
Replay after apply: Point-defence gear is already authored; nothing to do.
Tests (EvasionTrackingTests): PdGearIsAuthored, PdGunsHaveHomes, PdGunHoldsASpeedDemon. Not yet run when committed; the next job reports them.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Dry run prints every record; apply commits (the field-kinds pattern). Designs are
deep copies of ClearPath, Spectra and DeathCluster with only the cut-spec stats
overridden; branding lives in the eleven products. Catalog not yet applied in
this commit. Not yet compiled when committed; the next commit's job builds it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Title derives the brand's product name and falls back to the design name.
The trade list, buy dialog and properties title read it; the product name
line leaves the properties body. Build not yet verified at this commit.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Unity 6000.3.24f1 batchmode; the three Substance materials fell back to the project's plain materials (Car Paint
Red, Car Paint Magenta, Steel) because .sbsar imports nothing in this Unity. Font-asset churn reverted.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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>
Supersedes the catalog blob of b60c695a (item without shape, curve or product, which broke six tests). The blob
is the file the verify job authored from the master catalog and ran 968 tests against.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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>
The RPM Display subtree (55 serialized objects) and the seven removed SchematicDisplay
fields in both instances (AetherDriveUi, the three RPM labels and fills). Removed by a
scripted edit of the scene YAML, not an editor re-save, to keep the diff to the
subtraction; Unity 6000.3.24f1 batchmode then opened the scene and compiled the project
with no error CS and no missing reference on either SchematicDisplay.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Tools need interior hull cells (the shrunk shape); (0,4) is an edge cell. The 2x1 array
turned clockwise takes the free interior column beside the cockpit.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Presence in the galaxy no longer gates gear (ruling gear-without-presence).
Delete the IsPrelude exception, Galaxy.ContainsFaction with its cache, and
the AetherDb loadout exclude-sellers mode that existed to exercise the
presence gate. An absent maker weighs as one jump beyond the farthest zone
the generator's zone reaches. Not yet verified; may not build.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Adapted the two boss-gate/demo-galaxy Capture call sites to pass 0 credits,
the value master's other tests pass when credits are not under test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first catalog commit left the item shapeless and unsold; this commit's mine-launcher command fixes both. The
catalog blob committed before this one is superseded by the next commit.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
AetherDriveData and AetherDrive, Union(12) (now a retired-do-not-reuse comment, as 26 is),
HardpointType.AetherDrive (last member, nothing shifts), Ship's drive sets, aggregates and
Axis write, the five request-field rows, GameplaySettings Aether multipliers, ShipInstance's
drive particles, SchematicDisplay's RPM display and AetherDriveUi, the undock refusal's drive
clause, the drive prefab and material, and AetherDb's drive roles and brownout row. The
design is kept at parked/aether-drive.
Adds EveryShippedShipHullHasATopSpeed. ARPG.unity still holds stale serialized references to
the removed SchematicDisplay fields and the RPM Display object; the next commit removes them.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Produced by AetherDb retire-longinusx apply on this branch's base catalog (63250cf2 in
the LFS store); dry run listed exactly the four records, a second apply is refused,
and AetherDb dangling reports 0.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The first dry run was refused by NameFile USA, whose name list holds longer strings
containing the words. A reference is a string equal to the id or the name.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The previous commit did not compile AetherDb (missing using) and dropped the
Python name while HardpointType.AetherDrive still existed, which ShipSchemaPinTests
pins. Both repaired; the name leaves with the enum member.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
MineInstance reads a Mine and draws it (latest sim body at the grid height, spin, real-time pulse by arming and
trigger); ZoneRenderer makes one per Zone.Mines add and plays the blast on remove; EntityInstance gives a mine
layer no effect manager. AetherDb mine-launcher authors the item; the catalog, prefab and scene wiring follow in
later commits, so this one does not build a playable mine (no MinePrefab yet).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Dry run unless "apply". Refuses unless exactly the four records are present,
refuses when any other record's bytes name one of their ids or the names
LonginusX or Traction, and finds nothing on a second apply. Deleted again once
the catalog lands.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Duel, Arcs, Launcher Angles and Starved Reactor fit and place the Longinus
(thrusters, Core Power or MoveOnPro at (2,4), radiators relaid); the modes start
a Longinus (Settings.asset StartingHullName; the Aether multipliers leave it).
The drive's tests, its Steering Axis probe and its Brownout fixtures go with their
subject; ship_cc.py and the live-game checklist drop the drive and the hull.
The drive itself and the catalog records are deleted in the following commits.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Question aetheria-release❓broadside-hull-source is raised on the spec. The Cut5 arc tests now
aim astern so only a solution can fire the gun (the aim used to be zero, which is free fire).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Bears is extracted from PFire; ArcPermitsFire passes on a fused gun, a solution, or a free aim in arc.
Solve flies along Solution's direction. ArticulationPoint loses its yaw clamp and follows a per-group
aim point placed from the group's solution. Arcs gets its new brief; Broadside is new.
Unverified: first commit of the cut, not yet built.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The merge brought tests that set Ship.LookDirection and relied on Ship.Update turning toward it. Master's helm split removed both. EvFace sets Aim and asks Steering.Toward for Turn each tick, the same law the old steering pass applied; the two multi-step loops now face per tick.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Soul gate closed at verdict cut-evasion-term.s5 (head 473a4bcc); remaining Low
test gaps are deferred to a follow-up. Merged under operator ruling
aetheria-release:ruling:completed-cuts-merge. Carries the migrated
GameData/Aetheria.cc (LFS sha256 63250cf2...7ae25); master did not change the
catalog.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Soul gate closed at verdict cut-sim-mines.s2 (head 06efde6f; branch already contained master, so the merged tree is the verified head). Merged under aetheria-release:ruling:completed-cuts-merge.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The helm split removed LookDirection. The ship-gravity test asserts only Velocity
after Update, so it needs no facing; Aim keeps the value the fixture carried
(Direction already defaults to the same +z facing).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Delete ABigGunHitsABigShipAtRange and TheCatalogMeetsTheRuledOutcomes (rulings cshot-rainbowlite-rate, evasion-angular-velocity-r2).
- TheJinkIsForgivenPerShipInThePrice: HitProbability of two guns of different rates carries PDeviation(Evasion, Tracking(shooter)).
- TrackingRate: an unlimited gun is +infinity whatever the gear reads, not inf * 0 = NaN.
- EveryAggregateAndTheCompensationReadLiveThrust: trim membership read from the thrusters' torques, not Ship's private list.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
MineLayerData (union 40) lays through InstantWeapon -> FireControl.Fire -> Lay;
Zone.Mines steps each mine as a KinematicBody after the entity loop, arms it,
triggers it on a hostile hull its blast disc touches (FireControl.Touches, the
coverage Detonate uses) and detonates it through FireControl.Detonate at the
fuse delay or lifetime. Hostile trigger, faction-blind blast, no owner leash.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Soul gate closed at verdict cut-floating-bodies.s3, head 6299627b, under
operator ruling aetheria-release:ruling:completed-cuts-merge.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Ship.Update no longer clamps Turn: Thruster.Axis saturates and AetherDrive.Axis
clamps, which is where the bound is enforced. TurnIsClampedToOne becomes
TheActuatorsBoundATurnBeyondOne and asserts the actuator bounds directly. The turn
rate is pinned against a value computed from each thruster's Torque, Thrust,
TorqueMultiplier and the hull's Mass; the behind-the-hull rule is probed at 85 and
95 degrees.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Data checks over the migrated catalog; no value is restated and nothing derives a rate from Shape.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
One-shot, dry run unless apply, refuses a second apply. Deleted in its own commit after the catalog lands.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Ship at +x in an empty zone with a slope: velocity after Update(delta) equals the
3035230b ship law scaled by delta * 60, at 1/60 and 1/30. Fails on `-=` and on a
changed delta factor.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Stryker survivor at FireControl.cs:211 (target.Position + source.Position) was invisible while the shooter sat at 0.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The law ships felt (normalize(f) * strength * (1/(1-|f|^2) - 1), gated at |f|^2 .001) is
now Zone.GravityAcceleration, per second via a 60 Hz reference rate; Ship.Update applies it
times delta, so ships push as before at 60 fps. Zone.GetForce's separate law is gone and
delegates to it. Floating-body drift at 60 fps now matches the legacy GridObject magnitude.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A ship's targeting gear carries its own item quality, so Tracking(shooter) differs between two ships fitted alike:
the gun-times-ship comparison needs two designs on one ship. The gunner now carries FastBlast+- and ColdFire, and
the mount fixture puts it back at the origin so the sweep is the geometry the test states.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Tool gear fits only interior hull cells and the Fire Control Array has no free one on a Longinus. The jink that
stops at t=3 s has been quiet longer than the 0.75 s window by t=4 s, so the Djinni reads 0 there: the jink is the
0.5 s half-period that was already used in the speed-demon rows.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
e360ceeb removed one line too many (public override bool Execute), so the drop commit and its two successors do not
build. The file is now exactly the base minus the 24 lines of Manoeuvre.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
AngularVelocityIsTransverseOverRange, TheMountHitsHalfAtItsRate, TrackingRateIsGunTimesShip,
LightAndHeavyGunsSplitOnAFastCloseCrosser, ABigGunHitsABigShipAtRange, TheJinkIsStillForgivenPerShip,
AShotFreezesItsMount, AnUnauthoredWeaponHasNoMountLimit. Rates are set on the loaded designs in code.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
PMount(AngularVelocity(source, target), TrackingRate(weapon, source)) multiplies HitProbability after the pFire gate;
Fire freezes it into PendingShot.PFire; Inspect and the debug HUD show omega, rate and the factor. TrackingRate is the
one product of the gun's rate and the ship's gear (ruling tracking-per-gun). Tracking(Entity) in metres still alone
forgives the changes of vector (ruling jink-per-ship).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Key 33 (first free across WeaponData and its subclasses), default positive infinity: an unauthored gun has a mount
that follows anything. The resolver's NaN fallback to Min yields +infinity for the degenerate lerp.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Ruling retire-longinusx. AetherDrive.Manoeuvre and the drive line in Ship.RecalculateEnvelope go: the envelope
sums thrusters only. TheDrivesReportReadsItsStateAndTheShipsSpeed, the EvGemini fixture and the drive-only asserts
are deleted. Sideways jinks and the shooters' target move to the generated Djinni; the gun platforms (FastBlast+-,
ColdFire) are EvLonginus, the Duel's guns on a Longinus. TheThreeShipsOrder becomes TheShipsOrder.
Not built or run on its own: verified together with the mount term at the head.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Holding heading after a state stops piloting (SteeringTests, and a Minion that
loses its target), Combat leading a moving target, MoveTo's Aim and Turn in the
low-delta regime, the behind-the-hull threshold, the Turn clamp, and the hold
branch clearing a stale MovementDirection.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Turn is a rate. Combat wrote it each tick and nothing wrote it after the Minion
returned to its root state, so the hull kept the last rate and spun without end.
The pilot tick now writes zero before the state runs; a piloting state overwrites
it with its demand. One writer per tick, no reset in Ship.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Soul gate closed at verdict cut-retire-unity-mines.s1 (head 17f5eb7d). Merged under aetheria-release:ruling:completed-cuts-merge.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling mines-back-in-sim-asap: delete now, return as a simulation body.
Mesh, materials and BigExplosion stay for the sim-mines presenter. GridObject
stays for the pickups until loot-1 r2. Comments in FireControl.Detonate and
HullCollider drop the deleted callers. Builds not yet verified at this commit.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Stryker on the batch's ranges left the corner faces, the wrap, the no-turn box support, the time before a face may thrust,
the zero line of sight and heading, the Observe guards and thresholds, and a step of no time alive; each is pinned here.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Stryker survivors on the first run: the low-delta branches of Accelerate had no behavioural test,
the velocity error was only exercised from rest, and astern was asserted by magnitude only.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The player writes Ship.Turn through HelmInput.Demand, and the FACE AIM mark shows while the latch is
on. Bindings keep their ids so saved overrides still apply; Face Aim and Toggle Face Aim are new
actions, and the wrapper is regenerated to match the asset.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Ship.Update reads Turn and feeds the rotation thrusters and aether drives; it no longer reads the
look direction or snaps Direction toward it. Steering.Toward is the one law from heading to demand
(a heading astern now turns at full demand). HelmInput owns the face-the-aim state. Accelerate,
Combat and MoveTo write Turn through Steering.Toward. Entity.LookDirection is renamed Aim in the
simulation, the Unity readers and the tests. Unity player input follows in the next commit.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Reach against a brute-force turn search (M26) on five envelopes, two windows, an oblique heading; a line of sight at
30/45/120/200 degrees rotated not reflected; evasion from the heading, not the velocity; the entity's track at the
solution window; the drive's offline gate and speed efficiency; the forget time as a function of the window, coasting
and burning; every Ship thrust aggregate and the strafe compensation against live thrust. TheSpeedDemonNumbers now
states what the code gives, to 0.5 percent.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Soul gate closed at verdict cut-station-services.s4 (head daa9a839). Merged under operator ruling aetheria-release:ruling:completed-cuts-merge.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ZonePack.BossSpawned records the spawn, since a dead boss leaves no entity. RunGoal.ExitOpen requires it, so an exit zone
without a boss stays sealed, and ZoneGenerator logs that configuration by field and code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Armour repair is priced at fractional wear (a fraction of a cell on every cell, and a thousandth of one cell), and
gear repair is priced exactly when RepairFraction exceeds SellFraction.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An asymmetric forward-only envelope pins the clockwise and counter-clockwise sides, the
mirrored direction, the two-sided average capped by the observed spread, and the track's
cross term.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Destroy the Djinni's right-pushing thrusters and the envelope loses its right push and
the turn they gave one way; reverse push and the Traction drive's turn rate are checked
against the ship's own flight.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An armour price one credit low survived: ceil absorbs it at the fixture's even hull price. The armour test now runs
the same wear patterns at five hull prices.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A price and durability sweep (1..3000 by 20, three fraction pairs) and the 360 at 7 of 20 case assert repairing then
selling never nets a credit. Armour tests wear several cells, partial and full strip, different cell values, and
check every cell is restored and the cost is the worn share of the hull price. A gear test pins the repair share at
odd and even prices.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Selling floors price * fraction * condition in doubles while repair ceilinged a separate expression, so the two
disagreed by one credit for some prices (GetPrice 360 at durability 7 of 20). SellValue is now the one sell
rounding, and RepairShare charges at least SellValue(whole) - SellValue(worn), so repair-never-profits holds exactly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ZoneRenderer adds an exit wormhole at RunGoal.ExitGatePosition; Interact asks RunGoal.ExitOpen and either
calls Win or names the boss that seals it. Die and Win share one EndRun.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EntityPack.Boss (key 21) carries Entity.IsBoss through save and Continue. GenerateZone adds one boss
per BossZones entry before the ambient return, flying the faction's BossHull or, with the gap logged,
its most expensive available ship hull. RunGoal (ServerShared) is the one owner of boss life and exit
state, derived from Zone.Entities.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every hit price gains one factor, PDeviation(Evasion, Tracking): how far the target can
and does move off the line of fire within SolutionWindow (0.75 s). Capability is Reach, an
eight-face closed form over the ship's ManoeuvreEnvelope (summed from what each live
thruster and aether drive reports with its own Execute arithmetic, read live: a thruster's
Thrust property is stale until it fires, which is why the Djinni moved 1.6 times its stats,
M25). Behaviour is ManoeuvreTrack, observed once per Entity.Update from the measured
acceleration; a steady burn, coasting and a dither much faster than the window do not
evade, and sideways speed alone does not (ruling evasion-speed). The commit price adds the
term at any flight time, so an instant weapon feels a jinking target. Forecast owns the
term for HitProbability and Inspect, which gain Evasion and PEvasion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DeviationProbability's inline saturate becomes PDeviation(deviation, tolerance), defined
for a zero tolerance as well. No behaviour change for a positive tolerance. Delete-first
step of the evasion term: every later deviation-like price reads this one shape.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The column's sort key was the raw design price; it is now the shown price, the sell price while selling. Buy affordability and the credit charge move to StationServices.TryBuy, so a purchase costing exactly the credits held succeeds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RepairCost adds the worn share of hull armour priced as that share of the hull's price, and TryRepair restores it. The per-unit-of-wear charge is the larger of RepairFraction and SellFraction. TryBuy checks credits with the same boundary as TryRepair and charges after delivery. A 100-unit ore sale pins the commodity sell scale.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The expected fit is the generator replayed from the staging seed, so a bare hull, a preset or one swapped item fails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kills the two survivors Stryker left in StationServices.cs: Any() to All() over the station's bays, and Concat() to Except() over the docking bays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kills the two survivors Stryker left in StationServices.cs: Any() to All() over the station's bays, and Concat() to Except() over the docking bays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Pirates hire the player, who is not one of them (ruling player-not-a-pirate);
the base Longinus with its default fit is the start hull (ruling
demo-start-hull-longinus). The hull is named in the script; StartingHull no
longer owns the demo's hull. The Pirates-hull test is replaced by one pinning
the Longinus, its Alakrita maker, a null ship faction and the origin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each reads the compiled scenario as the run start builds it: the listed scenario's Name, the staged player's faction,
hull and position, and the generated galaxy's zone count and links against the same cast at density one half.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DemoTerminus generates a prelude galaxy of the Pirates, Zhestokost, Lucent Media
and Aeronautics Unlimited with no quest faction, places the gate at the
antagonist's home (Galaxy.PlaceGate: boss zone and exit), and stages the player
in a Pirates hull with the cast's standing. The prelude constructor resolves
every cast name through one refusal that names the field, and places a quest
zone only when a quest faction is named. Release builds list only the demo; the
tutorial and main galaxies join the test arenas in editor and development builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hardcoded credits initializer and its stale scene serialization go. Buy charges the hull price it checked. Sell is a
mode of the trade list over the selected bay; Repair is a foldout option for the docked ship.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RunSave.Capture takes the credits; a scenario may set the starting amount, defaulting to the gameplay settings'.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings gain SellFraction, RepairFraction and StartingCredits. A sale pays the price scaled by condition and moves the
item into a station bay or refuses with everything unchanged; a repair restores every durability and charges the wear,
or refuses when short.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rewrites the release target for the demo cut (one gate, one region, one boss, a fixed cast of Pirates, Zhestokost, Lucent and AU, called a demo). Adds the map's Demo scope section with body facts D1-D6 and the scope table, and sweeps four stale docs the design review listed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Records the targeting index and Entity's press queries as built: their owners, inputs, outputs, forbidden writers, verification layer, and live seams. Marks L3 and L5 as historical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mining Cuts 1-3, the general targeting index (ships and belts), and next/previous/reticle as index queries on Entity, with master merged in and the catalog's field kinds rebuilt. Soul held mining-index-pins, mining-target-queries (two passes) and mining-merge; the Unity compile and both ships smokes pass. Mining yield (Cuts 4, 5, 7) is not in this merge.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GameData/Aetheria.cc 70a7b0a9 -> 78fe2afe (12703072 bytes): one FieldKindData
record, Asteroid, cross-section 5, weight 1. Run on Yggdrasil from the merge
commit's tree, output pulled back byte-identical. Fixes
TheShippedCatalogHasAPickableFieldKind and makes master's strict Exact weapon
assertion hold on the rewritten catalog.
Builds: both commits build; the previous commit fails one test by design, this one passes 837/837.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mining owns belts, chunks and target picking; scenarios owns admission and run
staging; master owns ships and fire control (rule scenarios-cut.md S4).
- AetheriaStores.CatalogTypes: nine-type union, master's list plus FieldKindData, no Scenario.
- tools/AetherDb/Program.cs: both cases (ship-authoring, field-kinds), one usage string.
- Zone.cs: master's MineAsteroid dropped (mining deleted its inputs); scenarios' SettleBelts and its three calls deleted.
- RunStartTests: the two belt-task tests deleted (they read belt.Transforms and MineAsteroid).
- FireControlCut124Tests: master's strict Exact assertions; mining's CompatibleDrift allowance and its comment dropped.
- GameData/Aetheria.cc: master's pointer 70a7b0a9. This commit lacks the Asteroid field kind in the catalog, so
TheShippedCatalogHasAPickableFieldKind fails here by design; the next commit rebuilds it.
Builds: this commit builds (verified at the end with commit 2); the catalog pin test fails at this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A second density-equal belt size and reach, held-target presses and the reticle at three headings,
each ceiling within about 10% of the measured count; the shared ceiling drops from 1500 to 700.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pins two Stryker survivors: Entity.Order's field comparison and DistanceKey.Bound's forward bound.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The four handlers in ActionGameManager call Entity.TargetUnderReticle, TargetNext,
TargetPrevious and TargetNearestEnemy. TargetCandidates, which built, sorted and
searched every visible ship and rock per press, is gone. Previous with no target
takes the farthest.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tutorial Galaxy and Main Galaxy are scenarios; six test arenas show in editor and
development builds. RunStart owns new-run staging; Zone.Admit stays the one admission.
Galaxy zone linking moves to a double-precision Delaunay (the vendored float hull
dropped zones; ~2% of galaxies threw). A fixed seed fixes every fit; a failed start
keeps the saved run and says why. Soul s1 and s2 held (cut-scenarios-menu.h1, h2).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Launch cleared the run store before generating, and a generation or staging failure only reached the log: the
Generating Galaxy dialog stayed up, StartGame threw, and the saved run was already gone.
RunSave.Replace runs a start with the saved run still stored: when it returns a staged run the saved run's records go,
and when it returns null or throws, only the records it wrote (the arena's orbits and bodies) go. StartGame's
new-run branch is StartScenario, chosen by PendingScenario rather than by the absence of a SavedGame; it mints into an
empty ledger, and a failed start clears CurrentGalaxy (so quitting saves nothing) and refuses on the main menu with
every failure. Launch no longer clears; a galaxy that throws is refused on the title menu. The menu offers Continue
whenever no run is live, which replaces MainMenu.InGame (cut, with its prefab value and scene override), so a
failed start can resume the saved run in place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ScenarioStage.Generated picked from ItemManager.Random, which is clock-seeded per process and advances in play, so a
relaunch of Djinni Shakedown or Long Haul in one session gave the player a different fit; every branded lot's
workmanship and role fills came from the same clock stream, so arena gear and explicit fits varied in quality too.
Galaxy now keeps the seed it was generated at, and RunStart.GenerateArena and Stage each reseed the item manager's
random from it, one stream per step, so what runs between them cannot shift either.
SeedRules relaunches every fixed-seed scenario around a clock-free Main Galaxy launch and a scrambled item random,
and compares the galaxy (links, factions, homes, exit too), the arena down to each item's maker and workmanship, and
the staged fits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The vendored MIConvexHull (NIH, ported from double to float) returned triangulations that left whole sets of
interior zones with no cell at all (prelude seed 103: 22 of 64 zones; main seed 4247651650: 182 of 196). Those
zones had no link, Distance lacked them, and the constructor threw KeyNotFoundException placing the antagonist
home (prelude) or the entrance (main). Galaxy was its only consumer; Delaunay.Edges (Bowyer-Watson, double) replaces
it, and the 3,896 vendored lines go.
The three RestoredHullsTests loops that skipped seeds whose galaxy threw now require every seed to build, and the
stale comment that Galaxy mutates Faction.InfluenceDistance goes with them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MarkovNameGenerator kept every name it ever gave in a static list, so each later galaxy in a session drew different
zone names at the same seed, and ZoneGenerator seeds a zone from its name: a fixed-seed scenario relaunched in one
session got a different arena. Galaxy now passes one used-name set to its factions' generators. The unused Reset goes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Scenario is an abstract C# class compiled with the game: Generate picks the galaxy through GalaxyStage (Main or
Prelude, at the scenario's seed, the background noise drawn from the same seed), Stage places the run's first
entities through ScenarioStage (Fit, Bare, Generated, Preset, Player, Place, Cargo), which collects named failures
and admits nothing. RunStart generates the galaxy, the arena under Ambient and admits everything through Zone.Admit,
all or nothing. The aetheria.scenario record, ScenarioShip, ScenarioEntity, RunStart.Check and the null-scenario
branch are cut.
New Game opens a submenu: Tutorial Galaxy and Main Galaxy, then (editor and development builds) Djinni Shakedown,
Starved Reactor, Arcs, Duel, Launcher Angles and Long Haul, each with its brief. MainMenu.Launch is the one way into a
new run; ActionGameManager.PendingScenario carries the scenario to StartGame once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IsTutorial duplicated Galaxy.IsPrelude; its readers read CurrentGalaxy.IsPrelude, and RunSave.Capture writes
SavedGame.IsTutorial from galaxy.IsPrelude with no isTutorial argument. TutorialPassed was never written and its only
reader was New Game's unreachable main-galaxy branch, which goes with it (S2 brings the main galaxy back as a
scenario). spawnturret's two identical generator branches collapse to one. Old player.cc files still load.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A prelude halved every faction's InfluenceDistance on the shared catalog object, so each later generation in the same
session, prelude or main, started from an already-halved value, and a continued prelude read the unhalved one. A
fixed-seed scenario relaunched in one session therefore could not reproduce its galaxy. Galaxy.InfluenceOf derives
the reach from IsPrelude; Galaxy and ZoneGenerator read it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Division of labour (Studio edits hull semantics, Blender owns space, strokes
and the body), Line Art strokes into Package's capture, the shared ship.cc with
a Studio disk guard as the bridge, mount gizmos and a read-only overlay, Thing
readiness facts and question ship-authoring-host, six cuts and their order
against the .cc move.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruling ship-data-all-in-cc. One ShipModel record (nodes, per-attribute
mesh streams, submeshes, surfaces, images with format and levels) at
mod-model:<id> replaces ship.glb and the planned PNG sidecars. C# seeds
and judges it, Python packs it at Package, Unity builds Mesh and
Texture2D from it, and glTFast leaves. Body facts M26-M30, sizes, the
authority map, questions ship-model-owner and ship-image-encoding, cuts
ships-cc-model, ships-cc-package and ships-cc-image-bc, and the specs
it supersedes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bind called bpy.path.relpath after setting aetheria.asset_kind and aetheria.id, so a .cc on another drive than the
.blend raised and left the collection half-bound, and an unsaved .blend stored a path relative to the working
directory. The stored path is now computed first: absolute for an unsaved .blend, '//'-relative when relpath succeeds,
absolute when it raises ValueError. Every reader already resolves both through bpy.path.abspath.
smoke_package.py gains the bind cases: unsaved stores absolute and survives two Save As moves, a failing bind leaves
no aetheria.* property, same-drive stores '//'-relative, and with SMOKE_OTHER_DRIVE the final bind crosses drives
before the package passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
smoke_package.py builds a primitive ship from role-tagged objects, creates it --like Djinni, saves a layout with an
internal reactor, packages it and requires validate to pass; then swaps the thruster disc for an empty and requires the
refusal naming the anchor; then restores and packages again so the left-behind package is good. Proof gate 2's
outstanding authoring action is now Package.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every object tagged aetheria.role becomes a ShipAnchor whose Id and ModelNodeId are its aetheria.id (a muzzle's ParentId
is its parent's). The collection, Grease Pencil excluded, exports to ship.glb with custom properties as node extras;
replace_visual writes the anchors, a lone Grease Pencil object's lines are captured, and AetherDb's verdict shows in the
panel. The add-on preference 'Aetheria repo' names the checkout; by default it is the nearest folder above the .cc.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MODEL_ASSET_SLOT, ANCHORS_SLOT and ANCHOR_MEMBERS sit beside the other slot constants and ShipSchemaPinTests binds them
to ShipAuthoring's and ShipAnchor's [Key]s. replace_visual carries an anchor's slots past Order by Id, refuses a blank
or duplicate id, a wrong-length row and a changed ship id, and leaves the hull record and the lines alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ShipAuthoringStore.HullLike copies a reference hull through its MessagePack round trip and resets Name, Shape,
Hardpoints, Prefab, Schematic and Visual. 'create ... --like <name>' opens the shipped catalog read-only and refuses an
unknown or non-ship hull. 'validate' runs ReadPackage when ModelAsset names an existing file and says which it judged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ReadNodeIds is the one GLB parse and now records, per aetheria.id node, whether it carries a mesh. Bind refuses a
thruster-emitter or radiator-mesh anchor whose node has none, naming the ship and anchor (ruling
thrusters-radiators-are-meshes), so Compose and validate refuse such a package before the catalog sees it. AetherDb
'ship-authoring validate' judges the GLB when it exists (next commit).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three tests in MiningIndexTests, no production change. RegionsStayNearTheSearchArc bounds the share
of a small search's regions that lie past reach on both sides (window widened, nearest loosened);
VisibilityBoundPrunesADimBelt bounds rocks examined against rocks in reach in a half-lit belt of
largest rocks; ARockExactlyAtReachIsFound searches from 1-300 units with reach set to a rock's exact
distance, a third of them radially outward from a belt's outermost rock. Each threshold's unmutated
and mutant values are in its comment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Blender-authored material graphs judged by AetherDb into a catalog record,
lowered to a CultMath expression IR, generated into compute kernels and baked
per (hull, livery) with per-ship wear only. Body facts M19-M25 (Blender pin,
Cycles point oracle, dump, interpreter bench, compute substrate, faction key
clash, CultMath seams), the authority map, parity chain, tape relation, cuts
and rationale.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruling targeting-sublinear-wanted-not-critical supersedes the reading that
rejected it. mining-index-tree makes the provider seam a region tree with a
heap-ordered Best and a detection gate; mining-belt-cells rebuilds the belt
as rings of about-square cells in co-rotating frames, refreshed by drift.
Target: per-press cost grows as sqrt(rocks in reach), the exact-query floor,
measured on F1's fixed belt at 30k, 300k and 3M. Both land after
mining-merge and gate nothing. Forks: reticle-exactness, belt-size-tiers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruling targeting-density-scaling-accepted accepts the index's growth with
rock density, so the sublinear region structure is recorded as rejected
under it. mining-index-pins adds the three tests for Soul's
cost-loosening-unpinned and exact-reach-unpinned findings and lands before
mining-target-queries, which stands as specced.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soul's scenarios-adopt probe S4 as a committed test: on generated ships,
stations and turrets every weapon, thruster and radiator holds a non-null
Hardpoint containing all its cells, and Tool gear and the hull hold none.
It pins the input the muzzle, arc, thruster-emitter and radiator readers
dereference (finding headless-reader-input).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The one conflict, ShipInstance.cs's thruster emitter lookup, keeps the
branch's thruster.Item.Hardpoint.Transform and master's new
ThrusterHardpoint.WireExhaust(particles, thrusterHardpoint). Master's side
as written would have brought back the origin-cell hardpoint lookup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruling hull-paint-mask-texture (option texture-sidecar). The material model
now samples ship.mask.png on UV0 (RGBA8, linear, DXT5 with mips, loaded by
ImageConversion; the sidecar rather than GLB extras, with the reason). M18
records Blender saving a byte mask raw. The per-hull steps add retopology,
a clean unwrap and the Selected-to-Active rebake, list who does each step,
and leave the time budget to the operator. Select Debris, the mesh report
and the triangle warning are dropped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The one conflict is EntityInstance's SoundBank block, which this branch had
moved onto item.Hardpoint and ship-mounts deleted: it stays deleted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The emitter carries the invisible material, whose alpha is 0, so an exhaust
prefab left at Unity's default (Use Mesh Colors on) emitted nothing. The
shipped thruster prefabs only worked because theirs is off. ThrusterHardpoint
.WireExhaust now owns the wiring and turns it off; ShipInstance calls it. The
preview smoke wires a default particle system to each emitter on the clone and
requires every particle emitted and none transparent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PreloadAsync rethrew on the first failing hull, so one bad package faulted
Loading and no mod ship could spawn. Each hull now loads in its own try: a
failure destroys its partial visual, is logged with the hull's name, and the
rest still preload, as Compose skips a bad package. The play smoke installs the
package beside a broken sibling that composes but cannot assemble (its map icon
moved onto a meshless muzzle node) and proves the sibling has no prototype while
the good ship spawns.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
forceRenderingOff is not serialized, so Instantiate and SaveAsPrefabAsset dropped
it and every cloned mod ship (and every FBX-built prefab) drew its collision hull.
The collider renderer now takes the template's invisible material, as the thruster
emitter does. The preview smoke checks the collider on the clone and the prefab
smoke checks it on the configured prefab.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BeltTargets drops its own reach skip (TargetRegion.CanHold owns it) and an
empty-belt guard AsteroidBelt already makes unreachable. Tests add an
off-centre sun, whole-belt searches across the antipode, cost after time
passes, a late short step that re-keys nothing, and an entity search off the
origin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rulings hull-runtime-materials and thrusters-radiators-are-meshes. Body facts
M13-M17 (probed: glTFast readability and vertex colours, Blender's named
colour export, the Tripo hulls' parts, UV islands and albedo clusters), the
hull material model (Aetheria/Hull, a glTFast material generator, COLOR_0
paint regions, factory and faction livery), the per-hull prep table and time
budget, the Tripo PBR check for the operator, and the cuts it adds or revises.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Zone.Targets (TargetingIndex) answers Within and best-first Best over
ITargetProvider regions. EntityTargets offers the observer's VisibleEntities;
BeltTargets keys each belt's rocks into kinetic bands and bounds each segment's
annular sector for distance, bearing and visibility. Entity.VisibleChunksInReach
queries the index with DetectionReachPerVisibility, an upper bound from the
sensors' hull. Zone.ChunksNear and AsteroidBelt.InnerRadius are deleted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soul's scenarios batch-4 probe P30 lived only in a session scratchpad. Its
fixtures are now tests: an L gun in an L hardpoint whose origin lies outside
it, an edge gun whose origin lies in a neighbouring radiator hardpoint,
TryFindSpace finding what ItemFits accepts (unrotated and turned), reload into
the same hardpoint, and Tool gear holding none. Three batch-4 minors are
pinned too: IsFilledBy against a cell oracle written in the test, the last-row
placement search, and billing independent of heater equip order.
The file is a part of RunStartTests, so it shares that fixture's scratch-hull
and catalog helpers instead of a second copy of them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The placement rule (HardpointAt) already finds the one hardpoint an item lies
in at equip; TryEquip threw that answer away and eighteen readers recomputed it
from the item's origin cell, which need not lie in the hardpoint (Soul's
scenarios batch-4 P30). EquippedItem.Hardpoint now carries the answer, null for
Tool gear and the hull, and every origin-cell lookup reads it instead:
FireControl.ArcFor, the eleven weapon managers, EntityInstance, ShipInstance
and ActionGameManager's articulation grouping. Entity.Hardpoints[,] stays the
per-cell map for armour and display.
TryFindSpace's hardpoint search tries every origin the fit rule can accept,
including ones up and left of the hardpoint, so a design whose shape lacks its
origin cell is found wherever ItemFits would place it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kills the Stryker survivor that armed every weapon with any muzzle on the ship
(the && in the per-weapon muzzle check turned into ||): a second weapon mount
with no muzzle is refused even when the first has one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The one conflict is AetheriaStores.CatalogTypes: keep ShipAuthoring from the
ships lane and Scenario from the scenarios lane.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Body facts L1-L9 (Soul's P30, merge-tree probes, the belt freeze, the loot
path, a catalog probe of weapons, ammunition and the Tractor Beam), model
page rows, the merge order of every lane and the faction-play cuts, and the
rationale for the cut specs admitted to the mind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
forceRenderingOff is not serialized, so the prototype's clone drew the
emitter again; the preview smoke's clone check caught it. The emitter's
materials become the template's invisible material, which survives
Instantiate and which EntityInstance keeps as the renderer's own material.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruling thrusters-radiators-are-meshes: thruster and radiator mounts are meshes;
the thruster's is not drawn and is the surface its exhaust emits from, the
radiator's is drawn. The assembler already required a mesh for both; it now
turns rendering off on the thruster emitter, and the preview smoke checks it.
The authoring doc states the mount rules and the mesh rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ShipAuthoringStore.Validate now alone decides which hardpoints carry anchors
and which role each plays. A thruster, radiator, energy, ballistic or launcher
hardpoint needs an anchor of its mount id with role thruster-emitter,
radiator-mesh or the new weapon-mount; every other hardpoint is internal and
no anchor may take its id. A mount-role anchor must be a hardpoint's mount, a
muzzle must hang off a weapon mount, and each weapon needs a muzzle; these
checks move out of ShipModPlan.Build, which now only maps a validated pair to
slots and throws nothing itself. 'articulation' stays in the vocabulary for
pivots and is never a mount.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The array's only reader was the dead SoundBank block in EntityInstance, which
looked up a hardpoint transform and did nothing with it. ShipModPlan.Equipment,
ShipModShips' assignment, the preview check, the FBX builder's HP.Equipment
case and its smoke assertion all fed that one dead reader. An HP.Equipment node
is now refused by the builder as an unknown hardpoint kind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Body facts M1-M12 (merge probe, lane conflicts, the mount ruling still
only in docs, packages reachable only through the console, the grid
rule, Blender export axes probed, the Tripo hulls in Quiet.blend, the
missing radiator material, the unproven built player, LFS gaps, the
unpinned CultLib path). S2's owed checks split into agent-provable and
operator-only, the merge plan, the Blender package action designed for
one hull a day, S3 with Headliner first, and what the strand needs from
the variants campaign. Cuts ships-merge to ships-player and questions
ships-merge-gate, release-hull-home and s3-player-build are in the mind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F14: only three weapons draw rounds, generation stocks none and stations
sell none, so the Zhestokost tender loop needs its weapons to draw a
commodity first (question zhestokost-ammunition). The proof splits into
four cuts along the primitives, each under a Hands budget.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The map gains "Faction play: the NPC scripting": body facts F1-F13 on the
agent graph, tasks, perception, hostility, target writers and generation;
what each extension point can and cannot carry; seven primitives (doctrine,
flight, engagement with grace and hail, slots and the duel, anchor and
leash, roles with break-off and rearm, follow and flee states) and three
after the proof; model page rows; rationale; cited prior art.
The target's faction-play strand cites ruling npc-scripting-may-grow and
names the three proof cuts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The target defines what the release is, the invariants, what is out of
scope, the four strands (feedback, audio, content pipeline, faction play),
the sequencing, and how this campaign avoids the Codex lanes in flight. The
map records the body facts behind the first feedback and audio cuts, the
model page, and the rationale for the recommended audio route.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- AFirstLoadKindIsSavedFromACleanStore: FieldKinds.Ensure's kind is saved
when the store was clean at load (ZoneData.cs:162).
- SetTargetRefusesALitChunkBeyondSight: a lit rock too far to see is
refused (Entity.cs:345).
- TheChunkQueryMatchesBruteForce: 3000 random queries, off-origin, two
belts, across the outer skip edge (Soul F7).
- ThePingArmIsUnchangedByTheGainExtraction: Soul's ping sweep, golden
recorded at cb4a1dbd; kills the Sensor.cs:168 and :189 mutants.
- TheShippedCatalogHasAPickableFieldKind (Soul F2).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Weapon.CanMine is the one mining predicate; LockWeapon overrides it to
false. Entity.VisibleChunksInReach counts only weapons that can mine.
Tests: LaunchersDoNotCountTowardChunkReach (a launcher-only ship is offered
no rock; a gun of 200 beside a launcher of 400 is offered the near rock
only) and AnOfflineWeaponAddsNoReach (Soul F8: reach reads Active, not
Enabled).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soul's S6. Generation always gives Zenith's 16-cell Reactor hardpoint a reactor
that fills it though smaller ones fit; a generated Djinni's eight thrusters
share one lot; across 5 K either side of a heater's target the bus bills it
exactly when its thermostat is open; and a thermostat gates only its own
behaviour group (a two-group fixture: the closed group's 10 is not billed, the
other group's 7 is).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soul's S3 and the S2 ruling ("B is the design intent, saving slots in a
hardpoint can be a valid tradeoff for crowded ships if you really need to fit
an extra tool"). Entity.HardpointAt is the one placement rule: at an origin,
the item lies within a hardpoint of its type at that hardpoint's rotation
(HardpointData.TakesAt), its own cells are free, and that hardpoint holds no
other hardpoint item. The origin no longer has to be a hardpoint cell, and
TryFindSpace tries every offset inside a hardpoint, the offsets the fit rule
tries, so an L-shaped hardpoint takes its L design instead of generation
throwing. Leftover hardpoint cells may hold general gear, and a hardpoint still
holds one hardpoint item.
Tests: an L hardpoint takes its L design by hand and in generation (Soul's
P11); a turret's gun goes back beside the tool gear in its leftover cells
(P21); a second gun in a roomy hardpoint is refused; a saved entity reloads to
the same loadout with its items in reverse order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling, 2026-09-30: "removing the 11 self-entries was my intention".
IsAvailable, the one reach rule, now says a faction always reaches its own
manufacturer's gear, and a transient `AetherDb allegiance-self [apply]` drops
the self-entries from the eleven factions that had one (Rossum & Douglas never
did, which is why its stations could not carry its own gear).
Tests: in a main-sector galaxy every faction is offered all it makes and no
allegiance map names its own faction; and, through a fixture faction outside
the galaxy with no self-entry, its own product is on offer to it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling, 2026-09-30: "Keep the variety, not every galaxy will have
Zhestokost in it." A transient `AetherDb station-reactors [apply]` gives each
manufacturer that makes reactors a design for Zenith's 16-cell Reactor
hardpoint, scaled by cells from its largest reactor with every other value,
stat term, role, temperature and sound bank its own, and a product by it:
Vulcan Station Reactor (AU, from Vulcan 12 cells x1.333) charge 66.7..266.7, mass 666.7, price 1,550,000
Notorious Station Reactor (Lucent, from Notorious 9 x1.778) charge 711.1..2133.3, mass 1333.3, price 1,800,000
MoveOnPro Station Reactor (NiteLife, from MoveOnPro 4 x4) charge 100..500, mass 1200, price 3,600,000
Core Power Station Reactor (R&D, from Core Power 4 x4) charge 400, mass 200, price 1,000,000
products: Vulcan Station, Notorious Station, MoveOnPro Station, Steadfast Station
The Manhattan-derived one is renamed "Manhattan Station Reactor" (product
"Manhattan Station").
Reach is read through the one rule, LoadoutGenerator.IsAvailable. Test: a
faction allied only to AU, Lightsail and NiteLife is offered none of
Zhestokost's products and AU's, its stations carry nothing it cannot reach and
nothing by Zhestokost, and they are powered.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AFailedBeltTaskSurfacesOnce swapped a belt's asteroids while that belt's task
could still be running, so it failed in 5 of 8 runs including unmutated ones
(Soul S1). It tests belt-task machinery that codex/mining deletes (Cut 1,
22a54ccb), so it goes rather than being reworked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- SensorGainExtractionChangesNothing traced a saturated sensor: a hull's own
heat makes a ship ~800 bright, so the observer's info sat at 1 every tick and
the hash pinned nothing (a hand probe dividing by the bearing response, and
one pricing a ping as a passive tick, both survived). The scene now observes
through a weak sensor (Dim eye, sensitivity 0.01) and asserts the trace is
unsaturated and carries a visible ping. Golden re-recorded against the
pre-extraction Sensor.Execute (cb4a1dbd with this scene patched in); the
post-extraction code gives the same hash, 919B7EE7...
- Cycling reach fits its guns 250, 120, 350, so the last active gun is not the
longest (a "last weapon wins" reach survived the old order).
- The weighted pick uses three positive weights, so a pick that falls through
to the last kind is told apart (it survived with two).
- The chunk query runs around a belt centred off the origin.
- TargetRef hashes an entity target and None.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling B, 2026-09-30: "a station with no access to Zhestokost gear
should not have Zhestokost gear." Test: in a main-sector galaxy (not the
prelude, which offers every product), every faction's generated stations carry
a heater and a reactor, and nothing, equipped or stocked, whose manufacturer is
outside the galaxy or the faction's allegiance.
With today's catalog every faction's allegiance names every faction (weights
only bias preference) except Rossum & Douglas, which leaves out itself; the
test killed a reach that ignores allegiance through an R&D station carrying
R&D reactors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soul's F5 and F6. The asteroid-belt tasks read the clock, the orbits and each
belt's damage and respawn state. Update advanced the clock before waiting on
last tick's tasks (Soul measured 242k transforms placed for the next tick in
576 of 3000 ticks), MineAsteroid wrote damage and respawn state under a
running task, CreateOrbit and AddOrbit wrote the orbits, and a task that threw
stayed in the list, so Wait rethrew it on every tick after.
SettleBelts waits for the tasks and clears the list even when one failed; Update
settles before it advances the clock, and MineAsteroid, CreateOrbit and
AddOrbit settle before they write.
Tests: over 3000 ticks every asteroid of a belt is placed for the time of the
tick that started its task; mining every asteroid of a belt, chipping and
breaking each four times a tick for 1000 ticks, never races the task; a task
that fails surfaces once and later ticks run clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bus billed every active IPowerConsumer, but a behaviour group stops at its
Thermotoggle: a heater above its target never runs its EnergyDraw, yet its
full draw (1000-2500/s) was charged every tick. On an idle station that kept
the reactor in permanent deficit, overloading at overload efficiency. The bus
now skips a consumer whose group has a closed thermostat ahead of it, the same
rule it already applies to inactive items; Thermotoggle.Open is the one
statement of when a thermostat is open, and Execute returns it.
Tests (Soul's F2; S17 survived before): from a hull at one uniform temperature,
one tick below the heater's target raises its cells and one above does not, so
the heater's own contribution is measured with no reactor heat in it; and the
bus bills the heater's draw below its target and not above.
This does not keep an idle station warm (Soul's F1, reported separately): the
reactor's throttled idle heat alone still drives it past its plateau.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling, 2026-09-30: "Hardpoint fit is loose: nobody's gonna stop you
from putting a small reactor in a large reactor's hardpoint." There is one rule,
HardpointData.Takes: gear of the hardpoint's type fits when its shape, at the
hardpoint's rotation, lies within the hardpoint's cells. TakesAt is the same
rule at one offset, and Entity.ItemFits (equipping, save load, presets) now
asks it instead of repeating the containment test. Generation's hardpoint
picks, HasHome (so station stock and loadouts), product reuse and `AetherDb
hardpoint-fit` all read Takes.
Generation keeps its own placement preference, named IsFilledBy and not a fit
rule: it offers gear that fills the hardpoint when any is on offer and falls
back to anything that fits only when none is, so loadouts change only where a
hardpoint used to stay empty. RestoredHullsTests pick fillers through
IsFilledBy.
Consequence: the Autocannon, Vulcan, scorched void policy, Notorious, pretty
pretty bang bang, Manhattan, plight, DeathCluster, Small Drive and pswarm now
have a home and are offered. Still homeless: CShot RainbowLite Lazer,
ChargeBlast SG, ChargeBlast+-, OK Disperser.
Tests: generation offers exactly the sold hardpoint gear some hull fits; a 7x7
design no hull fits is offered only once a hull that takes it exists; a 5x5
Reactor hardpoint nothing fills still gets a reactor; equipping and the rule
agree for every hull, hardpoint and design; the homeless list is stated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soul's F4: LRMM72, SRMM72, Autocannon, pswarm and plight had DamageCurve ==
null, and once they had products NPCs were generated with them and
CombatState.SampleDps threw at InstantWeapon.cs:64 / ChargedWeapon.cs:81.
A transient `AetherDb damage-curves [apply]` copies each one's curve from its
nearest comparable weapon, same class first, then closest range and damage:
Autocannon <- DeathCluster (auto-fire Ballistic, 4 cells, range reaching 1000)
LRMM72 <- GT 3K (the only other unguided launcher)
SRMM72 <- scorched void policy (guided; 750 range and 128 damage inside its 500-1250, 128-512)
pswarm <- scorched void policy (guided; identical damage, range, cooldown)
plight <- ChargeBlast+- (charged Energy; nearest range)
Every curve in the catalog is the same one, keys (0, 1, 0, 0) and
(1, 0, -1.7656243, -1.7656243): full damage at minimum range falling to none
at maximum. Test: every weapon a product sells has a damage curve.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ChunkInfoMatchesAnEntityHeldStillUnderTwoSensors pins that the settled value
divides the summed rate by the number of sensors as well as the decay.
- ChunkInfoIsCappedAtOne pins the saturate the per-tick rule applies.
- AChunkTargetBearsThroughItsOwnPosition pins the arc gate reading a chunk's
planar position (x and z, not swapped) and that a chunk never safes a weapon.
The fixture's settings gain a 90-degree default arc for it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling, 2026-09-30, on the Autocannon: "Leave the autocannon unsold,
we'll need to author a bunch more hulls before all the gear variety in the game
has a home." The fit rule gets one owner, HardpointData.Takes (the gear's type,
laid in at the hardpoint's rotation, filling every cell); LoadoutGenerator's
hardpoint picks, its reuse of a previous product and `AetherDb hardpoint-fit`
all read it. RandomProducts, which both station stock and NPC loadouts draw
through, offers only gear with a home: Tool gear, hulls, or hardpoint gear some
hull in the catalog takes. The Autocannon keeps its product and price.
Test: the Autocannon is never drawn in 2000 draws of its hardpoint type; after
a scratch hull whose hardpoint takes it is authored, it is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Update cleared `_updatedOrbits` and only then waited on the previous tick's
asteroid-belt tasks, which read the cache through GetOrbitPosition. A task
still running could see the set mid-clear ("concurrent update ... corrupted its
state") or recompute an orbit into last tick's position. The wait now comes
first.
Test (Soul's orbit counter, committed): 3000 back-to-back 0.02 s ticks of a
quiet arena, and no orbit stands still against its parent. Clearing first gave
500+ stalls or the corruption exception. Measured against the parent because a
moon can genuinely pause in zone space: one such reading (tick 1321, a moon
whose motion cancels its planet's) is real motion, not the race.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling, 2026-09-30: "Yep, we need a station reactor." Zenith's 16-cell
Reactor hardpoint had no design to fill it, so generated stations had no power.
A transient `AetherDb station-reactor [apply]` authored it from Manhattan, the
largest and most industrial reactor, scaled by cells (16/9):
Station Reactor (GearData, Reactor hardpoint, full 4x4)
mass 500 -> 888.9, price 750,000 -> 1,350,000 (to the nearest 50,000),
charge 500..1000 -> 888.9..1777.8; every other value, the efficiency,
overload and throttling stats and all stat terms, the roles (core,
regulator), temperatures, durability and sound bank are Manhattan's.
Product "Station Reactor" by Zhestokost (Manhattan's maker), default role
quality.
Station generation fills the slot through its ordinary hardpoint pass. Tests:
every generated station carries one heater and a reactor; an idle station (a
quiet arena, nothing else running) holds its heater's cells above freezing over
the second five of ten minutes (unpowered they fell to ~247 K); a station hull
with no room for a heater fails generation by name instead of skipping it
(Soul's S11 survived before).
The station's border cells still sit near 265 K with the heater and reactor
running (the reactor core reaches ~433 K); how warm the whole hull should run
is left as a tuning question.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling, 2026-09-30: an idle station has no heat source and cools
toward freezing and invisibility, so station generation (LoadoutGenerator, the
one owner of station loadouts) equips one heater after the docking bay, from
the products whose design is Tool gear with a low-pass Thermotoggle and Heat,
the cockpit's thermostat composition. It is required, like the docking bay.
Not yet warm: generated stations have no reactor (no reactor product fits
Zenith's 16-cell Reactor hardpoint), so the heater's EnergyDraw gets no supply
and the station still cools (280 K to ~247 K over ten idle minutes). The test
that an idle station stays above freezing waits on that.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Q3 with the 2026-09-30 ruling: a design no product sells is a scenario test
design and nothing else. A test design is recognised as one a scenario places:
its preset's hull or a slot of it, or cargo it carries. The census holds for
the shipped catalog (today there is no unsold design at all), and in a scratch
copy a new unsold design is flagged until a scenario places it, once through a
preset slot and once as an entity's cargo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator rulings, 2026-09-30 (docs/scenarios-cut.md, "the ten real designs with
no product"), landed through a transient `AetherDb product-gap [apply]`:
- Deleted, nothing referencing them: Assembly Line, Deep Ore Extractor, Surface
Ore Extractor, Refinery, Shipyard (RTS-era placeholders, price and durability
0, three with unreadable behaviour entries).
- Products, maker taken from the nearest analogue's product:
Autocannon -> Autocannon by Zhestokost (DeathCluster: Ballistic, 4 cells, AutoWeapon)
LRMM72 -> LRMM72 by Rossum & Douglas (GT 3K: the only other unguided launcher)
SRMM72 -> SRMM72 by Death Monkey Explosives (scorched void policy: guided, nearest size and price)
Tractor Beam -> Space Tractor by Ewan Hart Inc (no analogue; the wishlist's
"Space Tractor: It's a tractor. In space.", content-batch-one section 10)
Industrial Thermostatic Heater -> same name by Lightsail Express (Cockpit 2x2,
the same Thermotoggle/EnergyDraw/Heat group)
Each product carries its design's roles at the default quality (0.5 +- 0.15).
- The heater design kept (operator), with Price 0 -> 100,000 (between Cockpit
2x2 and Fire Control Array) and Durability 0 -> 100 (Cockpit 2x2's): at zero
price generation never offers it, at zero durability it never comes online.
The rewrite stores every weapon record at the current schema, so
FireControlCut124's shipped-catalog check now expects an Exact migration
report instead of drift with slot 32 defaulted. `AetherDb census` reports no
unsold design. Tractor Beam keeps price 0, durability 0 and no behaviour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AddTransition's graph walk kept its visited, leaf and ignore sets in static
lists, so two Minions built at once on different threads (zones are built in
parallel) walked and cleared each other's state. Soul hit it as "collection
was modified" during parallel test runs. The sets are now the call's own.
BaseStateTests starts sixteen walks together behind a barrier, fifty rounds,
over prebuilt 200-state chains; with the statics restored every run failed
(several hundred of 800 walks wrong or throwing); with locals, clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every entity's zone-removal subscription dropped its IFF override for whatever
left the zone, and TryDock removes the docking ship, so a scenario stance was
lost one way (the other entity forgot; the docked ship, deactivated, kept its
own) as soon as the player docked. The removal event already tells the two
apart: TryDock parents the ship to its dock before removing it, and every
leaving-for-good path (death, warp, unload) leaves with no parent. The override
is now dropped only for an entity that leaves unparented.
Tests: a hostile stance holds both ways, including the live hostility each side
tracks, across dock and undock; a destroyed entity takes the player's stance
toward it with it. UndockingAdmitsWithNoAgent commits Soul's probe: undock
admits unpiloted, active, with no agent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The quiet-arena test ran with an empty Narrative directory, so the story-station
branch of ZoneGenerator never ran under Ambient: false and keeping its turrets
went unnoticed (Soul's S06). The test now places a story station in the entrance,
as the narrative loader would, and checks it is generated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Zone unchanged on failure" was defended only by xunit formatting an entity
sequence until the test host overflowed its stack. SafeAssert.Unchanged compares
the zone's entities and agents by identity and order and names what changed by
hull name; every Assert.Contains over zone.Entities in RunStartTests goes through
SafeAssert.In for the same reason.
AMissingDesignFailsNamedAndChangesNothing gains the partial-admission case: a
player and a piloted entity that build cleanly, then a later entity whose preset
is missing. Admitting the built player before the all-or-nothing return now fails
by name ("the zone's entities changed, 5 -> 6: added [Djinni]").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tools/AetherDb field-kinds apply, run on Yggdrasil against catalog oid
2aa79c0a. One FieldKindData record: "Asteroid", cross-section 5 per schematic
cell, generation weight 1. Without a kind every belt is dark (Cut 3), so this
is what makes rocks seeable in play. The two numbers are placeholders for the
operator to review; Cut 7 authors the full content. If the catalog changes
upstream before this merges, rerun the command on the new catalog rather than
merging the binary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ShippedCatalogIsNeutralOnBlastRadiusAndFuse also pinned that every stored
weapon record predates slot 32 (CompatibleDrift, slot 32 defaulted). The field-
kinds authoring pass rewrote every catalog record through CultCache, so the
weapons now store slot 32 and load Exact. The rule the test protects stands:
no weapon authors a blast radius or fuse, and a record that does drift may
drift by slot 32 and nothing else.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An admitted entity that was never activated tracks no hostility, so
targeting and grudges never see the stance. The stance test now also reads
each side's live EntityHostility, which only activation populates; dropping
Activate from Zone.Admit survived the suite before this.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The zone's gravity moves every ship, so an unpiloted scenario ship drifts
(about 14 m over 5 s at the test's spot) with nothing flying it. The rule is
"no agent steers it": the test now checks its movement and look inputs stay
untouched every tick, and that it still has no agent afterwards.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A ship's equipment comes online on its first tick (UpdatePerformance), so a
sensor sees nothing before it: the chunk tests tick each observer once before
asking what it sees or picking a target. The generation test opens its copy of
the shipped catalog through a registry scoped to the shipped assembly, as
RestoredHullsTests does, so the test assembly's own catalog global is not
demanded of it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A chunk's reflectivity comes from its belt's kind, so a catalog with no
FieldKindData leaves every belt dark. The command dry-runs by default and, with
"apply", authors "Asteroid" (cross-section 5 per schematic cell, weight 1)
through one validated Commit batch. Both numbers are for operator review.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chunks are detected by reflected light (Cut 3), not by a scanner. The
behavior's per-tick Update of Range, MinimumDensity and ScanDuration had no
reader anywhere; it goes, with the three properties and the interface.
ResourceScannerData stays authored.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Entity.Target is one slot of TargetRef, read-only from outside
(IReadOnlyReactiveProperty); Entity.SetTarget is its one writer. A chunk is
taken only while the entity can see it, so a rock in darkness cannot be
picked. Every handler, agent, turret and test writes through SetTarget.
- Entity.ChunkInfo (Q14 A): the value the per-tick sensor rule settles at,
computed on demand through Sensor.PassiveRate/Sensor.Gain -- sum of the
active sensors' passive rates over (sensors x TargetInfoDecay), capped at 1.
Stored nowhere; no per-tick loop over chunks. ChunkVisible is the detection
threshold through the chunk path.
- The lost-track rule reads through the chunk path: Activate's reconciliation
and a per-tick check of the one slot drop a chunk target that has gone dark.
- Entity.VisibleChunksInReach (Q13 A): visible chunks within the longest range
among active weapons -- the one reach rule for cycling and programmatic picks.
- Entity-only readers take .Entity: TargetedBy/TargetedByCount, target items,
the stance gate (a chunk has no stance), lock (Q12 A: never on a chunk),
turrets, Combat, Minion, FireControl (a chunk fires untargeted until Cut 4).
TargetRange and the arc gate read a chunk's planar position.
- ActionGameManager: the reticle and next/previous offer visible ships and
visible chunks in reach; nearest stays enemies-only; the target panel,
indicator and fire-control debug take TargetRef. EntityInstance reads .Entity.
- MiningCut3Tests pin parity, darkness, monotone brightness, wear and break,
reach, the chunk query, target survival and loss, TargetRef equality, lock,
and field-kind assignment at generation and first load.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- FieldKindData, a catalog document (Q11): a kind's reflectivity per schematic
cell and its generation weight. AetheriaStores routes it to the catalog.
- AsteroidBeltData.Kind, key 10 (Q16 B). FieldKinds.Assign is the one pick (a
weighted choice by a stable hash of the belt's key); FieldKinds.Ensure gives
a kindless belt its kind and saves it. ZoneGenerator calls it at generation
and Zone calls it on first load, so an old belt is assigned once by the same
rule and keeps its kind after later catalog edits.
- Zone.ChunkVisibility: Reflector's rule for a chunk -- kind cross-section x
area in schematic cells x the light on it. Stored nowhere; a worn chunk dims,
a broken one or a kindless belt's is dark.
- Zone.ChunksNear: the one chunk query, skipping belts whose annulus cannot
reach. AsteroidBelt gains InnerRadius for that skip.
- TargetRef: an entity, a chunk, or none, with value equality and no ==.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sensor.Execute's ping and passive gain arithmetic moves, term for term and in
the same multiplication order, into one static function, Sensor.Gain. The
entity loop calls it; Sensor.PassiveRate calls it for the on-demand chunk
query that follows. SensorGainExtractionChangesNothing holds the recorded
EntityInfoGathered trace bit for bit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SensorGainExtractionChangesNothing drives a fixed scene (two reflecting ships at
different ranges and bearings, a ping part way through) and pins the observer's
EntityInfoGathered trace, bit for bit, by hash. The golden value is recorded
against the pre-extraction Sensor.Execute.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entity.Update walks _orderedEquipment, which only TryEquip and TryUnequip
set, so an entity nothing was ever equipped on threw a
NullReferenceException on its first tick. Scenario presets stage bare hulls
(a turret, an unpiloted target), and RunStartTests found it: MapEntity now
sets the ordered equipment to the hull alone when it adds the hull.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
StartGame's new-run branch no longer generates, unpacks or admits the
player: RunStart generates the arena and stages the run, and StartGame
enters the zone and binds the returned player (docs/scenarios-cut.md, 2.2,
deletion line). PopulateLevel's warp arrival and the spawnturret console
command admit through Zone.Admit, unpiloted. The console commands stay (Q4);
give's hull route moors a ship at the docked entity and joins a zone only
through Entity.TryUndock, which already admits.
Needs the Unity batchmode compile; Assembly-CSharp is not built headless.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RunStart (ServerShared, no Unity type) owns what a new run starts with
(docs/scenarios-cut.md, 2.2):
- GenerateArena: the galaxy's entrance pack, generated under the scenario's
Ambient rule. ZoneGenerator.GenerateZone gains `ambient`; false keeps
planets, orbits and stations and generates no ship, turret or wanderer.
- Check: every failure staging a scenario would hit, found with no zone,
touching no store.
- Stage: all-or-nothing. With no scenario, the generated starting-hull ship
at the origin, as StartGame built it. With one, the player preset and the
entities at their authored place and heading with cargo, stance set both
ways through SetIff, a Minion for each piloted one; all through Zone.Admit.
Scenario (aetheria.scenario v1) joins AetheriaStores.CatalogTypes.
RunStartTests stage into a real prelude arena from the shipped catalog and
authored settings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two rules on Loadouts.Materialize (docs/scenarios-cut.md, 2.2, Q3):
- A design no product makes is outside the economy and is built unbranded,
with no maker, at Loadouts.UnbrandedQuality (.95, the console give's).
A design whose products are all unavailable still fails, as before.
- The hull's type picks the entity: a ship hull builds a Ship, a turret hull
an OrbitalEntity with no orbit; a station hull is refused. Materialize now
returns Entity.
Loadouts.Resolve is the one rule for how a design is built; RunStart uses it
for scenario cargo. LoadoutTests.MissingDesignReports no longer uses the
product-less "Orphan" as a failure (it now builds); two tests pin the Q3 rule
and the unavailable-product contract it does not loosen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Zone.Admit(entity, piloted) adds the entity to Entities, activates it and,
when piloted, gives it the zone's Minion. It is the only writer of
Entities.Add and the only creator of agents (docs/scenarios-cut.md, 2.2).
The constructor's pack loop admits every packed ship but the player's as
piloted and keeps its own origin-scatter rule, which is generation
placement, not admission. Entity.TryUndock admits the undocked ship
unpiloted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Q1 A, Q2 B, Q3 A. Q4: every console command stays ("never toss a
single one"), so Cut 0 is cancelled and give/spawnturret admit through
Zone.Admit in Cut 1. Self restores the smoke weapons in the operator
tree when Cut 4 lands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling 2026-09-30: mount points exist only for externally
visible hardpoints. No equipment role, no plain mount. Articulation
points stay; they are not mounts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DontDestroyOnLoad throws outside play mode, so the edit-mode play smoke
failed before it reached assembly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ShipModPlaySmoke.Run asserts thruster and weapon hardpoints, the hull MeshCollider and the map icon, prints
SHIP_MOD_PLAY_SMOKE and exits explicitly. The authoring doc gains a Unity batch smokes section.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The fixture package now has real vertices (BIN chunk written by the generator, no committed bytes), an energy hardpoint "gun" with a
muzzle anchor, and the thruster moved outside the schematic, so ShipModShips.Assemble accepts it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- RequireDesigns names the null (entity, item, zone record, lot, lot ledger) instead of catching NullReferenceException.
- ResolveCatalog reports a Compose-level failure as an exclusion covering all mods and boots the shipped catalog.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Validate: a hardpoint's anchor role is fixed by its type (thruster-emitter, radiator-mesh, else articulation); ShipModPlan's
duplicate role check is deleted.
- Loadouts.Commit refuses a preset naming a mod design (mod-hull:/mod-ship:), so the shipped catalog never references a mod.
- Compose quarantines each package: a bad package is excluded and named, a collision excludes every package involved; the
game boots on the rest (or the shipped catalog) and the main menu lists the exclusions.
- RequireDesigns refuses a save with a null item or lot instead of throwing NullReferenceException.
- Tests: per structural role, muzzle id ties, every slot/zone/lot in RequireDesigns, listing order, exclusion cases.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Unity generated them on import; without them the two scripts' GUIDs are
unpinned and differ per checkout.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Boot opens the derived catalog when GameData/Mods holds packages
(ShipModCatalog.ResolveCatalog), preloads each hull's GLB into an assembled
inactive prototype (ShipModShips), and LoadEntity clones it when the hull names
a Visual. ShipModPlan decides which anchor fills which ShipInstance slot; a
ShipModTemplate prefab carries the shared effects. Continue refuses a run that
names a design the catalog no longer holds (RunSave.RequireDesigns).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Load reads the raw ship authoring payload and refuses a non-nil retired key 1 (the
pre-S1 embedded hull) naming the old shape, so a stale hull is no longer silently
ignored; ship_cc.py refuses the same file on read and therefore on every save.
A hull naming both a Unity prefab and a visual is refused by one predicate
(ShipAuthoringStore.RequireOneBody), called by Validate and by Compose over every hull
in the derived catalog, shipped ones included; a whitespace-only Prefab is unset. This
replaces the mod-ship-only "cannot name a Unity prefab" check.
The validator also refuses non-finite or negative hardpoint Armor and FiringArc, an
undefined HardpointType or ItemRotation, and an anchor Role outside the eight the
schema documents.
Load wraps MessagePack decode failures with the file path. Python replace_layout
refuses non-boolean layout and hardpoint-footprint cells.
Tests: exact Visual key comparison, HullData.Visual at key 32, a save refreshing
stored_at, plus one case per new refusal. HullData.Visual is now [Inspectable] like
Prefab (Unity editor only; not compiled here).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Owner, shared-path and compose paragraphs describe the S1 shape: a ship
file holds a HullData and the ShipAuthoring visual it names through
HullData.Visual, and the derived catalog holds one hull per mod ship.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A mod ship file now holds a HullData at mod-hull:<id> and the ShipAuthoring
visual package at mod-ship:<id>. HullData.Visual (key 32) is the typed ref
between them; ShipAuthoring.Hull (key 1) is retired.
- One joint validator over (hull, visual) is the only semantic check; Load
owns the file structure (one hull, one visual, deterministic keys).
- Compose copies the mod store's records verbatim, so the derived catalog
holds one hull per mod ship.
- ship_cc.py edits the hull record's slots directly; the body[HULL_SLOT]
indirection is gone.
- The package fixture is generated from ShipAuthoringTests.Fixture() (glTF
scene included) and exported when AETHERIA_SHIP_FIXTURE_DIR is set.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Applies corrections 1-4 of docs/moddable-ships-cut.md B.1 to
docs/moddable-ship-authoring.md: label the Quiet probe numbers as unrecorded,
name the double-held mod hull in the derived catalog, name the ShipAuthoring
catalog-routing invariant, and scope "same semantic validator" to C# callers.
The map's status header gains the S0 result (615 tests on the merge), the
S-Soul status, the corrected TradeMenu.cs path, and anchors re-taken against
the merged tree.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Applies corrections 1-11 of the refresh's A.1 to docs/mining-cut.md: the status
header records the 2026-09-30 rulings, Q14 and Q16 stay open (they block Cut 3),
and M-Soul content is unknown (the mutation sweep at 22fb4021 stands in). Cuts 3
and 4 are replaced with the refresh's versions; Cut 6 is deleted with a history
pointer (Q9 = C); the scanner-as-chunk-sensor plan and the fallback range tunable
are struck; Q2's detection paragraph is replaced with the operator's 2026-09-30
correction; the FireControl target table is marked history at 8bd25f6f.
The refresh's Cut 5 and Cut 7 texts are not folded in; the header says so.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A pass of 137 hand-written mutations over ShipAuthoring.cs, ShipModCatalog.cs
and ship_cc.py, run against the tests from the last three commits, left six
surviving or unrunnable. Each is resolved:
- Validator: the hull Width/Height < 1 guards and the opacity IsInfinity
guard could not change any outcome (an empty grid has no occupied cell;
+Infinity exceeds 1 and -Infinity is below 0). Deleted.
- Two accepted shapes had no test: a hardpoint footprint with empty cells
beside occupied ones, and an empty ParentId meaning no parent. Added.
- The GLB header length was pinned only from above. Added the shorter case.
- Compose's working-directory cleanup was invisible because the assertion
listed files, not directory entries. It now lists both.
- ReadPackage runs the catalog's item validation on the hull (the derived
catalog must reopen); that call now has a test.
- Python: a single non-ship record, a non-array payload long enough to fill
every slot, and a hardpoint-only change to the layout revision each had a
surviving mutation. Tests added.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflict in Zone.cs: kept master's TryGetShot, kept Cut 1's deletion of the BeltUpdates field.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Imagination pass 1 against dcd7bbc5, designed from the operator's own
words (quoted in the map). Cuts 0-4; four operator questions open
(Q1 arena in a real run, Q2 who records results, Q3 test-only designs,
Q4 which ad hoc affordances retire).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fire-control batches 1-8 with Soul passes, verified headless (Aetheria
Shared tests). Play smoke 2026-09-30: compile clean, steps 3-6 played
(pass/partial), steps 7-20 and fire-control checks A-F unplayed. The
operator preferred to merge now; Self ruled merge, with the unplayed
checks carried as open behaviours to verify through authored main-menu
scenarios (new Eureka cut). Report: docs/merge-to-master-checklist.md
section 8.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ConsoleView appended the control characters text input delivers (\b, \r),
so Backspace inserted a glyph and Enter's \r got every command refused
since d503f076 made the parser refuse instead of strip. Control characters
are no longer text; Backspace deletes like Delete.
- give reports an unknown name or a full cargo bay. A ship hull is refused
unless docked, and when docked is commissioned through
ActionGameManager.CommissionShip, the primitive TradeMenu.Buy now uses: a
bare player ship moored at the docked entity, no docking bay. Non-ship
hulls are refused.
- Record play smoke results for steps 3-6 and the docking bay finding.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Missing catalog or save capabilities are added to AetherDb as commands,
not written as scratch projects, loose scripts or byte readers. Records
the serializer options and the CultLib pin gate an agent hits on first use.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every mutant of the Cut 1-2 chunk surface (Zone.ChunkExists, ChunkRadius,
Wear, EvaluateBelt, ChunkPose, PackZone/unpack, AsteroidBelt.Pose and its
sizes, ChunkId) was run against MiningCut1Tests and MiningCut2Tests. Six
survived; five are behaviour and get a test each:
- ChunkExists is false for a body that is not a debris field (a planet) and
for a key naming no body.
- A hit on the exact respawn instant lands on the live chunk (Wear agrees
with ChunkExists and ChunkRadius about the boundary).
- A chunk worn after respawning packs with no BrokenUntil.
- ChunkPose carries the worn size, and zero for a broken chunk.
- Chunk rotation wraps at one turn (the fixture never reached one before).
The Cut 2 fixture gains a non-belt body, and its settings the three curves
that body's constructor reads. The sixth survivor, a ChunkId hash that
ignores the index, changes no observable behaviour.
Tests only; no production change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ship_cc.py edited a ShipAuthoring record by bare slot numbers and hardpoint
positions (hp[3], hp[7:], raw[0..6]), and __init__.py copied the
HardpointType and ItemRotation enums by position. Nothing tested either
file and nothing tied the numbers to the C# [Key] attributes that own them.
- ship_cc.py names its schema facts once: the four record slots (each
commented with the C# member it mirrors), HARDPOINT_MEMBERS (HardpointData
member names in slot order), and the two enum name tuples. Hardpoint rows
are built and read through encode_hardpoint/decode_hardpoint; replace_layout
and the Blender operators no longer index a row by literal. __init__.py
builds its enum items from the tuples. The rotation label
"Counterclockwise" becomes "CounterClockwise", matching the C# member.
- tools/blender/tests/test_ship_cc.py (30 unittest cases, bpy-free, needs
CultLib's cultcache-py and msgpack): read, replace_lines, read_layout,
replace_layout (untouched slots and later-schema hardpoint slots preserved
by mount id, schema catalog preserved, every refusal leaves the file
byte-identical), the row codec, and capture_grease_pencil against stand-in
objects.
- ShipSchemaPinTests (C#, 7 cases) reads ship_cc.py and fails when a slot
constant, a HARDPOINT_MEMBERS name or position, or an enum name/value
disagrees with the C# types, and when a serialized ship no longer has the
nested array shapes the Python indexes (schematic [[w, h, cells]], cells
x-major, hardpoint transform and footprint positions).
Reading the slots from the file's schema catalog instead was rejected: the
catalog lists only document members, and Hull, Shape and HardpointData are
nested types it does not describe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Compose, ReadPackage and ReadNodeIds had no test. ShipModCatalogTests now
covers the derived catalog, the atomic replace of an existing one, every
failure path leaving the previous derived file byte-identical, the unsafe
source/output refusals, package binding (directory name, GLB existence and
extension, anchor node presence), and the GLB reader (each header guard, the
JSON chunk boundary, node index accounting, duplicate ids).
Writing them exposed three defects in Compose, fixed here:
- F6: AetheriaStores.CatalogTypes routes ShipAuthoring to the shipped
catalog, and CultCache indexes names per concrete type with last writer
winning while GetByName over a base type throws on a cross-subtype match.
Compose checked keys only, so a mod hull named like a shipped item, or like
another mod's hull, silently shadowed it or broke lookups. It now refuses
a hull name matching any catalog ItemData and a ship ID matching an
existing ShipAuthoring, naming the ship.
- The backing store writes a .lock file beside every file it opens, so each
Compose left <output>.<guid>.tmp.lock behind, success or failure, once per
boot. The working copy now lives in its own directory that is deleted whole.
- Deleted: the duplicate-ID check (ReadPackage already pins ID to directory
name, so it could not fire) and ReadPackage's containment StartsWith
(Validate already refuses rooted and `..` model paths on the same Read).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soul's mutation pass on 375d6bd4 found 8 of 9 validator mutations
surviving, including the `..` model-path guard and the prefab refusal. The
validator had five hand-picked cases. ShipValidationTests now carries one
case per guard: each starts from the valid fixture, breaks exactly one
thing, and names the message that guard alone produces. It also pins the ID
alphabet at both ends of each range, the accepted model-asset spellings
beside the refused ones, the range edges of point attributes, an anchor
naming its hardpoint as parent, and Read's exactly-one-record and
validate-on-load behaviour.
ShipAuthoringTests.Fixture becomes internal so the new file shares it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ARPG.unity gains Debug Info Panel and Debug Info Text, which
ActionGameManager.DebugInfoText drives as the fire-control debug HUD.
FieldShieldTest.unity gains the ShieldInterceptor, ShieldEnvelope and
Panel prototype rig from shield-panel Cut 4.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regenerate AetheriaInput.cs from the committed Aetheria.inputactions so the
wrapper carries Cycle Target Item, and bind it through Input.Player like the
other target actions. The by-name FindAction lookup was a stopgap from when
the generator could not be run outside the editor.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One Solve decision feeds Fire, Inspect and Refuses. Literal arming distance
on every detonation path; no-lock rounds of any fuse stop at the first hull;
a round fired at a target that leaves does not burst; refusal is free,
cooldown included; AI decides per weapon and never fires a refused weapon;
the HUD shows the outcome. Soul passes 5-7; batch 8 closes their follow-ups.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After its fade a round kept integrating Velocity through the BindingLifetime wait, then a Hit (which carries
no burst point) played HitEffect at the drifted position. The wait now holds still; a hit without a point
plays the effect at the round's target position, and is skipped when the round never knew one. OnEnable resets
BindingLifetime so a pooled split child, which is never bound, does not inherit the last round's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A released charge has a burst pending and no cooldown until its first round, so a second Activate in that
window started a second charge; Activate now asks CanFire. The round that starts a burst's cooldown does so
after the tick's decrement, which made every cooldown one tick long; it now charges that tick to the cooldown.
Pins both, and that a burst triggered after a fired one is not ready before its first round.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A burst is sized from stats the first Execute resolves, so the untouched fixture gun fired nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BurstCount is set by the first Execute, so a trigger pulled with no tick has no burst pending. The
tests now run one tick and read whether the cooldown started.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cooldown now starts at a burst's first fired round, so Progress no longer moves at the trigger.
IffAndCombatTests read CanFire (false while a burst is pending) instead, and the charged-weapon
control uses the charge-start event name the sound bank resolves.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A burst whose every round is refused fires nothing and leaves no cooldown: the cooldown moves from
Trigger to the first round Execute does not refuse, and CanFire holds a triggered burst that has not
yet fired. ChargedWeapon.Activate asks FireControl before it starts charging, so a refused weapon pays
no charge heat or audio. InReach is deleted: Designated carries the one range test and the lock gate,
and Combat reads it. GuidedProjectile answers Resolve while alive and keeps its binding through the
shot's flight time, so a faded round still shows the simulation's detonation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mutation pass on the fuse fixes left one survivor: a no-lock round whose
aim ends inside the blast radius (a designated target out of arc) flew only to
its aimed point instead of the arming distance, and nothing noticed. Its arrival
is now pinned to the arming distance, beside the targeted case.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling 2026-09-30: a no-lock round of any fuse stops at the first hull
on its aim line and detonates there (bomb fishing still bursts at max range on
a clear line). CommitBurst no longer special-cases Contact.
Soul's findings on 14f66634..7e620ff9:
- Arming (HIGH): a contact or delayed blast on a hull nearer the muzzle than the
blast radius detonated at the hull. Armed is now the one owner of the arming
rule; Solve plans its burst point through it and ApplyBlastHit pushes the hull
point through it.
- A refused beam never starts (ConstantWeapon.Activate asks Refuses), so
OnStartFiring no longer flashes visuals and audio for a beam that fires nothing.
- Step removes a shot from the queue before Apply runs damage observers, so a
throwing observer cannot make the blast repeat every tick.
- AgentFires consults Solve for a fused weapon: a refused weapon is never fired
and Combat's group pick no longer counts its damage.
- SingleAmmoBurst pays at its first unrefused round in Execute, judged with the
Range that round flies with, instead of at the trigger on the previous tick's.
- Inspect reads Designated from Solve (FireSolution.Designated) instead of
recomputing visible, in-range and locked; FireControlDiagnostic drops those
three fields and the debug HUD shows the one. Combat's group range test is
FireControl.InReach, the range half of Designated (a LockWeapon must be
activated to acquire its lock, so Combat cannot ask Designated itself).
Tests pin each rule, plus the two surviving gaps: a pushed-out targeted round's
arrival at the arming distance, and the AgentMinHitProbability boundary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The overshoot branch of GuidedProjectile.Update (out of range, or flown past
its target) instantiated HitEffect whatever the simulation had decided. A round
the simulation never detonated could therefore explode on screen. The branch now
fades the round out; Resolve stays the one place a HitEffect is shown for a
detonation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The trigger's own refusal check stopped a short-Range round before Execute
ever reached its refusal, so wear, heat, visibility and sound left ahead of
Execute's `continue` survived every mutation. The cost theory now also
authorises a burst directly, the way a trigger that passed leaves it, and the
separate ammo-only test folds into it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Unity-only, compile-checked by reading.
* F5: GuidedProjectile.Binding is one owner: a new binding disposes the one it
replaces, Kill disposes it, and OnDestroy does too, so a projectile torn down
with its scene cannot leave an observer on the zone's subjects to touch a
destroyed transform.
* F6: Resolve takes whether the simulation detonated. A miss (a contact or
delayed round that missed, a target that left) fades out with no explosion.
* F7: BurstAt resets the previous target position and target velocity, so the
retarget jump does not feed the guidance a bogus target velocity.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Operator rulings 2026-09-30.
* Combat and TurretController ask FireControl.AgentFires for each weapon. A
fused weapon fires whenever its target is designated (Designated, the one
visible / in-range / locked predicate PFire already used), whatever the arc;
an agent never fires one at nothing. A direct-hit weapon keeps the
AgentMinHitProbability threshold. The group's first weapon no longer speaks
for the group, and the turret's own range test is gone.
* FireControl.Solve is the one decision about what a round does, extracted
from Fire: Direct, Burst at a reach, or Refused. Fire, Inspect and the
weapons' refusal check all read it. Inspect gains Outcome and BurstReach and
the debug HUD prints them for fused weapons.
* A refused round costs nothing. InstantWeapon asks before its ammo and energy
spend (at the trigger and at each round), ConstantWeapon folds the refusal
into the gate its power request and Execute already share, so a refused beam
draws no power, wear, heat or visibility and stops.
* The arming distance is BlastRadius, measured from the shooter's position and
blind to hull size. HullAhead's exit, ArmingDistance and
PendingShot.ArmingDistance are deleted; HullAhead survives as HullEntry for
the contact fuse. A round fired at a valid target inside the radius is pushed
out along its line but keeps its target, so a departed target cancels it.
* Step settles a shot's queue state before publishing about it, so a throwing
presentation observer costs its own notification and cannot re-commit or
re-resolve the shot on every later tick.
Tests: FireControlPerWeaponTests (new; includes a fused-beam fixture), the
arming tests in FireControlCut124Tests rewritten to the literal distance,
the hull-inclusive probe premises deleted.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- GuidedProjectileManager passed target.transform to OnFireGuided for a
no-target fused round: a NullReferenceException inside InstantWeapon.Execute
after the shot was queued, which skipped audio, wear, heat and visibility
and unwound Zone.Update. The tuple's transform is null-safe now, and the
trackmissile console command falls back to the missile.
- Presentation follows the commit rather than predicting it. The manager binds
each guided round to Zone.ShotCommitted (retarget to the burst point the
commit names, so a Contact round bursts at its hull and a Range-clamped
burst is not overshot) and ShotResolved (burst there, then). The binding is
disposed when the projectile dies, so a pooled round does not accumulate them.
- Beams and barrel-fired projectiles stay with F5.
Unity-only; compile-checked by reading.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Operator ruling 2026-09-30. A fused round's burst is at least BlastRadius
beyond the far edge of the shooter's own hull, measured along the flight line
against the hull as it stood at fire (HullAhead's exit). A close burst -- a
target inside that distance, in or out of arc -- is pushed out along the aim
and carries no target; a contact round is floored to it too.
- The F3 trigger gate is deleted. Fire refuses a round (returns 0) whose
arming distance exceeds Range, per round, where Range is read after Execute
refreshed it; InstantWeapon and ConstantWeapon skip a refused round.
- PFire's range, Inspect's range and the range clamp are planar (R7).
- HullEntryDistance becomes HullAhead (entry and exit); the max(...,0) clamp
on entry is gone, the arming floor covers it.
- ShotOutcome.HasBurstPoint names the rounds that have a frozen burst point.
- SafeAssert: the tests no longer hand xunit an Entity, PendingShot or
ShotOutcome to format; a failing assertion had been overflowing the test
host's stack.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
EntityInstance handed effect managers the shooter's selected target whatever
FireControl decided. A fused round whose target was invalid or out of arc
flies at none, and GuidedProjectileManager dropped a launcher shot with no
target entirely, so a real burst had no missile.
- Zone.TryGetShot: a presentation reads the shot the simulation committed
(its target, its burst point) by the ShotId OnFire hands it.
- EntityInstance passes the shot's own target; a launcher round with none
flies to the shot's burst point.
Unity-only; compile-checked by reading.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Operator ruling 2026-09-30: a fused weapon with a selected but out-of-arc
target fires. Its round flies along the arc-clamped aim and bursts at the
target's range. Non-fused weapons still refuse.
- FireControl.ArcPermitsFire is the one trigger arc gate (fused weapons are
exempt); Fire reads the same InArc through PFire's new designated/inArc
outs, which replace `bears`. No valid targeting data still bursts at Range.
- F3: a fused weapon whose effective Range does not exceed its BlastRadius
stays safed at the trigger (FuseCanArm, read from the runtime Range). The
catalog check on Range.Min is deleted; it read the authored floor, which
Evaluate can scale below.
- F4: a targeted proximity burst is pulled back along its line to Range.
- F8: a proximity round that detonates commits as Burst (or Hit); Apply's
Proximity case collapses into the Burst path via Outcome.BurstPoint.
- Tests: arc-only and LockWeapon gates, out-of-arc burst, trigger gate,
runtime-Range refusal, Range clamp, Burst typing, commit horizon, and
Soul's adopted probes (aim clamp edges, contact point, target leaves,
shooter destroyed).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Assert.Null(shot.Target) and Assert.Same(committed, resolved) format the
entity graph when they fail, which overflows the xunit stack and crashes the
host instead of reporting a red. Use Assert.True with a message.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Operator rulings 2026-09-30 after Soul's pass on 8c002637:
- A no-lock round flies along Entity.LookDirection restricted by the mount's
arc (FireControl.AimDirection: inside InArc's cone as is, outside it turned
to the cone's edge; no aim falls back to the mount). The simulation had no
aim clamp before; the old code used the hull-fixed mount direction.
- An invalid target (out of range, out of arc, not visible, not locked) counts
as no lock for a fused round. PFire's gate is the one decider and now also
reports `bears`; Fire reads it. The round carries no target afterwards.
- A no-lock Contact round bursts at the first hull its aim line crosses
before max range, read through Lane's own cell admission (CellsOnLine,
split out of Lane). Proximity and Delayed keep the max-range burst. Such a
round commits at Fire, since it has no target facing to read late, and
arrives when it has flown to the burst point. The seam for mining chunks is
FireControl.ContactDistance.
- A round outlives its shooter: Step no longer cancels a shot whose source
left the zone. Resolution reads the zone and the target; the shooter is an
identity a shot carries. PendingShot.FireOrigin freezes where it left from.
- ShotOutcome.Result (Miss, Hit, Burst) replaces the stored Hit bool, which
is now derived. A burst without a target is no longer reported as a miss,
and the debug HUD labels it BURST.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Soul: a no-lock round bursts at Range, so Range <= BlastRadius (or a zero
Range) lands the blast on the shooter. Made structurally impossible at the
catalog rather than policed at runtime: StatValidation.ValidateFuse joins
the checks CultRecordRefs.Validate runs on every Upsert, at the weakest value
the Range stat can take. Only a weapon that will detonate (a Fuse and a
BlastRadius above zero) is held to it.
AetheriaStores.Open now calls that same Validate for every equippable and
consumable document instead of carrying its own copy of the three checks, so
Upsert and Open can no longer disagree.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Proximity, contact and delayed shots fired with no target burst at Range
along a non-axis aim (2,1) and leave the shooter undamaged; the shot flies
there at the weapon's Velocity. A targeted proximity shot keeps the burst
point and the damage value measured at b66ba524 (5.65884256).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Operator ruling 2026-09-30: fused weapons without a lock explode at max
range. Fire froze BurstPosition at the shooter's own position with flight
time 0, so a proximity shot detonated on its own shooter and contact and
delayed shots did nothing.
With no target and a fuse, Fire now freezes the burst point as the mount
direction times the weapon's Range stat from the source, and flight time as
Range over Velocity. Apply sends every such shot through Detonate at that
point whatever its fuse. Targeted shots and unfused shots are untouched.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Re-anchored to the post-fire-control-12 Body (synthetic merge tested on
Yggdrasil: 379/364/381 green). Codex's map is raw material; this is the
proposal. Operator questions open.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-anchored to the post-fire-control-12 Body (synthetic merge tested on
Yggdrasil: 379/364/381 green). Codex's map is raw material; this is the
proposal. Operator questions open.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
c50029d5 committed the file with CRLF endings: the inserted paragraph had
mixed endings, and autocrlf does not normalise a mixed file. Content is
unchanged, and the net diff against c50029d5^ is the 26-line status
paragraph.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assert.Contains on a Ship formats the whole entity graph on failure and overflowed the stack, aborting
the test run instead of reporting red; the new tests check membership with a boolean and a message. The
cockpit-kill test now gives the target a hull the blast also kills, the case where the double Death
actually occurred.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Entity.Death, the owner of the stream, now ends after its first cause. A blast that destroyed a
cockpit fired Death, which removed and deactivated the entity, and the blast's own DamageHull then
raised a second Death to every subscriber but Zone's idempotent handler. Every damage path gets the
guard because none of them raise Death themselves.
New pins: one blast killing three ships (a break after the first kill survives the old suite), a
shieldless ship outside the disc receiving nothing, a cockpit-kill blast and a direct hull kill each
raising Death exactly once.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LaneAndSilhouetteAllocateNothingGivenAPooledBuffer failed in 1 of 12 full-suite
runs (1232 bytes over 1000 Lane calls) and in 0 of 100 runs of the test alone
or its class. The allocation is a one-off, not a per-call cost: under suite
load the runtime's background tier-up is delayed by other tests' JIT activity,
so first-use allocation lands inside the single measured window.
The rule stays: Lane and buffered Silhouette allocate nothing. The test now
measures up to 20 rounds of 1000 calls and requires the smallest round to be
zero. A per-call allocation appears in every round and still fails it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Each rule below was protected only by a test a mutant passed. New or
strengthened tests, with the mutant each one kills:
- AContactOrDelayedBlastOnAMissDoesNothing: ApplyBlastHit's miss guard.
- ToWorldPointIsTheInverseOfToSchematicPointAtANonAxisFacing and
AContactBlastLandsOnTheCellItHitAtANonAxisFacing: ToWorldPoint's right axis,
asserted per cell at facing (2,1); TurningAfterCommit now compares per cell.
- ProximityBurstPositionIsThePredictedInterceptForAMovingTarget: a moving
target and an intercept solved in the test, not Durability <= before.
- AnUntouchedEntitysShieldIsNotConsulted: Detonate's skip of untouched
entities.
- TheFusePointStaysOnMetalAcrossAConcaveGap: the delayed point clamps at the
first gap (the lane's walk ends there).
- ShieldPaysForWhatReachesIt asserts the reserve dropped by the covered share;
AShieldRaisedBetweenCommitAndArrivalAbsorbsTheBlast and
AShieldDroppedBetweenCommitAndArrivalDoesNotAbsorbTheBlast replace the
outcome-flags-only check with the shield state flipped between commit and
arrival.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
F1: RectDiskOverlap counted a whole square when a cell edge sat exactly r from
the disc centre (`h < yhi`, `-h > ylo`). The comparisons are inclusive now, so
the chord bounds the piece. A proximity burst on an integer centre of mass
delivered 127.32 of 100 at BlastRadius 1 and 100.80 at 3.
F2: delete Detonate's distance prefilter and the radius <= 0 guard. The
prefilter measured from the centre of mass with the bounding box's
half-diagonal, which is not a bound on how far a hull's cells reach; it culled
real overlap on an L-shaped hull and on the shipped Longinus corners.
RectDiskOverlap is the single decider. An untouched entity is still skipped
(its covered list is never allocated).
F3: Detonate iterates a snapshot of zone.Entities, as Zone.Update does. A
lethal blast removed the entity from the live collection and the next MoveNext
threw, leaving the shot pending so it detonated again the next tick.
Tests: tangent discs through Fire and Detonate, an L-shaped hull, grazing
blasts at the farthest corner of Longinus and LonginusX (expected values from
a numeric overlap), and a lethal blast that must resolve once.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
One file with the checks only the operator can do before the merge,
folded into a single play session, with a map pointer on each line. It
also lists what was already done, what is not part of this merge, the
open questions, and the three uncommitted scene and input files to
decide on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TutorialEntranceWithNoLagrangeCandidateStillSeatsAStationWithFiniteNearbyTurrets
asserted a turret orbit's Distance matched its station's -- true unconditionally,
since PlaceTurret sets Distance from orbit.Distance directly the same way
CreateLagrangeOrbit does for the station itself, so the check could never
fail regardless of placement. Replaced with a bound on PlaceTurrets' actual
"near their station" claim: the Phase offset (20 * distanceMultiplier /
orbit.Distance), generous over PlaceTurrets' own max distanceMultiplier.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F5: committed ThrowReachableWithAuthoredSettings (from Soul's SoulL3Census.cs)
against a real seed-1 tutorial entrance -- RosetteProbability 0 and MassFloor
1e9 leave every planet a parentless root, so even the widened candidate set
(F3's fix) is empty and the throw fires through public construction, not a
hypothetical.
F6/N1: EntranceStationSitsOnAGenuineLagrangeOrbitWheneverOneExists strengthens
the existing across-seeds test with a per-seed pin: whenever a seed's entrance
actually has a genuine (non-rosette) Lagrange candidate, its station must sit
on one, not the widened fallback.
F6/N3: MultiRootEntranceStationOrbitsAreAlwaysParentedWithPositiveDistance
authors a fragmented, heavily-rosette entrance (RosetteProbability 1, a fixed
SubZoneCount of 8) and pins that every resulting station orbit is a real,
positioned orbit -- a set Parent and a positive Distance -- regardless of
which candidate selection produced it.
F6/N9: EntranceStationPhaseDiffersFromEveryPlanetAtItsOwnDistanceAcrossSeeds
pins CreateLagrangeOrbit's own Phase offset: a docked station's Phase must
differ from every planet's Phase at the station's own (Parent, Distance),
catching a mutant that seats a station directly on its source planet's orbit
object instead of an offset copy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F6/N2: added WideningDoesNotLeakToANonEntranceNoLagrangeZoneThatRollsAStation.
Seed 3's "EAC-7089" (the same no-Lagrange fixture the widened-candidate test
uses) naturally has no genuine Lagrange candidate; boosting one faction's
presence there via the same public SavedGame round trip
BuildGalaxyWithForcedEntrance uses makes its station-count roll nonzero
without touching ZoneGenerator's private RNG. Verified directly: dropping
ZoneGenerator's isTutorialEntrance guard on the widening condition turns
this fixture's station count from 0 into 2, so the fixture genuinely
exercises the leak path this test pins closed.
F7: fixed the leak test's comment (the widened-candidate fixture is seed 3,
not seed 1, so it no longer claims to share a fixture with a test that
doesn't). ForcedTutorialEntranceStationDoesNotInflateEnemyCount asserted
shipCount == 2, which was pinning TutorialZoneSettings' own default
NeutralWandererCount (2), not the enemy roll baseStationCount gates -- it
now filters ShipPacks by the same nearest-faction ZoneGenerator computes
internally (EntityPack.Faction), asserting the real enemy count (0) instead.
Also (Soul's finding): IsRosetteMember's planet-orbit count
(pack.Orbits.Count minus the OrbitalEntityPack count) over-counts whenever
a station or turret loadout generator returns null, since
CreateLagrangeOrbit already appended that orbit before the null check
skips adding the entity. It now derives the set from pack.Planets' own
Orbit refs directly (Census.cs's own approach), which sidesteps the bug
entirely.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RestoredHullsTests.Settings() hand-set thermal radiation/conduction at
their bare-class defaults (1/1/1) against the shipped 3 / 1e-8 / 0.01
(Assets/Resources/Settings.asset), so equipped parts froze toward 0 K on
real physics and every test built on that fixture ran on thermal behaviour
nobody ships. It now loads the real GameplaySettings through
AuthoredSettings (tools/AetherDb/AuthoredSettings.cs), the same headless
YAML reader tools/AetherDb already uses to inspect Settings.asset outside
Unity -- the tests project now references AetherDb.csproj to reuse it
instead of hand-copying the same fields a second time.
LonginusNeverExceedsItsRestoredTopSpeedUnderFullThrust no longer needs
OverrideShutdown to keep the ship flying (that was compensating for the
broken fixture shutting equipment down within ~1s); it now drives a
generated Longinus (seed-pinned, including ItemManager.Random, which
defaults to a DateTime.Now seed and was making item quality -- and so
the measured speed -- non-deterministic) for 5s, the window Soul measured
before this loadout's thrusters cross 386 K and shut down. The tolerance
is now asymmetric instead of the old +-10: +4 on the ceiling (the mixer's
own tick order settles real, unforced flight at the cap plus one tick's
acceleration, measured at ~103.3 for this loadout) and -3 on the floor
(tight enough to catch a nerfed TopSpeed catalog value, which +-10 or a
wider ceiling would not).
Every other test in the file now runs on real settings instead of the
frozen fixture; all 15 tests in RestoredHullsTests pass unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Geometry, absorption order, symmetry/frame, shields and fuse tests from the
cut doc's own verification list, plus Soul's three fold-in probes (a
radius-without-a-fuse damage-outcome test, a moving-target proximity-burst
pin, and Detonate's nonpositive-radius guard) and the two catalog probes
(shipped-catalog neutrality on BlastRadius/Fuse, and a WeaponFuse/BlastRadius
round-trip through a written and reopened catalog copy).
Fixture: SchematicCellSize 2, target off the origin, hull centre of mass not
an integer cell, one facing with |fx| > |fy| -- the rules the cut doc names
for every blast test. Geometry expectations come from exact half-split
symmetry where possible (TheShareOffTheHullIsLost, ShieldPaysForWhatReachesIt)
and from an independent numeric area integral otherwise (EachCellTakesItsShare
OfTheDisc, TheFusePointStaysOnMetal), never from the SUT's own overlap code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F3: the throw fired on selectedStationOrbits.Length == 0, which is also true
whenever stationCount rolls 0 -- an unrelated variable. It now checks
lagrangeCandidates.Length == 0 directly, the actual "no candidate orbit"
case, and the message says so.
F4: CreateLagrangeOrbit's FixedPosition copy and its comment described a
fallback orbit that no longer exists (widening always returns a real,
parented planet orbit). Deleted; nothing reads FixedPosition off a
lagrange/turret orbit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FireControl.Apply now switches on the frozen shot.Fuse: a null fuse keeps
12.3's lane path (ApplyDirectHit), a proximity fuse detonates at arrival with
no roll, and a contact or delayed fuse detonates only on a committed hit
(ApplyBlastHit), locating P through Lane and the new Reach owner (moved
verbatim from Apply's own inline penetration clip; the delayed fuse point
reads the identical rule). Step has no damage branch left.
Detonate (FireControl.cs) is the one owner of blast damage: candidates by
centre-plus-half-diagonal, exact circle/cell overlap shares computed once,
every blast's shield decided live per Q12-9 = A (including the host of a
contact/delayed blast -- Commit now gates its own shield decision on
shot.Fuse == null), armour absorbed per cell, and each item's covered cells
pooled into one ItemAbsorb call. Entity.Absorb and FireControl.Splash are
deleted; Entity gains ToSchematicPoint/ToWorldPoint for the point conversion
Detonate and the delayed fuse point need. Mine.Explode now calls Detonate.
Existing Splash/Absorb-era tests across Cut4/5/6/11/123 are rewritten or
retargeted to the new public owners; behavioural coverage for the blast
model itself lands separately.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F1 (speed cap): assert against Longinus's legacy TopSpeed literal (100)
instead of reading the bound back out of the same catalog value being
tested -- the old assertion passed under TopSpeed=1000 because the
bound moved with the mutation. Also fixes the false "drag/thrust
balance" comment: there is no such balance near 100. At full throttle
Longinus's own thrusters heat their hull past ThermalOnline's shutdown
threshold within ~1s, and once the hull's own cell goes offline the
same way, VelocityLimit (a hull behaviour, gated identically) shuts off
with it, so the cap was only ever exercised for the first ~15 ticks.
OverrideShutdown -- a real, first-class field (the cockpit's "Override
Shutdown" control) -- forces the hull and both firing thrusters to keep
running for the whole 12s scenario, so the cap is what actually holds
speed down throughout, not an unreached ceiling.
F2 (entrance station): EntranceZoneAlwaysGetsADockedStationAcrossSeeds
now also requires the normal case to land on a genuine (non-rosette)
Lagrange orbit. Adds TutorialEntranceWithNoLagrangeCandidateStillSeats...,
built via a public SavedGame/SavedZone round trip (no reflection) that
re-targets a real galaxy's Entrance to a real zone already known to
have no Lagrange candidate (seed 3's "EAC-7089": four planets, one
rosette, no captured satellites) -- pins a docked station whose turrets
land on a real, finite, nearby orbit. Adds the leak checks (override
must not reach other tutorial zones or a non-tutorial game's own
entrance) and the enemy-count check (the override must not also raise
the enemy roll, which stays keyed to the pre-override station count).
F3 (asset identity): EveryAssetReferenceResolvesToAMetaGuid only checks
that a referenced GUID exists somewhere under Assets/, so it passed
when Djinni's Prefab was swapped for Longinus's. The new test compares
each reference against the specific legacy file's own GUID (Djinni's
own .prefab.meta; Thruster 1.prefab.meta for every restored thruster's
ParticlesPrefab).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two writers decided the tutorial entrance's station orbit: the floor
(stationCount < 1 -> 1) and a synthesized fallback orbit, each masking
bugs in the other (removing either alone still passed the entrance
test). Collapse to one rule: the floor still guarantees stationCount
>= 1, but its orbit always comes from the same Lagrange selection. When
that selection has no candidate (a zone that is entirely one rosette),
widen it to every parented planet orbit ordinary placement draws from
before the rosette filter, instead of inventing an unparented,
zero-distance orbit -- which gave PlaceTurret's phase a division by
zero and left an orphan orbit record. If even the widened set is empty,
throw loudly instead of silently omitting the entrance's station.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WeaponItemData.AirburstRange (slot 29) becomes BlastRadius, same slot and
type so CultCache opens the catalog as compatible drift. A new nullable
slot 32 (30 and 31 belong to EquippableItemData) carries WeaponFuse?
Fuse. FireControl.Fire now decides "detonates" once, from BlastRadius
and Fuse directly, and freezes Fuse only when BlastRadius > 0 -- the
WeaponModifiers.Airburst label no longer decides anything (Q12-6).
PendingShot.BurstRadius is renamed BlastRadius and gains a Fuse field
that nothing reads yet; Step's Splash/Apply dispatch is unchanged.
No shipped weapon carries a blast radius or the Airburst label (Cut 12's
Probe4, reconfirmed against GameData/Aetheria.cc), so this is
behaviour-neutral on shipped content. Apply's fuse switch and deleting
Splash are Cut 12.4(b).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Imagination pass: every anchor is re-probed; a disc runs armour per cell, then pools
each item through ItemAbsorb; Entity.Absorb dies with Splash; the penetrator's
fuse point reads the same Reach owner as the direct-hit walk; the ledger now
carries actuals for 12.1-12.3; Q12-9 (the host's shield for a blast) is opened.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FixedPointItemResolution now throws "the model never meets a cycle" if a pool
is still open after its fixed point settles, instead of silently
force-settling in the reverse of first-encountered order. The sweep already
gates its own draws (LanesReaching) so a genuine cross-item cycle is never
actually presented to the model; a thrown exception here means the gating
missed a real case, not a numbers question the model should answer with its
own competing tie-break. The stale "opposite tie-break" comment describing
the deleted behaviour is gone with it.
Three of Soul's five probes (soul125-FireControlCut123Tests.probe.cs) are
committed here, ported from reflection onto the public Fire/Commit/Apply path
this file's own tests already use -- FireCentre/FireUntilHit retries until
the committed hit lands on the expected column, the same idiom
ZeroDepositDoesNotStall and PooledPathHonoursTheItemThresholdLikeTheSoloPath
already use, never a direct call into the private Apply:
- ZeroDepositContributorFiresNoItemDamageEvent (P3): a zero-deposit
contributor must not fire a phantom ItemDamage event. Kills Entity.cs's
`incoming[i] > 0f` boundary flipped to `>=`, and a swap that reports the
post-split share instead of the incoming one (F2).
- ClippedLaneDoesNotHoldThePool (P4): an item beyond every lane's own
penetration clip must never be waited on.
- OwnRepeatVisitDoesNotHoldThePool (P5): a lane's own later visit to an item
must not hold that item's pool open -- only other lanes are future
reachers. Kills FireControl.cs's `alreadyContributed` guard being deleted
(F1).
The other two probes (Soul5_AngledMirrorAndConservationWithSharedItems,
Soul5_AngledMirrorCycleShapes) are NOT committed. They drive Apply directly
through reflection with a hand-built PendingShot.Outcome so they can sweep
1400 exact, mirrored (bearing, lateral) pairs per test and compare against an
independent model -- Commit's own bearing is reachable through the public
path (shooter/target positioning), but its lateral is a random draw from the
per-shot RNG, not a value either test can set or predict without either
control of that RNG's seed and Silhouette's exact sigma, or a decoy Apply
entry point. Reaching them without reflection needs one of:
(a) `[InternalsVisibleTo]` for Aetheria.Shared.Tests on FireControl.Apply, or
(b) a public/internal test seam that takes a pre-built ShotOutcome (bearing,
lateral) and runs Apply's own walk against it.
Neither is authorized by this fix batch's map; this is reported rather than
silently reflected. The Soul5_Alloc benchmark (also reflection-based, and not
one of P1-P5) is likewise left uncommitted.
Mutation verification (docs/fire-control-cut.md, Cut 12.3, one-apply-path):
F1 and both F2 mutants confirmed killed by the new tests; the earlier
fix batch's own forced-sequential, resolve-one-pool-only, and cycle-throws
mutants (Soul's M3/M4/M6 family) still die against CycleTieBreakIsMirrorSymmetric
and friends.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entity.ItemAbsorb's `item == null` guard is unreachable: both callers (Absorb,
and FireControl.ApplyPooled's Resolve) already guarantee a non-null item
before calling in. Its `total > 0f ? ... : 0f` divide guard is unreachable
too -- the function already returned above whenever total <= 0.1f, so the
divide never sees zero. Both deleted; the surrounding comment now says why
instead of describing a guard that no longer exists.
ArmorAbsorb's own comment was stale from before the "one code path" ruling:
it described the item phase as deferred only for a cell shared with another
lane, when every occupied cell now defers to ItemAbsorb's pool, a lane that
turns out to be the sole contributor included (a pool of one, not a separate
solo path).
Soul's findings (docs/fire-control-cut.md, Cut 12.3): F4, F6.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApplyPooled.Resolve no longer calls FinishLane directly -- AdvanceLane is now
the single place that checks pointer >= walked and finishes a lane, so lane
completion has one writer instead of two. StillReachableByAnotherLane's own
`blocked ? pointer+1 : pointer` collapses to plain `pointer[li2]`: the
alreadyContributed check just above it already excludes a lane that is
blocked on the very item being asked about, so `pointer[li2]` is never that
item's own cell by the time the +1 would have mattered.
Soul's findings (docs/fire-control-cut.md, Cut 12.3): F4.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four tests against the real shipped catalog and the tutorial construction path, each pinning one
of this campaign's rules:
- EveryAssetReferenceResolvesToAMetaGuid: every hull's Prefab, every ThrusterData.ParticlesPrefab,
and every non-empty Schematic names a GUID an Assets/**/*.meta actually carries. Adapted from
Soul's own probe (AssetGuidsResolve); it is what caught Djinni's fabricated Prefab GUID and the
five restored thrusters' null ParticlesPrefab.
- EntranceZoneAlwaysGetsADockedStationAcrossSeeds: replaces
TutorialZoneGenerationCanProduceADockedStation, which never used the tutorial construction path
and only claimed a docking bay shows up somewhere in 25 zone-name variants. This builds the real
tutorial Galaxy with Assets/Resources/Settings.asset's own values and generates the Entrance zone
the way ActionGameManager.StartGame/PopulateLevel does, requiring a docked station in every run
that gets that far. Galaxy zone-graph generation has its own pre-existing, seed-dependent
flakiness (a disconnected zone graph throws inside the Galaxy constructor on roughly 1 seed in
15) confirmed unrelated to this cut by reproducing identically against the pre-fix-batch catalog;
that failure belongs to Galaxy.cs and is skipped rather than asserted on, with a floor requiring
at least 15 of 25 seeds to actually produce a Galaxy.
- LonginusNeverExceedsItsRestoredTopSpeedUnderFullThrust: straight-line full-thrust run against the
restored VelocityLimitData cap (100).
- LoadoutGeneratorFillsEveryThrusterHardpointAcrossSeeds: LoadoutGenerator, filtered to each
restored hull, fills every thruster hardpoint across ten seeds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator, 2026-09-25 (docs/locomotion-cut.md): the tutorial's entrance zone always gets a
generated faction station with a docking bay, deterministically. Structurally, three defects made
that false on every seed: Galaxy.cs halves influence and picks the lowest-influence unowned zone
for Entrance, so stationCount rolled 0; a Lagrange orbit is only available when a planet has an
orbiting sibling, which a boring or empty system never has; and a null station loadout silently
skipped the slot.
GenerateZone now identifies the entrance zone by reference against the galaxy parameter it already
takes (isTutorial && galaxyZone == galaxy.Entrance), never by name:
- stationCount is floored to 1 for the entrance zone, after the roll that produced it.
- When no Lagrange orbit exists, a fallback orbit fixed at half the zone radius from centre is used
instead of leaving selectedStationOrbits empty. CreateLagrangeOrbit now carries FixedPosition
through so a Parent-less base orbit (the fallback) still positions correctly.
- A null station loadout throws for the entrance zone instead of continuing past it.
- The enemy count is based on the pre-override station count, so forcing the entrance's station
doesn't also hand it a bonus enemy.
The story-station path (Locations/LocationStory) is not used: it isn't saved, and
EntitySerializer.cs:77 would break Continue, per the map's own ruling.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Same pattern as restore-hulls itself: the one-shot command's carrying cost is
zero once it has run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Landed via `dotnet run --project tools/AetherDb -- fix-loco1 apply`, then deleted the command in
the same batch (Cut 9/Cut 1 precedent). Fixes, each against the decoded 2021-04-14 legacy record
or a named catalog precedent:
- Djinni.Prefab was a fabricated/truncated GUID; corrected to Djinni.prefab.meta's own GUID.
- All five restored thrusters' ParticlesPrefab (null) -> Thruster 1.prefab's GUID, the same one
Large Drive uses (legacy record names the same prefab path for all of them).
- All five thrusters' invented OptimalTemperature/PlateauWidth (300/100) -> Large Drive's own
200-400 precedent, 251/24 (docs/stats-power-cut1-migration.md).
- ThermalResilience: Medium Drive/Small Drive -> 10, Victoire/Talaria/RevvITup 2.0 -> 15 (legacy
record). Victoire's Durability 100 -> 30 (legacy). Victoire and Medium Drive's legacy
descriptions restored (Talaria/RevvITup 2.0's legacy descriptions are the literal placeholder
"[TODO]" and are not).
- Djinni.Schematic (null) -> djinni_schematic_transparent.png's GUID, already in the tree, unused.
- Djinni's OptimalTemperature 250 -> 251, correcting a rounding slip in restore-hulls's own
"apply LonginusX's optimal-fraction to Djinni's band" derivation ((275.15-173.15)/400 = .255 of
band applied to Djinni's 200-unit band rounds to 251, not 250).
- All seven restored records (Longinus, Djinni, five thrusters) re-keyed from freshly minted GUIDs
to their own legacy GUIDs, decoded directly from GameData/Legacy/AetherDB.2021-04-14.msgpack,
matching the Cut 9 import precedent (tools/AetherDb/Import.cs, git history). Each record's own
FactionProductData.Design ref repointed at the new key.
Verified: dry run then apply, second apply refused (Longinus already carries its legacy key),
`dangling` reports 0, `census` still names both hulls, `loadout` generates every hull cleanly
across seeds 1-5.
Re-keying changed GetAll<GearData> enumeration order, which
RestoredHullMovesAndTurnsUnderMovementAndLookDirection's design-selection (thrusterDesigns.First())
depended on implicitly: it started landing every Djinni thruster hardpoint on Victoire, the one
restored thruster with a real EnergyUsage draw, and the fixture ship carries no reactor. Fixed by
equipping the fixture ship's own Reactor hardpoint, which every restored hull actually has, instead
of relying on incidental unpowered-item ordering.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rewrite the shared-item model sweep instead of leaving it a follow-up:
the earlier ModelWithSharedItems copied FireControl.Apply's own
survey-then-walk shape closely enough that reverting production to a
plain sequential per-lane absorb (no pooling at all) still passed it.
FixedPointItemResolution settles items in a different order from
production: it batches every lane's own armour-only walk into full
passes, then checks every currently open pool for readiness once per
whole pass (a dynamic, re-derived-each-time check, not a table built
once up front -- a precomputed-once version deadlocked on a non-convex
item crossed twice by the same lane). Lane geometry is read from the
real FireControl.Lanes instead of a second hand-rolled walk, removing
a class of disagreement that had nothing to do with absorption order.
Durabilities are now drawn below a lane's own share so every shared hit
overflows, hull damage is compared alongside armour and items, and the
tolerance is tightened now that lane geometry can no longer drift.
Also fixes: rejected attempts in the retry loop were mutating real
state without resetting it, corrupting the "before" snapshot; the
model was missing Entity.ItemAbsorb's own pooled .1f threshold.
Reran M0 (control, green) through M4: M1 (forced sequential absorb)
now kills the sweep as well as the mirror/proportional tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Cut 1 restore-hulls command deletion also dropped a blank line separating
FitCommand from the dangling-refs command block. Restore it so the file is
byte-identical to base (git diff against pre-Cut-1 now empty).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Commit Soul's probes as behaviour tests against the pooled walk:
CycleTieBreakIsMirrorSymmetric (a genuine two-item cyclic dependency,
mirror-symmetric and damage-conserving by construction),
PooledPathHonoursTheItemThresholdLikeTheSoloPath (the .1f threshold is
decided once on the pool total, never per contribution, and pins the
pooled ItemDamage event count), and ZeroDepositDoesNotStall (a lane
whose armour ate its whole share still deposits and never blocks the
pool it belongs to). Adds the Custom fixture hook (an arbitrary named
item shape) the cycle test needs to build two crossing non-convex items.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the survey-then-branch (ApplySequential vs. ApplyWithSharedItems)
with a single pooled walk. Lanes are clipped to penetration once, right
after Lanes returns; every item -- single-cell or multi-cell -- pools
whatever lanes reach it and resolves the moment no other unfinished lane
can still reach it down its own remaining clipped walk, with no separate
reachability survey. A cyclic dependency between two non-convex items
resolves every currently open pool at once, order-independent by
construction. Entity.ItemAbsorb becomes the one absorption function,
gated on the pooled total; Absorb (Splash's only remaining caller) now
funnels through it with a one-contribution pool.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Same contract Cut 10 applied to the Cut 9 importer: a one-shot restore
carries zero ongoing cost once its records are in the catalog. The
authored-choices review list this command printed lives in the cut's
report, not in source.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Read-only against GameData/Aetheria.cc: each hull exists with the
expected thruster hardpoint count (Longinus 4, Djinni 8), every thruster
hardpoint accepts a catalog thruster design through TryEquip at its own
position, and each hull moves and turns for a few ticks under
MovementDirection/LookDirection on the pre-Cut-3 mixer -- content proof,
not controller proof.
Also covers the operator's tutorial-station addition (2026-09-25):
Zenith (the live Station hull) is confirmed working already -- station-fit,
hardpoint-fit and `loadout` all pass for it, and the legacy record's only
station design ("Zenith Station") is that same hull, so nothing needed
restoring. ZoneGenerator's story-station path is dead code today (Galaxy's
tutorial constructor has its StoryProcessor call commented out), so
TutorialZoneGenerationCanProduceADockedStation drives the background
station path directly (real ZoneGenerator.GenerateZone call, real
catalog, a minimal SavedGame/Galaxy fixture) across 25 zone/seed
attempts and asserts at least one produces a station with a docking bay.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Landed via `dotnet run --project tools/AetherDb -- restore-hulls apply`.
Longinus (13 hardpoints, Alakrita) and Djinni (22 hardpoints, Rossum &
Douglas), plus Medium Drive, Small Drive, Victoire, Talaria and RevvITup
2.0 (approved 2026-09-22), each with a FactionProductData. Verified:
census names both hulls with makers, dangling reports nothing new,
LoadoutGenerator/`loadout` generates every hull in the catalog cleanly
across seeds 1-5, second apply refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One-shot restore of Longinus, Djinni and their five thruster designs from
the decoded 2021-04-14 legacy record, in the shape of the existing
*-migrate [apply] commands: dry run by default, writes every record in one
Commit, refuses a second apply. Longinus's scalar fields and ReflectorData
come from LonginusX (confirmed byte-identical against the decoded legacy
record); its hardpoint layout is authored directly from the legacy decode,
since LonginusX's own interior was rearranged to fit its AetherDrive.
Djinni is authored entirely from the decode (no live twin exists).
Every legacy PerformanceStat's [Min, Max, HeatExp, DurabilityExp,
QualityExp] is translated into today's Min/Max/Terms shape, never copied.
Authored post-breach fields (roles, FactionProductData, PowerSupply terms,
FiringArc) are covered in the command's own header comment for operator
review. Deleted in a later commit of this same cut once landed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F8 tests, through the real Fire/Commit/Apply pipeline (no reflection):
- ProportionalAbsorptionSplitsTheLeftoverBetweenTheLanes /
...LeavesNothingWhenTheItemAbsorbsEverything: the ruling's own literal case
(two lanes bringing D1=25/D2=15 onto one 2x1 item), both the partial- and
full-absorption branches.
- MirrorSymmetryHoldsWithASharedMultiCellItem: a symmetric hull and its own
mirror-image fixture must produce the same sorted leftover pair. Fails on
the pre-fix-batch code (commit d1cc047a) at itemDurability 20 ({0,20} vs
{5,15}); a durability equal to one of the two D values turned out
degenerate and was rejected first (see the test's own comment).
- EveryAxisAlignedSpreadShotStrikesExactlyTwoNPlusOneFacingCells: Soul's own
owed regression guard on lane spacing, independent of item sharing. Killed
by hand-mutating FireControl.cs's laneSpacing by *.99f (R1b): 28/400
interior-column hits drop a facing column.
- ModelSweepAgreesWithProductionIncludingSharedMultiCellItems: an independent
model (armour-then-item-then-hull per lane, extended with the same
survey/sum-then-split shape production uses) checked against production
over a sweep of two hull sizes, three bearings and both 2x1 bars and
non-convex L-shaped items (Build gains lBars/LShape for this).
Tool-hardpoint items (Bar, Marker) can only occupy interior cells
(Entity.ItemFits), so every fixture above needed a hull at least 3-4 cells
deep -- shallower hulls (used in an earlier draft) fail marker/bar equip
outright.
Hand-verified against the M13 "lane_order_reversed" mutant Soul recorded as
surviving on the pre-fix code (FireControl.cs, Lanes' own `var k = li - n`):
MirrorSymmetryHoldsWithASharedMultiCellItem fails on the pre-fix code under
that mutant and passes on this cut's code under the same mutant, confirming
the new resolution is order-independent of which physical lane a buffer index
happens to hold, not only of Apply's own loop order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling (2026-09-25): "proportional absorption; go". A multi-cell item
struck by several lanes of one shot now absorbs their combined incoming damage
(up to its own remaining durability) and each lane's leftover is in proportion
to what it brought, replacing the old behaviour where whichever lane Apply
happened to visit first drained the item and the next lane inherited whatever
was left.
Entity.Absorb is split into ArmorAbsorb (per-cell, never shared) and ItemAbsorb
(the single-lane path), so FireControl.Apply can call armour immediately and
defer item resolution only for a cell whose item is reachable by more than one
lane this shot. FireControl.Apply gains a geometric survey (which items are
reachable by >1 lane, decided before any damage is applied) and takes the
untouched sequential walk (ApplySequential) whenever nothing is shared, or a
new coordinated walk (ApplyWithSharedItems) that deposits each lane's
post-armour remainder into a shared item's pool and resolves it once every
contributing lane has arrived, splitting the leftover proportionally
(sum-then-split, order-independent by construction). A deterministic tie-break
(smallest first-contact Entry) breaks the rare "opposite order" deadlock two
non-convex shared items can create between two lanes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Self's review found that commit 4e6495dd's fix for F1 replaced the per-lane
forward-arithmetic defect with a second, still-independent membership
computation: Lane's own half-open admission (Lo <= s < Hi, from a cell's own
Extent) versus Lanes' ceil((cell.Lo - centreLateral) / laneSpacing) lane
index. The two agree only when a cell's own (Hi - Lo) equals the externally
supplied laneSpacing exactly in float -- the identical two-derivations trap
Cut 12.2's own fix batch already found and removed from Lane itself,
reintroduced one layer up. Confirmed reachable: on commit 4e6495dd, solid5
b=(0,1) s=0.49999997 n=1 leaves the ceil-based centre lane EMPTY while Lane(s)
itself finds all 5 cells of the row -- breaking R3 ("the centre lane always
meets metal", Commit's own guard) and the 12.2 placement contract.
FireControl.Lanes is now built entirely out of FireControl.Lane: it calls
Lane(hull, b, centreLateral + k * laneSpacing, ...) once per lane index, so
Lane(s) is literally this function's n = 0 case rather than a parallel
re-implementation of its rule. Commit's placement and Apply's damage split
now read one function. The narrower question left -- can two individually
correct Lane() calls, at two different lines, still walk the same cell? --
is resolved by ownership (the lane closest to the centre keeps a contested
cell; the centre lane, k = 0, never loses one), not by a competing formula,
so the centre lane's final cell set is always exactly Lane(centreLateral)'s
own output.
Lane() itself is unchanged (still called directly by Commit and by
FireControlCut12Tests' edge-seed and interval-edge tests, all still green:
HitsLandOnTheFacingEdge, LaneSeamIsHalfOpen, LaneNeverEmptiesAtAnIntervalEdge,
Cut12-edge-68442709, Cut12-edge-64078070).
Adds LanesQueryIsDisjointMatchesLaneAtCentreAndNeverEmptyOnHull: now that
Lanes is a public, pure geometric query, the boundary sweep is a behavioural
test of its own contract, no reflection -- over every cell's own shadow edge
and float neighbours, at exact-axis, 45-degree, |bx| > |by| bearings and
Soul's own two reproduction cases, spread 1-2: (a) no cell is walked by two
lanes, (b) the centre lane equals a plain Lane(s) call, (c) the centre lane
is never empty when the centre lateral is inside the hull's own shadow. Hand-
confirmed to fail on 4e6495dd's ceil-based Lanes (property (b), the same
input above) and to pass on this commit (fc123fix2-mut3.log).
Adds CommittedCellIsTheFirstCellTheCentreLaneDamages: an end-to-end check
through Fire -> Commit -> Apply (damageSpread 0, a single lane) that the
committed outcome.Cell is the first cell Apply damages.
Full suite: 286 tests, identical across 3 runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F3 (medium, confirmed): NoCellIsStruckByTwoLanesAtAnAngledBearing used
penetration 0 (impact cells only) and one fixed-seed shot per bearing, at
only two bearings, so it survived R1e (one-cell spacing when |bx| > |by|),
R1h (spacing 1/max(|bx|,|by|)), R1c (spacing * 0.9), and R1b (spacing *
0.99). Its own comment also falsely claimed a plain revert "fails this at
both bearings" without ever checking a real revert.
Reworked as NoCellIsStruckByTwoLanesInOneShotAtAnyBearing: seven bearings
(exact axis, both 45-degree diagonals, |bx| > |by| and |bx| < |by| on both
sides, all within the fixture's own FiringArc), 300 hits sampled per bearing
at a wide targeting spread (so the committed Lateral's draw visits many
points along the hull's shadow), and ample penetration.
Hand-verified against fc123fix2-mut.log: reverting to one-cell spacing (R0)
and R1c/R1e/R1h are all caught by the full suite -- but not by this test.
F1's structural fix (this branch's earlier commit, FireControl.Lanes) assigns
each cell to exactly one lane index by a single computation, so a wrong
laneSpacing can misassign or drop a cell but can no longer make two lanes
claim the same one; R0/R1c/R1e/R1h are instead caught by the pre-existing
damage-shape tests (DirectHitAtAnAngledBearingMatchesAnIndependentModel,
FootprintWidensAtAnAngledBearingComparedToAxisAligned, and others). R1b (a
1% narrowing) survives the whole 97-test FireControl suite under any of
those tests, including this one, even at 5000 hits per bearing -- recorded
here as an open survivor for Soul's own mutation pass, not claimed dead.
This test's own job is now a standing regression guard on the disjointness
invariant through the real Fire/Commit/Apply pipeline, not a killer of
spacing-magnitude mutants (that job now belongs to the damage-shape tests).
F4: deletes MirrorImageShotsProduceStatisticallyMirroredHullDamage. Soul
showed it cannot fail on spacing -- it survives the full revert and every
spacing mutant, because no per-shot mirror pair is reachable through Fire
(LateralDraw's distribution reverses under mirroring, so a shot and its
"mirror" are independently drawn samples, not a matched pair). It was meant
to pin lane processing order under a shared-cell race; that question is
folded into the F2 disjointness rule (a shared cell can no longer arise) and
the pending F2 ruling on multi-cell item lane order, which remains on hold
per this fix batch's brief.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soul's second pass (CONFIRMED): even after the F2 shadow-width fix, Apply's
per-lane loop still called Lane() once per lane index, each call re-deriving
its own lateral s(li) = Lateral + (li - n) * laneSpacing by forward float
addition, then testing that value against a cell's Extent interval
independently of every other lane. Two lane lines are only exactly
laneSpacing apart in real arithmetic; accumulating the offset by repeated
addition can shift a side lane by an ulp or two, letting it land inside a
neighbouring cell's half-open interval that its own unrounded value would
never reach -- the same "two derivations of one quantity" defect Cut 12.2's
own fix batch already removed from Lane itself, recurring one layer up in
Apply.
FireControl.Apply now calls a new Lanes() that decides each candidate cell's
lane index with one computation: k = ceil((cell.Lo - centreLateral) /
laneSpacing) is the unique integer for which centreLateral + k * laneSpacing
falls in the cell's own [Lo, Hi) -- derived once, from values no other lane's
arithmetic has touched. A single Ceiling call returns exactly one integer, so
a cell can be assigned to at most one lane by construction, not by float
luck. When k == 0 this is the same condition Lane() tests for centreLateral
itself, so the centre lane's cell set is unchanged (the 12.2 placement
contract, HitsLandOnTheFacingEdge, still holds). laneSpacing is still used,
unchanged, to place each admitted lane's own line for AlongBearing's slab
solve -- geometry, not membership.
Lane() itself is untouched (still used directly by Commit and by
FireControlCut12Tests' edge-seed tests).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The field was deleted from PlanetSettings but the authored asset still
serialized MiningDifficulty: 500. Byte-exact removal of the one line;
nothing else in the file changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Zone.Wear did not check whether the chunk was already broken. A later hit
read wear.Damage starting from 0 (the value ChunkWear carries while
BrokenUntil is in the future), stayed under hitpoints, and wrote
BrokenUntil = null -- un-breaking a chunk mid-respawn.
Damage delivered to a chunk that is currently broken (zone time <
BrokenUntil) now changes nothing: Wear returns false (this hit did not
break it -- it was already broken) and BrokenUntil, and Damage, are left
untouched. The chunk stays absent until its original respawn time, then
resumes wear from zero as before.
Adds two behavioural tests: a re-hit inside the respawn window, both
below and above hitpoints, must not un-break the chunk or move its
respawn time; and the same rule survives a save/reload round trip.
Confirmed by reverting the fix and watching both new tests fail with the
chunk reappearing early, then restoring the fix byte-exactly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MirrorImageShotsProduceStatisticallyMirroredDamage compared per-cell armour
damage SUMS between a bearing and its mirror, using heavy (1000) uniform
armour. That armour never depleted, so no cell's absorption ever competed
with another lane's, and the test could not see the one thing the operator's
ruling is about: two lanes racing for one cell's finite armour under the old
one-cell spacing. Renamed to MirrorImageShotsProduceStatisticallyMirroredHullDamage,
it now resets a small, finite armour value before every individual shot (so
the race is live on every trial) and compares aggregate HULL damage, where a
lost race's leftover actually lands.
Finding, reported rather than hidden: even with this fix, the test does not
fail under the reverted one-cell spacing. Reasoning checked against measured
behaviour: FireControl.Apply always processes lanes in a fixed index order
(li ascending, i.e. lateral increasing), never based on the bearing's sign.
Over many INDEPENDENTLY random lateral draws, a bearing and its mirror sample
statistically equivalent distributions of "which side's lane goes first" --
the fixed-index tie-break rule respects the mirror symmetry in aggregate, so
independently-drawn trials wash out the order-dependent bias that Soul's own
LaneOrderDemo probe found (that probe compares ascending vs descending
processing of the SAME committed shot, not independently-drawn mirrored
bearings). A production-level test that reliably fails under the old spacing
for this specific property would need to force an exact, deterministic
lateral for two geometrically mirrored shots, which is not reachable through
Fire/Commit without reflection (Commit's lateral draw is randomised, and
Apply is private). NoCellIsStruckByTwoLanesAtAnAngledBearing and
FootprintWidensAtAnAngledBearingComparedToAxisAligned remain the two tests
that actually discriminate the ruling behaviourally (confirmed failing under
a hand-reverted one-cell spacing). The mirror test stays in the suite as a
true, currently-passing invariant and useful regression coverage, not as a
defense of this specific ruling.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F1: no existing test through Fire commits at a non-axis bearing.
DirectHitAtAnAngledBearingMatchesAnIndependentModel checks every cell's
armour, every item's durability and the hull's own total against an
independent double-precision ray/box model (ported from the Soul probe that
verified this cut's arithmetic to 14,984/15,000 shots), at 45 degrees, a
shallow angle and a bearing with |bx| > |by|.
DiagonalDirectHitReachesEveryCornerCellInOrderThroughFireCommitApply pins the
spec's own diagonal case (Probe1's corner-crossing lane) through the full
Fire -> Commit -> Apply path rather than a direct Lane call: the lane reaches
(0,0), then (1,1), then (2,2), in that order (a bearing this close to 45
degrees also admits the diagonal's row/column neighbours, so the walked lane
is usually five cells, not three -- the spec only requires the three corner
cells present and in order, the same standard the existing direct-Lane test
already pins).
F2 (operator ruling, 2026-09-25: spread lanes should diffuse sideways rather
than double up): three behavioural tests, deliberately not asserting the
spacing value itself (operator: "test behavior, not shape").
NoCellIsStruckByTwoLanesAtAnAngledBearing fires at 45 degrees and at a bearing
with |bx| > |by|, spread 2, heavy uniform armour, and checks no cell fires
ArmorDamage twice. MirrorImageShotsProduceStatisticallyMirroredDamage compares
300 hits at a bearing against 300 at its mirror image, and asserts the
per-cell damage totals are mirrored within tolerance.
FootprintWidensAtAnAngledBearingComparedToAxisAligned compares the lateral
span of hit cells for the same spread at 45 degrees against axis-aligned, and
asserts the angled footprint is wider.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stryker over Zone.cs (concurrency 1, 111 mutants tested) surfaced four real
gaps on this cut's own lines, all now killed:
- ChunkExists's range check (`index < 0 || index >= length`) survived both an
AND weakening and a `>` boundary weakening, because nothing tried an
out-of-range index on either side alone.
- The broken-until comparisons in ChunkExists/ChunkRadius and the pack-time
pruning in IsExpired all survived a `>`/`<` vs `>=`/`<=` boundary flip,
because every prior test advanced zone time well past BrokenUntil instead
of landing on it exactly.
- ChunkId.Equals survived an AND-to-OR weakening: the wear dictionary's own
GetHashCode already separates same-field/different-index and
same-index/different-field pairs into different buckets, so a bad Equals
was never reached through a lookup. Testing equality directly closes that.
Two survivors on this cut's lines are recorded as equivalent, not chased:
ChunkRadius's `wear.Damage <= 0f` boundary (at damage == 0, DamagedSize and
UndamagedSize evaluate the same curve at the same point, so no input
distinguishes the two branches), and ChunkId.GetHashCode's exact formula (a
hashcode's bit pattern isn't a contract; any deterministic function of the
same two fields trivially keeps equal keys hashing equal, and this
dictionary is too small for distribution to matter).
The remaining survivors (lines 44, 75, 170, 200, 212, 216, 319, 336-412) sit
in Update/GetOrbitPosition/GetSecurityLevel/GetHeight/GetNormal/PowerPulse,
none of which this cut touches; they predate it and are out of scope here.
Mutation score 38.62% -> 42.33% (78/111 killed, 2 timeout, 31 survived, all
31 either pre-existing or one of the two equivalents above).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous AlongBearingReturnsTheExactSlabIntersection wrote out SlabAxis's
own threshold (1e-9f) and clamp a second time in its ExactSlab helper, so a
mutant that only moved that threshold (C7b, 1e-9 to .2) agreed with both
copies identically and survived.
AlongBearingReturnsTheExactSlabIntersectionAtSeveralBearings replaces it: the
expected entry/exit now come from an exact ray-box intersection that never
special-cases a near-zero bearing component -- IEEE division by a signed zero
already yields the correctly signed infinity, so an axis with no motion is
unconstrained without a hand-written epsilon to disagree with SlabAxis's own.
Checked at the original fixture's bearing plus two within about 11 degrees of
an axis, inside C7b's mutated .2f threshold but nowhere near the real 1e-9f
one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F4: AnAPRoundStopsAtTheFirstGapAndLeavesTheFarSideOfTheNotchUntouched, a notched
U-shaped hull, pins Lane's own contiguity break (an AP round with ample
penetration must stop at the first gap, not reach the far side).
F5: EverySideLanesRemainderReachesTheHullNotOnlyTheCentreLanes, an
axis-aligned spread-1 fixture (lanes share no cells, so order cannot matter),
pins that every lane's own leftover reaches the hull, not only the centre
lane's.
F6: AbsorbSpendsArmourBeforeTheItemOnTheSameCell, direct Absorb calls (this
file's existing style for the function's own arithmetic) with armour and an
item on the SAME cell for the first time -- no prior fixture combined them.
F7: ADirectHitFromStarboardDamagesStarboardCellsWhenFacingIsMostlyLateral, a
direct-hit test at a facing with |fx| > |fy|, computing "starboard" as the
independent right-perpendicular of the facing rather than reading it from
Entity.ToSchematic. Cut 11's Splash tests are the only thing guarding this
handedness today, and 12.4 deletes Splash.
Both F4 and F5 track hull damage per-attempt (reset on each retry), not as a
before/after total across every retry: this fixture's off-target lanes carry
no armour, so a stray hit on the wrong column during a retry spends real
damage on the hull and would otherwise contaminate a cross-attempt total.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling (2026-09-25): overlapping spread lanes should diffuse sideways
rather than double up. FireControl.Apply now spaces lanes by the shadow width
a cell casts at the committed bearing (Extent's own h, doubled) instead of by
one cell -- 1 at axis-aligned bearings (unchanged), up to sqrt(2) at 45
degrees. Reuses Extent, the one function that already owns h for Silhouette
and Lane, rather than re-deriving it a second way.
F3: PenetrationExcludesACellExactlyAtItsOwnBoundary's 1e6-durability marker
absorbed the whole shot before the walk could reach the boundary cell, so the
test could not fail under the `>=`-to-`>` mutant. The marker now carries 10
durability, leaving a remainder that would visibly damage the boundary cell if
the walk incorrectly included it. Adds
PenetrationExcludesCellsBeyondANonIntegerDepth, pinning the ordinary
"< penetration" comparison at a depth (1.5) that isn't an exact-integer
boundary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixtures use two belts of distinct sizes and authored-shape hitpoints/
respawn curves, exercised over uneven time steps, never the flat default
curves that would hide a broken formula.
Pins: damage accumulates and shrinks the chunk before it breaks; the break
threshold is strictly greater-than (equal-to-hitpoints does not break);
respawn is a comparison against zone time, reached through uneven dt steps;
a chunk broken, respawned, and damaged again starts from zero rather than
leaking stale wear; wear does not cross-talk between chunks that share a
field or an index; wear round-trips through PackZone/Zone(pack); a healed,
non-broken entry is pruned from the pack; and a record built as raw
MessagePack bytes with only the six pre-Cut-2 keys (not a default-
constructed ZonePack) deserializes with ChunkWear == null and loads with no
wear, per the nullable-key-6 persistence rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MiningTool (its Execute range-checked against nothing, since no writer ever
set AsteroidBelt/Asteroid) and Zone.MineAsteroid (mints nothing; the
SimpleCommodity drop was commented out) are deleted, per Q1=A: "Bring back
the drill, indeed." Union tag 26 is retired, not reused. ResourceScanner's
dead survey Execute and its scan-target fields go too; its authored Range/
MinimumDensity/ScanDuration stay pending Q2. PlanetSettings.MiningDifficulty
had one reader, now gone.
Chunk wear (damage, broken-until) moves off AsteroidBelt's three per-belt
dictionaries and onto Zone as the one owner (Q3=A): Zone.Wear is the only
writer, ChunkExists/ChunkRadius are the readers, and respawn is a lazy
comparison against zone time, never a per-tick loop. ChunkId is
(CultRecordKey Field, int Index) -- "Field", not "Belt", because chunks are
meant to generalize past asteroid belts to other debris fields later.
Persisted in ZonePack's new key 6 (List<ChunkWearPack>, nullable so an older
record with no key 6 loads with no wear instead of throwing), pruned of
healed, non-broken entries at pack.
The break threshold stays strictly greater-than, matching today's behaviour:
damage exactly equal to hitpoints does not yet break the chunk.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
First Stryker pass over the changed lines (Entity.cs: Absorb/DamageHull;
FireControl.cs: Apply, Splash) killed 584/844 candidate mutants; these
six survivors on the cut's own diff get real behavioural tests instead:
- Entity.Absorb's `d > 0f` and `d > 0.1f` guards, and DamageHull's
`damage > .1f` guard, each had their boundary flipped to `>=` and
survived -- at the exact boundary (0 incoming, or exactly the .1f
carry-forward threshold), real code does nothing while the mutant
fires an extra event or spends an extra fraction. Not a rare float
coincidence: a spent lane routinely reaches a cell with exactly 0
remaining, and an authored penetration/armour value can land exactly
on .1f.
- FireControl.Apply's `target.IncomingHit.OnNext(shot.Source)` (moved
here from the deleted Entity.ApplyHit) had no test that it still fires.
- FireControl.Apply's two `walked[li] > 0` checks (metalLanes counting,
and the absorb-loop entry) both flipped to `>= 0` and survived, since a
lane count can never be negative -- a real bug class (a lane entirely
off the hull would dilute the split and leak a damage share to the
hull) that a hull no wider than the spread now catches.
- The penetration cutoff's `>= shot.Penetration` flipped to `>` survived
at the exact boundary -- an axis-aligned lane's entry values are exact
integers, so an authored integer penetration routinely lands exactly on
a cell's own entry difference.
Recorded, not chased: `shot.Outcome.Lateral - (li - n)` (the lane-offset
sign) is an equivalent mutant -- 2n+1 lanes built symmetrically around n
cover the same SET of offsets either way, just enumerated in reverse, and
the loop's own per-lane work is order-independent. The missing
`ArrayPool<LaneCell>.Return` survivor is a perf-hygiene gap (GC pressure,
not a spec'd invariant -- only the forecast path is zero-allocation), not
yet reached by a dedicated allocation test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DamageIsAbsorbedInOrderAlongTheRay, ArmourFacesTheShot,
BuriedCockpitNeedsPenetration, SpreadWidensTheImpactAcrossLanes,
ARayThroughACornerCrossesTheCornerCell and LaneRemainderReachesTheHull,
per docs/fire-control-cut.md's Cut 12.3 verification list. Builds its own
fixtures (the file's own Build), matching every other cut's own test file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Items carried into 12.3's brief from 12.2's third Soul pass, all on code
12.3's own lane walk builds on:
- LaneSeamIsHalfOpen pins the s >= Hi admission test (FireControl.cs) on
the smallest fixture where the seam matters -- the s > Hi mutant that
survived 12.2 would have interleaved two columns at a shared seam once
12.3's walk started reading more than buffer[0].
- AlongBearingReturnsTheExactSlabIntersection gives AlongBearing/SlabAxis
their first test of actual VALUES (every mutant there survived in 12.2,
equivalent while only buffer[0].Cell was read) -- a diagonal fixture
checks Lane's own Entry/Exit against an independent slab solve, cell for
cell, in order.
- Corrected the allocation comments on Forecast and
LaneAndSilhouetteAllocateNothingGivenAPooledBuffer: the ~120 B/call on a
gated-in HitProbability is three GetBehavior<TargetingSystem> calls (not
EquippedItem.Behaviors, which is a plain array), plus CellsOf's ~944 B
when an item is aimed at; noted that the loose <500_000 bound doesn't
catch a reintroduced comparer wrapper but the buffered zero-allocation
asserts do.
- ZeroProbabilityShotCommitsAMissWithoutBuildingASilhouette is now
behavioural: PFire 0 comes from closing VisibleEntities (the real gate),
and "no Silhouette was built" is checked by asserting zero allocation
rather than reading CommitProbability's own struct default.
- Dropped the unused System.Reflection import.
Also replaces ExpectedMarchCells, which reproduced Entity.ApplyHit's own
deleted 0.5-step march, with ExpectedLaneCells, built on the
already-verified FireControl.Lane plus an independent penetration cutoff --
used by TurningAfterCommitChangesNothing, whose fixture also gets real
per-cell armour (the flat 1000 armour every Build() authors would let the
new sequential Absorb model's first cell alone swallow the whole 5-damage
shot, leaving nothing to show on the rest of a several-cell lane).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HardpointHitDamagesItemThenHull and PenetrationMarchIsPlanar both called
Entity.ApplyHit directly, which Cut 12.3 deletes. The rules they pinned:
"damage above armour plus item durability zeroes the item and passes the
remainder to the hull" is now FireControlCut123Tests.DamageIsAbsorbedInOrderAlongTheRay,
through the real Fire/Commit/Apply path. "The penetration march is planar,
in the target's own schematic frame" is subsumed by 12.2's own
HitsLandOnTheFacingEdge and TurningArmourIntoTheShotTakesItOnTheArmour
(which already exercise FireControl.Apply's committed, schematic-frame
bearing end to end) and by FireControlCut123Tests.ArmourFacesTheShot,
which pins planarity across several non-axis-aligned facings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deletes Entity.DamageSchematic (the even split over the whole hit shape)
and Entity.ApplyHit (the 0.5-step penetration march), replacing both with
Entity.Absorb (armour then item, per cell) and Entity.DamageHull, moved
verbatim from DamageSchematic's own body. FireControl.Apply now builds
2n+1 parallel lanes from the committed Bearing/Lateral (Q12-2 = A),
shares damage evenly over the lanes that meet metal, walks each lane in
order via the already-verified FireControl.Lane, absorbing cell by cell
up to the penetration depth, and sends every lane's own remainder to the
hull where it ends (Q12-3 = A). Splash is rewritten onto Absorb/DamageHull
(arithmetically identical to before) since DamageSchematic is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measured (Release, 7x9 solid hull -- the LonginusX shipped-hull fixture the
brief named is not available to this test file):
- Lane: 0 B / 1000 calls (was: 64 B/call from Array.Sort's comparer wrapper;
now confirmed 0 after the earlier IComparable<T> change in this batch).
- Silhouette (caller-supplied buffer): 0 B / 1000 calls.
- HitProbability, gated in (a real, non-gated-out call): ~120 B/call. This is
NOT from Lane, Silhouette or the comparer -- it comes from Accuracy/
Resolution's own GetBehavior<T>, which walks Entity.Equipment and each
item's Behaviors (both ReactiveCollection<T>); that type's enumerator is
heap-allocated per foreach. This predates Cut 12.2 and is out of this cut's
scope to fix.
- Inspect, gated in: ~280 B/call (same cause, more GetBehavior<T> calls).
The Forecast comment claiming "the forecast path (HitProbability, Inspect) no
longer allocates on a gated-in call" was not true and is corrected to say what
was actually measured: Silhouette itself is allocation-free given a pooled
buffer, and the residual cost belongs to Entity's own collection choice.
LaneAndSilhouetteAllocateNothingGivenAPooledBuffer asserts the true zero-alloc
claims exactly and records (with a loose regression bound, not a false zero)
the unrelated Entity-level cost on the full HitProbability path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CommitProbability's `if (p <= 0f) return 0f;` guards against building a
Silhouette for a shot that cannot hit. Stryker's `p < 0` boundary mutant
(excluding only p == 0 exactly) survived because nothing exercised the guard
at that exact boundary. ZeroProbabilityShotCommitsAMissWithoutBuildingA
Silhouette sets a real, fired shot's PFire to 0, calls CommitProbability
directly and asserts sil stays the zeroed default (Intervals null, Count 0 --
no Silhouette was built), then resolves the shot through Zone.Update and
checks it lands as a clean miss.
Manually verified against the exact `p < 0` patch: it now fails (sil.Intervals
comes back non-null, a real Silhouette array).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only a 2x2 (BigMarker) aimed item was ever tested -- a square's centroid and
its bounding-box centre are the same point, so N11 (aim point = bounding-box
centre instead of the true cell-average centroid) survived. AimingAtAnLShaped
ItemCentresOnItsCentroidNotItsBoundingBox equips a 3-cell L-tromino (Build's
new equipLMarker fixture), fires from a diagonal facing so the shape's true
asymmetry projects fully onto the lateral axis, and asserts the empirical mean
lateral offset tracks the item's own true centroid (computed by the test) and
not its bounding-box centre.
Manually verified against N11's own patch: it now fails (empirical -2.139,
tracking the wrong bounding-box prediction -2.121, against the true centroid
-1.886); the existing BigMarker test is unaffected since a square's two
candidate points coincide.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TheHudEstimateIsTheCommitPrice used to fire each shot, then reach into
Zone.PendingShots and overwrite the frozen Tracking field after the fact. Build
now takes a `tracking` parameter (default 100000, matching the old fixed
value) alongside Precision and Accuracy, so this test authors Tracking = 6 up
front like every other frozen shooter stat, with no post-hoc patch loop.
Manually verified M15 (Commit applies PSpread twice) still fails this test
(empirical 0.070 vs forecast 0.126) after the rewrite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F8 (Soul's earlier fix batch, 2026-09-24) moved range computation ahead of the
visibility gate in FireControl.PFire so Fire's own flight time stays real even
when the shot prices at PFire 0, but no test observed that behaviour --
N4 (Soul's own mutation set, reverting F8) survived every existing test.
InvisibleTargetStillResolvesAtItsRealFlightTime fires at a target removed from
VisibleEntities and checks the shot does not resolve before its real 5s
arrival (N4's bug would resolve it instantly, at range 0) and resolves as a
miss once it does.
Manually verified against the exact N4 patch: it now fails (arrival at ~1s
instead of ~6s, i.e. range treated as 0).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TwoProngHullSplitsTheScatterAcrossBothIntervals's own within-prong check
(atNearColumn > atFarColumn) held under M12/M12b (an entire second silhouette
built at the wrong sigma) and the sigma/sqrt(2) Stryker mutants, because every
wrong sigma tried still puts more mass on the nearer column -- direction alone
cannot separate a sigma of 1.5 from 1.5*sqrt(2). Their analytic near/far mass
ratios are well apart (~2.9 true vs ~1.73 at sigma*sqrt(2)), so the test now
computes the true ratio from the same per-cell Gaussian integral it already
uses and asserts the empirical ratio (8000 shots) tracks it within a tolerance
that rejects the wrong-sigma ratio.
Manually verified against the exact M12 and M12b patches from Soul's mutation
script: both now fail this test (empirical ratios 581 and 1.68, matching a
collapsed and a sqrt(2)-shifted sigma respectively, against the analytic ~2.9).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces LateralDrawIsHalfOpenAtTheShadowsTopEdge (reflection into the private
LateralDraw) with:
- ShotAtBearingLoTouchingOnlyACellCornerHitsMetal and
ShotAtBearingLoOnTurningArmourHitsMetal, Soul's two named-seed crash repros
(Cut12-edge-68442709 and Cut12-edge-64078070 -- StableHash(zone name) seeds
Zone.CombatSeed, so the exact name reproduces the exact die roll) run
through the real public Fire/Zone.Update path, asserting the shot commits a
hit whose cell's own projected lateral interval contains the drawn Lateral,
instead of throwing.
- LaneNeverEmptiesAtAnIntervalEdge, the general sweep: every interval's Lo and
the point just below its own half-open Hi, across several bearings,
precisions and hulls (including a non-convex, holed one), must never return
an empty lane from FireControl.Lane.
Build gained a zoneName parameter (Cut 6.1's one-fixed-name convention doesn't
serve a repro that needs an exact CombatSeed) and a tracking parameter and an
equipLMarker fixture, both used by later findings in this batch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
S1 (HIGH, crash in play): Lane's own shadow prefilter (a re-derivation of
Silhouette's centre/h) and the independent SlabAlongB 2-axis intersection test
could disagree at a float edge, rejecting a cell the shadow sum had already
counted metal for -- an empty lane after a hit passed its roll (R3 violation),
reproducible through Zone.Update. Both are replaced by one shared admission
test (Extent(c, axis), the exact support of a unit square along an axis) that
Silhouette and Lane now both call; SlabAlongB is deleted, and its geometry is
kept only as a non-rejecting AlongBearing() that computes entry/exit for
already-admitted cells (the doc's own equivalence proof -- "s in the shadow" is
exactly "the line intersects" -- makes rejection there unreachable by
construction, not merely unlikely).
S3 (allocation): Interval and LaneCell now implement IComparable<T> (same
order as the deleted IntervalByLoComparer/LaneCellComparer) so Silhouette's and
Lane's sorts use Array.Sort(T[], int, int), which resolves T's own comparison
at JIT time, instead of the (T[], int, int, IComparer<T>) overload, which
wraps even a cached singleton comparer in a fresh delegate per call.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stryker over Zone.cs (231 mutants, scoped by --mutate to this cut's file)
found two real gaps, both NoCoverage/Survived, not equivalents:
- Zone.EvaluateBelt and AsteroidBelt.Evaluate -- the renderer's own path
(ZoneRenderer.cs Update, AsteroidBeltUI.Update) -- were never called by any
test, so the whole batch body and its loop bound were unreached.
EvaluateBeltFillsEveryAsteroidExactlyAsChunkPoseWould pins that the batch
path fills every index exactly as the per-index ChunkPose would, closing
the gap without duplicating the orbit formula a second time.
- AsteroidBelt.Radius (data.Asteroids.Max(a => a.Distance)) had no assertion;
a Max-to-Min mutant survived. BeltRadiusIsTheFarthestAsteroidsDistance pins
it, since the renderer's visibility bounds and minimap mesh both size off
this field.
Full suite: 262/262 green (260 + these 2).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Zone.Update no longer threads a per-belt Task.Run over a double-buffered
Transforms/OrbitPosition pair. AsteroidBelt.Pose/Size are the one place a
chunk pose is computed, a pure function of zone time; Zone.ChunkPose and
Zone.EvaluateBelt read them at the zone's own time. MiningTool,
ResourceScanner and ZoneRenderer (belt matrices and AsteroidBeltUI) all read
through Zone instead of the deleted per-tick buffers; the renderer owns its
own float4[] buffer, filled fresh every frame.
Deleted: System.Threading/.Tasks usings, BeltUpdates, the wait loop, the
per-belt copy+Task.Run, NearestAsteroid (dead/broken reader), the private
UpdateAsteroidTransforms, and AsteroidBelt's four pose fields plus their
allocations. ~65 lines, matching the cut map's estimate.
miningprobe, densest zone of the operator's save (EAC-2265, 809 asteroids,
8 belts), rebuilt against this cut and rerun 10x each side:
before (branch point, 8bd25f6f/e729e6d6):
Zone.Update wall 541-3654 us/tick, CPU 120-391 us/tick (7 clean runs)
3/10 runs crashed: InvalidOperationException from Zone.GetOrbitPosition,
the documented _updatedOrbits/Orbits data race between the main thread's
Clear() and a belt Task.Run
after (this commit):
Zone.Update wall 95-185 us/tick, CPU 99-152 us/tick, in line with the
31-39 us "no belt work" baseline the map recorded -- belts are free per
tick
0/10 races
ChunkPose has no buffer to go stale: ~0.12 u of parent+asteroid motion is
visible across one 17 ms tick instead of being one tick behind
tests/Aetheria.Shared.Tests/MiningCut1Tests.cs (new, 260/260 suite green):
ChunkPoseIsTheOrbitAtZoneTime, ChunkPoseDoesNotDependOnUpdateCadence,
DamagedChunkShrinksAndABrokenOneHasNoSize -- each expected value is the
closed-form orbit/size rule reimplemented in the test, never a call to
AsteroidBelt.Pose/Size.
Negative grep (Zone.cs, "Zone Display", ServerShared/Behaviors) for
Task\.Run|System\.Threading|NewTransforms|\.Transforms\b|BeltUpdates|
NearestAsteroid|\.OrbitPosition\b|NewOrbitPosition: one hit, the commented
line in MineAsteroid that Cut 2 deletes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator, 2026-09-25: target the chunk, hit it with a weapon whose damage
suits the ore, and roll a weighted loot table from damage type, composition
and weapon stats. First cut slims the belt simulation, which probes show is
the heaviest per-tick cost and a data race.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F1 (correctness): LateralDraw clamped the drawn lateral to the closed
[Lo, Hi], not the spec's half-open [Lo, Hi). Soul enumerated all 2^24
NextFloat outputs against TurningArmourIntoTheShotTakesItOnTheArmour's own
geometry and found 3 that draw exactly Hi, which Lane's own half-open filter
then admits no cell for -- a hit that passed its roll landing nowhere (R3).
Fixed the clamp; Lane's silent int2.zero fallback on an empty lane is now a
thrown InvalidOperationException, since an empty lane is provably impossible
after the fix. Committed Soul's edge probe as
LateralDrawIsHalfOpenAtTheShadowsTopEdge.
F6/F8: HitProbability, Inspect and Fire each assembled bearing -> Silhouette
-> PSpread (or recomputed range) in their own copy; a shooter-frame bearing
bug in HitProbability's copy survived while the identical bug in Inspect's
copy was caught. Collapsed into one Forecast function and one PFire(out range)
overload; both callers, and Fire, read the one computation.
F2: FireControlCut12Tests' zone names carried a Guid, breaking every other
cut's fixed-seed convention (FireControlCut6Tests.cs:62-63) and reseeding the
die every run -- the actual source of the flake in
TurningArmourIntoTheShotTakesItOnTheArmour. Fixed to a fixed zone name.
F3/F4/F5/F7: added a two-prong hull fixture (LateralDraw's walk past the first
interval, and a wrong-sigma placement, were unreachable when every fixture
projects to one interval), a real-roll empirical test for CommitProbability
(the old one called the pure function twice and compared it to itself),
enough Penetration in TurningAfterCommitChangesNothing for ApplyHit to
actually read the bearing, and a 2x2 item fixture for ResolveAimPoint's
average (every "Marker" elsewhere is 1x1, so sum/length and sum*length agree).
F9: Lane's own comment claimed no allocation while its sort used
Comparer<T>.Create over a closure; LaneCell now carries its own precomputed
projection and Lane sorts with a stateless singleton comparer. Corrected an
overclaiming comment in FireAuthorityTests (SigmaFloor makes the aimed-lane
draw ~68% per shot, not deterministic; the fixture's fixed zone name is what
actually makes it reproduce).
Cost: Silhouette takes an optional caller-rented Interval[] buffer; the
forecast path (HitProbability/Inspect, via Forecast) rents from
ArrayPool<Interval>.Shared and returns it before returning, since neither
reads sil.Intervals after the call. Commit/CommitProbability still get an
owned array, since LateralDraw needs it alive past the call. Measured on
LonginusX (66 cells, Release): unaimed 672 B -> 184 B/call (1512-1637ns before,
1482-1560ns after); aimed 1160 B -> 672 B/call (2123-2140ns before,
2094-2162ns after) -- the remaining ~488B/call gap between aimed and unaimed
is ResolveAimPoint's own CellsOf allocation (a List<int2>+ToArray per call),
not in this batch's scope.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first Stryker pass (375 mutants, 69 survivors on this cut's own changed
lines) found one real split-authority bug and several untested edges:
- HitProbability recomputed target.Position - source.Position a second time
for PSpread's range, a second place the same subtraction could flip sign.
PFire now returns its own gate-computed range by out parameter; HitProbability
reads that one value instead of a second copy.
- TravelDirection's point-blank fallback and Fire's null-target guard around it
were only ever exercised at shooter/target pairs where the real direction
happened to equal the (0,1) fallback by coincidence.
- diagnostic.PFire (presentation) was only ever checked at PSensor == 1, where
Accuracy * PSensor and Accuracy / PSensor agree.
- CommitProbability's four-factor product was only ever checked with
pDeviation == 1 (no fixture moved a target between fire and commit), where
* and / by 1 agree.
- ResolveAimPoint's zero-cells fallback (an aimed item that no longer occupies
any cell) was never reached; every fixture's marker survives to be hit.
New tests: TravelDirectionMatchesThePredictedInterceptAndFallsBackAtPointBlank,
DiagnosticPFireIsAccuracyTimesPSensor, CommitProbabilityIsTheExactProductOfItsFourFactors,
AimPointFallsBackWhenTheAimedItemNoLongerOccupiesAnyCell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FireControl.Silhouette replaces HullKernel/WeightedPick/POnHull: the hull's
lateral shadow at a bearing, merged into intervals, integrated exactly with
CultMath.erf/erfinv instead of a discrete per-cell Gaussian sum. FireControl.Lane
does exact slab traversal to pick the impact cell from a lateral offset; only
its first element (the entry-nearest occupied cell) is used here, per the cut
map -- the rest is 12.3's armour-first absorption march.
Bearing timing: PendingShot.PBase is renamed PFire and now carries only the
shooter-side factor (Accuracy x PSensor) behind the gates; TravelDirection,
Spread and FireRange are frozen alongside it. The target's facing is read live
at Commit through FireControl.CommitProbability (steps 1-5 of the spec's
"Bearing timing"), not frozen at Fire, so a target that turns its armour into
an incoming shot takes the hit there. ShotOutcome gains Bearing/Lateral,
frozen at commit; Apply reads only those, never a live position or facing.
Entity.ApplyHit takes an already-schematic bearing instead of a world
direction, and the ToSchematic call 12.1 put inside it is gone.
Superseded tests: PlacementAndProbabilityShareOneKernel, EveryHitLandsOnMetal
and AimingAtTheSternHitsTheStern are replaced by
FireControlCut12Tests.PlacementAndProbabilityShareOneSilhouette,
HitsLandOnTheFacingEdge and AimingAtAnItemCentresTheScatterOnItsLane.
FireAuthorityTests.AimedHitLandsOnSelectedItem and PenetrationMarchIsPlanar are
updated for ApplyHit's new bearing parameter and Lane's entry-nearest-cell
placement (a solid hull's Tool item is never itself the entry cell; reaching
it now needs the same penetration march the new tests exercise).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The headless build pinned one CultLib revision for every package. CultMath
0.2.4 needs a newer revision than CultCache's, and bumping the single pin
would have compiled six commits of newer CultCache under the headless tests
while Unity stays on caching-unity-v1.4.0. CultMathRoot/CultMathRevision now
pin CultMath at the 0.2.4 tag; CultLibRoot/CultLibRevision keep CultCache at
45c2f40. Verified: 244 pass with both roots right, and each wrong root is
refused by name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lock hash is the tagged merge 6d5e2096; batchmode resolve and compile clean.
Nothing calls erf yet; Cut 12.2 will.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entity.ToSchematic(float2) is the one schematic-frame transform;
Entity.ApplyHit's penetration march and FireControl.Splash's
directional half both call it instead of each carrying an inline
forward/right copy. ShotOutcome.Aimed and its plumbing (Q12-4 = A) are
deleted: the aimed local/flag in Commit, MakeOutcome's aimed
parameter, and the AimedCells half of ResolveAimPoint's tuple, which
now returns float2 directly.
No behaviour change. All 244 tests pass unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Records what Soul found (the wrong-signed erfinv edge that would have put hits
on the opposite side of a hull), corrects three stale verification lines, and
sets a cost measurement 12.2 owes rather than assumes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Q5 is dissolved rather than answered: the controller asks for the force and
the torque it wants together and the solver minimises both, so the lurch is
spent only when the translation was wanted anyway. The planner rolls
candidates forward and scores where the ship ends up, which is what makes
strafing beat the big circle. The legacy hulls' second behaviour is
VelocityLimit, not VelocityConversion, as the operator said.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Q1 (divergence accepted), Q2 (differential main thrust allowed, no thruster
exclusion) and Q3 (fixtures follow the data) are ruled. A content-restore cut
recovers the legacy thruster hulls, which the live catalog lost, and the
rotation design no longer excludes any actuator.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The blast radius, not penetration, picks the model: no radius means the lane
walk with cell-by-cell absorption; a radius means the shot reaches fuse depth
and detonates as an area. AP round snipes a component, explosive penetrator
guts a section.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Operator: distribute the damage over the affected area. A blast covers the
cells within its radius and each takes the share of the disc inside it;
damage over open grid is lost, so an airburst wastes most of itself and an
internal detonation delivers nearly all of it. A shot has a direction and
keeps the lane model; a blast does not. Q12-6 ruled out, Q12-7 dissolved,
Q12-8 ruled.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Records the operator rulings of 2026-09-22 (Q12-1..4, penetration retune as a
follow-up, WeaponModifiers as labels) and adds sub-cut 12.4, which replaces
Splash with one detonation owner.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Splash sign flip Cut 11 pinned was an undefended mutant, not a shipped
bug: the line is unchanged since 482c47bf. The operator's wrong-side bug
was legacy Unity code.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Inspect.PBase is HitProbability's own answer instead of a second product of
the factors. New behavioural tests pin a beam shot carrying its override
damage, a fitted targeting system forgiving a jink, a shot orphaned before
commit committing as a plain miss, and the HUD's arrival countdown. The
tautological damage assertion in BeamRollsPerInterval is deleted.
Stryker on FireControl.cs: 51 survivors to 43, all triaged in the map.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Most of Stryker's surviving mutants in FireControl.cs traced to a handful of
fixture constants that make a mutant compute exactly the same number as the
original -- the same defect class as the zero-velocity target and the damage
that happened to equal an item's durability, found here as a pattern.
- The kernel fixture's shooter sat at float3.zero, where `target - source` and
`target + source` are the same vector. HitProbabilityMatchesInspect gains a
configuration with the shooter far from the origin: the correct separation is
500, in range; the mutated sum is 1300, out of range.
- The kernel fixture's Resolution was 1000, which saturates pSensor to exactly 1
above the detection threshold, so multiplying and dividing by it agreed. Now 2.
- EvasionCountsUntilCommitAndNotAfter fired at zone time 0, so `now - FireTime`
equalled `now + FireTime`. Cut 5 gave this test a moving target to defend its
own declared mutation and it still survived; the clock now advances first.
- Every fixture used zero spread and a cell size of 1, leaving the whole spread
term untested. PSpreadIsTheSilhouettesShareOfTheSpreadCone pins what pSpread
means -- a silhouette filling the cone scores 1, half the cone scores .5, and
a bigger one saturates -- rather than how it is computed.
Triaged as equivalent, not chased: the float-threshold boundary flips, and the
three MixSeed mutants that turn a right shift left -- measured, the die stays
fair under all three (worst deviation from p .017-.030 against .022 correct), so
killing them would mean pinning the mixer's bit arithmetic, not its behaviour.
Recorded, not fixed: FireControl.Apply's derivation of hitDirection from the two
positions has no geometry test through the FireControl path, and
StatResolverTests.AModifierThatTargetsItsOwnMagnitudeStatIsRefusedAtEquip fails
intermittently in the full suite -- likely cross-class pollution through
CultCache's process-wide document registry.
241 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nine tests, each stating as behaviour a rule a real Stryker mutant survived:
- A shield that absorbs must eventually run dry, on both the discrete path and
Splash's (whose absorb branch had no coverage at all). Deleting Shield.TakeHit
survived because earlier tests asserted only that the hull was untouched.
- Splash damages the side the blast came from, across five facings and both
sides. Flipping the sign of `right` survived every suite, and it survives at
the default facing (0, 1) too: the flip only changes the damaged side when a
ship faces more along world x than z. So the test spins the target through
facings where it bites. The current code is correct; it can no longer regress.
- Different zones roll differently. Replacing `CombatSeed *` with `/` dropped the
zone from the seed and survived, because the die test checks one zone only.
- A shot whose target left the zone resolves as a miss.
That last one is a real defect (Soul C4), fixed here. Step rewrote the outcome
only for uncommitted shots, so a hit committed inside the horizon and then
orphaned by its target dying was republished as a hit -- a hit marker on a
corpse. The resolution is now a fresh miss; the committed outcome is left
untouched, since R4 makes it immutable.
The fixture puts the shooter off the origin on purpose: at float3.zero,
`target - source` equals `target + source`, which is how four position mutants
survived.
240 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Twenty-three harnesses, 5,816 lines, across the fire control, stats and power,
shield reserve and condition ratio campaigns. Every one targeted code under
Assets/Scripts/ServerShared, which Stryker's pinned scope now covers, and every
one carried the same liabilities: literal-text anchors coupled to code shape, a
restore that could leak a mutant into the tree, a tree-clean verdict that
restored before it measured, and a red result that could not tell a killed
mutant from a compile failure. The behavioural tests they measured all stay.
Kept and recorded, not yet answered: the addressables harness and the three
shield panel harnesses. They target Unity Editor probes that Unity compiles
outside Aetheria.Shared, where Stryker cannot reach.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pinned in dotnet-tools.json (dotnet-stryker 5.0.0) so any checkout reproduces
it with `dotnet tool restore`. Reports land in StrykerOutput/, now ignored.
Spike against FireControl.cs alone: 276 mutants in 7m18s, 208 killed, 67
survived, 4 uncovered, score 74.6%. It killed every mutation the hand-written
anchor harnesses defended, compiles all mutants into one assembly and switches
between them at runtime -- so it never edits a source file, has nothing to
re-anchor after a refactor, and cannot leak a mutant into the tree.
Among the survivors: Splash's handedness is untested (flipping the sign of
`right` passes every test -- the operator's "damage on the wrong side of the
ship"), deleting the discrete shield's TakeHit survives, and most of the rest
trace to degenerate fixture values -- shooter at the origin, fire time zero,
spread zero -- which make whole families of mutants arithmetically identical.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The diagnostics refactor (72c0109c) kept the debug HUD and the simulation on one
fire-control model by routing HitProbability THROUGH Inspect. That was the right
invariant implemented at the wrong level: every AI and turret evaluation, per
weapon per tick, built the full diagnostic struct and ran IsRevealed's sort, the
aim-point cell walk and HullKernel's allocations and per-cell exp -- and only
then checked whether the target was even in range. A Soul pass measured 9.87 us
and 448 B per call on paths that used to bail in nanoseconds. Operator: it made
the hot path look like a diagnostic one.
One model is now shared at the level of its factors. HitProbability runs its
cheap gates first and bails, exactly as it did before the refactor, and computes
PSensor, PSpread and POnHull only for a shot that is actually possible. Inspect
calls the same four factor functions and fills the struct unconditionally,
because a HUD wants to see why a shot is impossible. The HUD panel and its one
test consumer are unchanged.
Two tests make the design enforced rather than conventional:
- HitProbabilityMatchesInspect: exact equality across 21 configurations covering
every gate the hot path bails on plus Precision and info sweeps. Dies when a
factor changes in only one caller.
- GatedOutHitProbabilityAllocatesNothing: 1000 out-of-range calls allocate zero
bytes. Dies when HitProbability is routed back through Inspect.
Both mutations verified RED and distinguished from a compile failure, with
restores checked against a hash taken before mutating.
231 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The combat roll was not a roll. FireControl seeded CultMath's xorshift32 per
shot and drew once; NextFloat returns the top 24 bits of the first round, and
one round cannot carry ShotId's low-bit variation into the high bits. Every
zone's hit roll spanned about an eighth with its threshold fixed at zone
creation, so a shot was a step function of p -- below the band it never hit,
above it always did. An unaided shooter hit 0 of 18,926 shots, which is the
operator's "I can target and fire with no hits registering," and being stable
per zone is why it came and went.
Measured across six zone seeds: hit fraction at p=.3 went from 0.0% in five of
six zones to 29-31%, at p=.6 to 58-61%, spanning the full range. The seed stays
a pure function of (zone, shot id); only its diffusion changes.
Three cuts shipped over this because nothing in the campaign measured a
distribution -- every fixture uses accuracy 1 or 0, and a constant satisfies a
determinism test perfectly. The missing test, not the seeding line, was the
defect.
Also: pOnHull collapsed to zero above Precision ~4 because no shipped hull's
centre of mass is integral, so a better targeting system made a weapon unable to
hit anything; sigma is now floored at half a cell. And the LockWeapon crash Cut
8 did not actually fix -- that code only runs on active entities -- is closed at
its real route, dock/target dies/undock.
229 tests. All nine harnesses clean, run sequentially in an isolated worktree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9.1 changed the line those two harnesses pin, stranding five anchors including
cut6's own no-op control -- so cut6 was defending nothing until now. Same thing
the diagnostics refactor did to four harnesses earlier, and the second time in a
day that a change to Commit has stranded anchors elsewhere. The rules are
unchanged; only the text moved.
That maintenance cost is now a known property of pinning mutations to literal
source text. It buys a stale anchor failing loudly within one run, which is how
both instances were caught immediately, and it belongs in Cut 10's list beside
Soul's harness audit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9.1, blocking. FireControl seeded CultMath's xorshift32 per shot and drew once.
NextFloat returns the top 24 bits of the FIRST round, and one round cannot carry
ShotId's low-bit variation into the high bits -- so every zone's hit roll spanned
about an eighth, with the threshold fixed at zone creation. A shot was a step
function of p: below the band it never hit, above it always did. An unaided
shooter hit 0 of 18,926 shots. That is the operator's "no hits registering," and
being stable per zone is why it came and went.
The seed derivation is unchanged -- still a pure function of (zone, shot id),
Cut 6b's rule -- and only its diffusion into the first output changes, via an
fmix32 finalizer. Measured across six zone seeds: hit fraction at p=.3 went from
0.0% in five of six zones to 29-31%, and at p=.6 to 58-61%, with the roll
spanning the full range.
The defect survived three cuts because nothing in the campaign measured a
distribution -- every fixture uses accuracy 1 or 0, and a constant satisfies a
determinism test perfectly. TheDieIsUniform now asserts the observed hit
fraction is within .08 of p across several zone seeds, which is the test whose
absence was the real defect.
9.2. Sigma is floored at half a cell. HullKernel approximates a continuous
Gaussian with a discrete sum over occupied cells, and that sum collapses once
sigma falls well under one cell against an aim point that is not exactly on a
cell centre -- true of every shipped hull. Past Precision ~4, pOnHull went to
zero and a better targeting system made the weapon unable to hit anything.
9.3. An active entity's Target is now always a live entity it has info on.
Cut 8's _active guard could never have fixed the operator's LockWeapon crash --
that code only runs on active entities, and cut8's own harness header says so.
The live route was dock, target dies, undock; Deactivate disposed the
subscription that nulls a stale Target and Activate reseeded EntityInfoGathered
without reconciling it. Still not a TryGetValue in LockWeapon.
229 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A Soul pass measured the thing three cuts assumed. FireControl's per-shot seed
feeds CultMath's xorshift32, whose NextFloat returns the top 24 bits of the
FIRST round -- and one round cannot carry ShotId's low-bit variation into the
high bits. Every zone's hit roll spans 0.1249. A shot is a step function whose
threshold is fixed when the zone is created: below the band it never hits, above
it always does. An unaided shooter hit 0 of 18,926 shots.
That is the operator's "no hits registering," and being stable per zone is why
it came and went.
It is this map's own prescription, written by Self in Cut 6b, and the reason it
survived is that no test in the campaign measures a distribution -- every
fixture uses accuracy 1 or 0, and a constant satisfies a determinism test
perfectly.
Also takes pOnHull collapsing to zero above Precision ~4 (no shipped hull's
centre of mass is integral, so a tight sigma underflows every cell weight), and
the LockWeapon crash Cut 8 did not actually fix -- that code only runs on an
active entity, so the _active guard was never what would prevent it. Docking
still reaches it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fixes the crash from the operator's play smoke. Cut 5.6 moved death removal into
the simulation and did half the transition -- Zone.Entities.Remove with no
Deactivate, where TryDock has always paired the two -- so a dead ship kept live
subscriptions and stale references while every survivor dropped it, and
Zone.Update's snapshot iteration handed it one more Update after the world had
forgotten it. Entity.Update never consulted _active; the flag existed and
nothing asked it. LockWeapon's raw indexer into EntityInfoGathered was simply
where it surfaced.
Death now removes and deactivates as one transition, and a deactivated entity
does not update. Not a TryGetValue in LockWeapon: that would silence one call
site and leave every other behaviour running on a corpse.
Also re-anchors seven mutations stranded by the diagnostics refactor, two of
which were no-op controls -- four harnesses were defending nothing until now.
Still open and deliberately not patched: the heat spike, the motionless ships
and the shots that registered no hits. Unreproducible since the diagnostics
changed timing, so the map records them with the unclamped dt as the place to
probe.
226 tests; all eight mutation harnesses clean with tree-clean PASS, controls
included, run in an isolated worktree. Unity batchmode NOT run for this cut --
the operator's editor holds the project lock.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The operator's play smoke crashed in LockWeapon reaching EntityInfoGathered
through a raw indexer as a ship vanished. Cut 5.6 moved death removal into the
simulation and did half the transition: Zone.Entities.Remove with no Deactivate,
where TryDock has always paired the two. The dead ship kept live subscriptions
and stale references while every survivor dropped it, and because Zone.Update
iterates Entities.ToArray() it received one more Update after the world had
forgotten it. Entity.Update never consulted _active; the flag existed and
nothing asked it.
Death now removes and deactivates in one transition, and a deactivated entity
does not update. Deliberately NOT a TryGetValue in LockWeapon: that silences one
call site and leaves every other behaviour running on a corpse.
Also re-anchors the seven mutations stranded by the diagnostics refactor
(72c0109c), which routed HitProbability through Inspect and rewrote the lines
four harnesses pin -- two of them no-op controls, so those harnesses were
defending nothing. The rules are unchanged; only the text moved. Anchors stay
strict: loudly stale is what surfaced this within one run.
The heat spike, motionless ships and shots that registered no hits remain
UNEXPLAINED and open. See the map's 8.3 -- unreproducible since the diagnostics
changed timing, so recorded with the unclamped dt as the place to probe rather
than patched speculatively.
226 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The operator's play smoke crashed in LockWeapon reaching EntityInfoGathered as a
ship vanished. Cause: Cut 5.6 added death removal from the zone but not the
matching Deactivate, so a dead entity kept living subscriptions and stale
references while every survivor dropped it -- and because Zone.Update iterates a
snapshot, it still got one more Update after the world had forgotten it. Nothing
in Entity.Update consults _active.
TryDock has always paired removal with Deactivate; the death path just did half
of it. The fix is that pairing plus an _active guard, not a TryGetValue in
LockWeapon, which would silence one call site and leave every other behaviour
running on a corpse.
Also records the symptoms this does NOT explain -- the heat spike, motionless
ships and shots landing nothing -- as open, with the unclamped dt and the
unguarded division by it as the place to probe rather than patch.
And re-anchors the seven mutations stranded by the diagnostics refactor, which
rewrote the lines four harnesses pin. Two of the seven are no-op controls, so
those harnesses currently defend nothing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Precision was a coin flip between landing exactly on the aimed item and landing
uniformly anywhere on the hull, so aiming at a stern thruster scattered its
misses across the bow -- no nearer the thing aimed at than never aiming at all.
One Gaussian kernel over the hull schematic now answers both halves: its
per-cell weights place the hit, and its on-hull mass prices the stray into
HitProbability, so a thin limb costs visible hit chance before the trigger and
centre mass is the safe play. One function, so the placement and the probability
cannot drift apart -- Cut 3's named risk was exactly two functions answering
where a shot goes.
There is still only one roll. A shot that passes it always lands on metal,
because the chance of the dart straying off the hull was charged at the roll.
Operator-facing consequences, measured rather than assumed:
- pOnHull and the existing pSpread term do NOT double-charge. pSpread prices the
weapon's own dispersion cone against the silhouette and is 1 for any zero-
spread weapon; pOnHull prices the targeting system's aim scatter against real
schematic metal around the actual aim point. Both keep their jobs.
- AI willingness shifts only where it should. Unaided fire is unaffected --
UnaidedAccuracy already caps it below AgentMinHitProbability. Aided fire at
centre mass is barely touched. Aimed shots at thin subsystems can now fall
below the threshold, so NPCs get pickier about sniping small targets. That is
the tradeoff working, and AgentMinHitProbability is the operator's knob.
Also fixes two pre-existing defects the kernel exposed. FireAuthorityTests.
ShieldTakesHit fired damage 10 at a fixture whose equipped gear had durability
10, so the item absorbed the hit whole and the hull never moved -- the shield
branch could have been deleted outright and the test would have passed. And the
Cut 3 harness still named a test Cut 7 had renamed.
222 tests, all seven mutation harnesses clean with tree-clean PASS, catalog
reads 53 designs / 47 products, 0 dangling refs, Unity batchmode exit 0 with 0
error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ShieldTakesHit: damage raised from 10 to 50, past the Reactor/Shield gear's own Durability of 10. Under the dart-throw kernel, Build's default Precision groups unaimed shots tightly enough around this small hull's centre of mass that this fixture's deterministic single shot lands on one of the two equipped Tool items. At damage 10 that happened to exactly consume the item's own durability with zero remainder, so hull durability stayed unchanged with or without the shield-absorb branch -- the mutation this test is supposed to kill was surviving by coincidence, not because the rule held.
mutation_tests_fire_control_cut3.sh: its closing section still referenced FireAuthorityTests.OutOfArcConsumesNoDraw, a test Cut 7 renamed to CombatNeverDrawsFromTheSharedStream before this branch started (verified against the merge-base commit). Replaced the stale not-yet-reached note with a real mutation against the current test: Commit reads shot.Source.ItemManager.Random once, restoring the shared-stream touch Cut 6b removed.
Modelled on tests/mutation_tests_fire_control_cut5.sh: a no-op control, byte-exact I/O, sha256-verified restore after every mutation, and an explicit tree-clean verdict at exit. Runs the four mutations named in the spec's Verification block plus the fifth this cut's own test file declares for EveryHitLandsOnMetal. Verified clean: all five checks behaved as declared, tree-clean PASS.
Adds AimingAtTheSternHitsTheStern, ThinLimbCostsHitChance, PlacementAndProbabilityShareOneKernel and EveryHitLandsOnMetal per the spec's Verification block. Each test builds its own synthetic hull/catalog fixture. The aim marker in every fixture is authored with very high durability so it survives the whole sample of shots without dropping out of the reveal ranking partway through.
FireControl.Commit used to place a hit with a coin flip between exactly
the aimed item and a uniform-random hull cell -- no spatial term, so a
near-miss on a stern item was as likely to land in the bow as the stern.
One Gaussian kernel, in hull-schematic cell units, now decides both
halves of "where will this shot go": w(cell) = exp(-d^2/2*sigma^2) over
every occupied cell, sigma = 1/Precision. HitProbability folds the
kernel's on-hull mass share (pOnHull) into PBase at fire time (R10/Q6
unaffected); Commit draws the landing cell from the same weights. A shot
that passes the roll always lands on metal -- the off-hull chance is
priced into that one roll, never resolved as a second miss stage. The
uniform-random branch is deleted outright, not demoted.
Precision now feeds every shot's pOnHull, aimed or not, so its unaided
fallback (FireControl.Precision) reads GameplaySettings.UnaidedPrecision
instead of a bare 0 -- the same shape as the existing UnaidedAccuracy/
UnaidedTracking floors. pOnHull is saturated: the discrete sum over-
estimates the continuous kernel's integral once sigma drops below about
one cell, and a mass share cannot exceed the whole kernel.
Existing fixtures across FireAuthorityTests, FireControlCut4/5/6Tests and
TargetingSystemTests authored Precision as 0 (or relied on the old 0
fallback) purely as a don't-care value under the coin-flip rule, where it
only gated the aimed-item branch. Under the kernel it drives every shot's
sigma, so those fixtures are re-authored to keep pOnHull near its
ceiling and stay pinned on what they actually test.
mutation_tests_fire_control_cut3.sh's two anchors into HitProbability's
return line and the deleted coin-flip branch are updated to the
equivalent lines/mutations on the new code.
Cuts 1-3 declared mutation tables in this document and nobody ever wrote them,
which is why a Soul pass could write five mutations against rules the campaign
claims to pin and watch all five survive a 197-green suite. Those tables now
exist as committed harnesses -- six in the repo where there was one this
morning, each with a no-op control and a sha256-verified tree-clean verdict.
Three findings came out of writing them, all the same shape: a test whose
fixture could not tell the mutant from the original.
- ShotResolvesOnArrival stepped the zone from t=4 to t=6 in one tick, crossing
both the commit horizon and arrival, so it could not tell an outcome applied
on arrival from one applied at commit -- R4's central distinction, undefended.
- OutOfArcConsumesNoDraw could not be killed at all, because Cut 6b had made it
vacuous: the shared RNG stream it guarded no longer exists, so it passed
identically with the target in arc. Rewritten to pin the rule that is live.
- 7.1 pins the reveal re-check at the shot's freeze, the fifth mutation Soul
left standing.
And 7.4: the shipped catalog is now opened by a test that enumerates every
document type. Verified by reintroducing the exact defect that broke the 6b/6c
merge -- it fails with the same MessagePack nil error, so the one hole that let
a branch brick the catalog while passing every gate is closed.
218 tests, all six harnesses clean, Unity batchmode exit 0 with 0 error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 3's harness reported OutOfArcConsumesNoDraw as "not yet reached" -- no
mutation could turn it red. Chasing that rather than accepting it found the
reason, and it is not a fixture defect.
The test asserted that a shot gated to zero draws nothing from ItemManager.
Random, so the shared stream advanced exactly as if the shot had never queued.
Cut 6b moved every roll onto a per-shot local generator seeded from (zone, shot
id), so Commit stopped touching that stream on EVERY path. The test then passed
for a reason unrelated to arcs -- it passed identically with the target dead
ahead -- and the rule it named stopped existing along with the stream it was
about. The short-circuit it guarded is now a cheap guard, not an invariant, and
Commit's comment was still asserting otherwise.
Rewritten as CombatNeverDrawsFromTheSharedStream, a Theory over both an
out-of-arc and an in-arc target, pinning the rule that IS live after 6b: no
combat path draws from the shared stream at all. Verified by hand -- restoring
the shared-stream read in Commit turns the in-arc case red while the out-of-arc
case stays green, which is exactly why the old test could not have caught it.
218 tests pass. All six mutation harnesses run clean with tree-clean PASS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ShotResolvesOnArrival stepped the zone from t=4 straight to t=6, crossing both
the commit horizon (4.5s) and arrival (5s) inside one Zone.Update. A mutation
collapsing Step's arrival gate to `if (shot.Committed)` therefore survived it:
one wide tick crosses both thresholds either way, so the test could not tell
"applied on arrival" from "applied as soon as committed". R4's central
distinction -- the commit decides the outcome, arrival performs it -- was
declared in Cut 3's prose, pinned by nothing, and this is the third rule in this
campaign found undefended by a fixture too coarse to distinguish the mutant.
Adds a tick at t=4.55, past commit and short of arrival, where the durability
must still be untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tests/mutation_tests_fire_control_cut1.sh and cut2.sh, modelled on
cut5.sh: byte-exact I/O, sha256-verified restore per mutation, explicit
tree-clean verdict. Each runs the mutation table its own cut already
declares in docs/fire-control-cut.md.
Two adaptations forced by the Body having moved since those cuts were
written (both cut2.sh's own table entries, and its header comment):
Cut 1's CombatStateStepsHeadless mutation targets FireControl.MountDirection
instead of the now-deleted HardpointTransforms readback, since Cut 3 moved
the AI's aim-direction path there; Cut 2's EveryArmedLoadoutGetsATargetingSystem
mutation targets the surviving acquisition predicate instead of a
`required: true` flag Cut 6c.2 already turned into a deliberate
degrade-and-log path.
TargetingSystemTests.cs's RevealOrder fixture equipped its three items in
the same order FireControl.IsRevealed's ranking would put them in, so
dropping that ranking entirely was unobservable -- Entity.Equipment's raw
enumeration order already matched by coincidence. Reordered the fixture's
equip calls to the opposite order (size-ascending, hardpoint-last) so the
mutation is actually distinguishable; assertions are unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
FireControlCut7Tests.cs adds the fifth mutation Soul found survived 197
green tests -- FireControl.Fire freezing source.TargetItem.Value instead
of the reveal-re-checked ResolvedTargetItem (7.1) -- and the campaign's
first assertion that the shipped GameData/Aetheria.cc catalog is actually
readable by the shipped code and can generate an armed LonginusX loadout
(7.3/7.4). The live-catalog test builds its own scoped CultDocumentRegistry
rather than AetheriaStores.Open's default shared one, because the shared
registry auto-discovers every [CultDocument] type loaded anywhere in the
test process -- including AetheriaStoresTests.cs's own TestCatalogGlobal,
a fixture type the real catalog has never heard of and was never meant to
satisfy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Merging 6b and 6c bricked the catalog -- a new non-nullable float at a new
MessagePack key cannot read records written before it existed, so
AetheriaStores.Open threw and the game could not load its own data. Both
branches were green: one never opened the real catalog, the other never had the
field.
Cut 7's live-catalog test therefore has to enumerate every document type rather
than just look for a design, because a lazy read that never touches
WeaponItemData would have passed the broken merge too.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 6b added WeaponItemData.AirburstRange as a non-nullable float at Key(29).
Every WeaponItemData record in GameData/Aetheria.cc was serialized before that
key existed, so the reader is handed nil for the absent slot and a float cannot
take nil -- AetheriaStores.Open threw MessagePackSerializationException and the
game could not read its own catalog. AirburstRange becomes float?, null meaning
no authored burst radius, which is also the more honest reading of a value no
product sets yet.
Neither cut could have caught this alone. 6b verified with unit tests over
synthetic fixtures and never opened the shipped catalog; 6c opened the catalog
constantly but on a branch without the new field. It appears only in the merge,
and only when something actually reads GameData.
The general rule, which will recur: a new non-nullable value-type field on a
persisted CultCache document cannot read records written before it existed.
Nullable, or a migration that rewrites every affected record -- and a migration
cannot run if the read already throws, so tolerant deserialization comes first.
Verified on the merged tree: catalog reads (53 designs, 47 products), 0 dangling
refs, 215 tests pass, and loadout with all three targeting sellers excluded
generates every hull with 1 armed hull falling back to unaided fire.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 6a authored the premium targeting system strictly worse than the cheap one,
because Resolution is consumed as a cost while its name promises a benefit.
Operator ruling: fix the formula, not the handle. HitProbability now derives its
ceiling reciprocally, so higher Resolution is a sharper instrument and the
unaided fallback of 1 becomes the floor by construction rather than by accident.
No rename.
Both designs also shipped from a single seller, and galaxy generation draws a
random subset of factions -- so ship generation still threw in any galaxy that
missed that draw, and Cut 6a's ten green seeds only sampled the die. Targeting
now comes from the roster's own sensor factions, Finch and Lucent, alongside
NiteLife; and FillInterior degrades to unaided fire instead of throwing when no
seller is present, which is only safe because Cut 5.1 gave unaided fire a floor.
Also records that the existing loadout command runs with a null galaxy, where
IsAvailable passes everything -- so availability had never been exercised by any
verification in this campaign until the exclude-sellers mode this cut adds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AetherDb targeting-catalog-6c apply, landed:
- Targeting Computer: Resolution .3 -> 2 (sensor-limited until info reaches
.55 under the new reciprocal reading)
- Fire Control Array: Resolution .75 -> 4 (until .325) -- now unambiguously
ahead of the 1-cell design at every info level, fixing the strictly-worse
regression Cut 6a's review found
- 4 new products: Panopticon / Panopticon Prime (Finch Cybernetics, passive
sensors) and ClapBack / ClapBack Ultra (Lucent Media, active sensors/lasers)
-- each design now has three sellers instead of one
Census: 53 designs (unchanged), 47 products (was 43). AetherDb dangling: 0.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AetherDb targeting-catalog-6c (dry run unless "apply"): re-authors Targeting
Computer/Fire Control Array's Resolution onto the reciprocal-reading scale
(.3->2, .75->4, matching Cut 6c.1's operator ruling) and adds a Finch
Cybernetics and a Lucent Media product for each design, alongside the
existing NiteLife ones, so a galaxy missing any one seller still has two more
chances to sell a targeting system (Cut 6c.2).
AetherDb loadout gains "exclude-sellers:Short1,Short2,..." -- the only way to
actually exercise Galaxy.ContainsFaction from this command, since the default
null-Galaxy run treats every product as available and can never reproduce the
single point of failure 6c.2 fixed. Builds a minimal one-zone Galaxy via the
same SavedGame/Galaxy round trip as FireControlCut6cTests, backed by a scratch
run store under the OS temp directory so GameData/run.cc is never touched.
Adds tests/mutation_tests_fire_control_cut6c.sh, modelled on Cut 5's harness:
no-op control, byte-exact I/O, sha256-verified restore after every mutation,
explicit tree-clean verdict.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Commit drew from ItemManager.Random, a stream shared with wormhole exit
velocity, loadout generation, travel and every AI's own DPS sampling -- so
adding an NPC or opening a shop reordered every subsequent die, and Q5's
reproducible fight held only inside a hermetic fixture. The roll is now a pure
function of (zone identity, shot id): no stream, no ordering dependence, and
nothing outside Commit can seed or advance a combat draw.
Airburst was deleted with the Physics.OverlapSphere path in Cut 4 and never
rewired, so a flak round flew and did nothing. It now resolves in the simulation
-- Splash instead of Apply at arrival, never both -- rather than going back into
the Projectile MonoBehaviour, which is the authority Cut 3 spent itself
deleting. No catalog weapon authors a burst radius yet, so the flag is wired and
inert until an authoring pass sets one.
Verified independently of the agent report: 213 tests pass, all four mutations
die, the no-op control stays green, both negative greps are clean, and every
target file is byte-identical to sha256 hashes taken before the run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tests/mutation_tests_fire_control_cut6.sh, modelled on Cut 5's harness: a no-op
control, four mutations (6.1's shared-stream generator and dropped-ShotId seed;
6.2's double-application and unconditional-splash), byte-exact I/O with
sha256-verified restore after every mutation, and the two negative greps the
Cut 6 spec names. All five mutations behave as declared and the tree-clean
verdict is PASS.
Fixes two of Commit's and Projectile's own explanatory comments that quoted the
literal strings the negative greps check for (ItemManager.Random, Airburst),
which the first harness run caught as false failures -- reworded, no behavior
change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.1 (Soul finding 6): Commit builds a local CultMath.Random from (Zone.CombatSeed,
shot.ShotId) instead of reading and writing the shared ItemManager.Random stream.
Zone exposes CombatSeed (the same expression that already seeds its own _random),
so a shot's outcome is a pure function of zone identity and shot id -- immune to
whatever else drew from the shared stream in between.
6.2 (Soul finding 5): airburst is wired into the simulation instead of back into
Projectile.cs. PendingShot gains BurstPosition/BurstRadius, frozen at Fire time
from PredictedIntercept and the weapon's authored WeaponItemData.AirburstRange
when WeaponModifiers carries Airburst. FireControl.Step resolves such a shot via
Splash instead of Apply, never both. Projectile.cs/ProjectileManager.cs drop the
dead AirburstDistance/AirburstRange fields and write-back.
Tests: FireControlCut6Tests.cs pins both rules (208 baseline + 5 new, all green).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6c.1 (operator ruling 2026-09-19): Resolution stays a benefit -- higher is
better -- so HitProbability derives its sensor ceiling as the reciprocal of
Resolution instead of reading Resolution as the ceiling directly. The
authored 1-cell/2-cell targeting systems' Resolution values move to the new
scale in the next (data) commit.
6c.2: LoadoutGenerator.FillInterior no longer throws InvalidLoadoutException
when no targeting-system product is available to a galaxy -- it degrades to
no targeting system and logs the gap, safe only because Cut 5.1 already gave
unaided fire a floor (GameplaySettings.UnaidedTracking).
Adds FireControlCut6cTests.cs pinning both: HigherResolutionYieldsHigherOrEqualHitProbability
(dies if Resolution is read as the ceiling directly) and
NoSellerDegradesToUnaidedInsteadOfThrowing (dies if the throw is restored).
dotnet test tests/Aetheria.Shared.Tests: 210 passed, 0 failed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cut 2 landed LoadoutGenerator's required-targeting-system rule and no targeting
system to satisfy it, so AetherDb loadout threw InvalidLoadoutException and the
player's own hull could not be generated. The turret firing-arc migration was
likewise written and never run, leaving a turret engaging only within 60 degrees
of its spawn facing.
Two targeting designs with products, four products for previously unsold designs,
and the arc migration applied: 53 designs, 43 products.
Cut 6c amends two defects in this authoring -- Resolution reads as a cost so the
premium system is worse than the cheap one, and a single seller makes ship
generation depend on whether a galaxy happens to draw that faction.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Seven code defects Soul confirmed across the campaign, every one the same shape:
a rule implemented on one path and left off the path beside it. Tracking gets a
floor instead of a cliff; a shot the shield cannot absorb now breaks it on the
discrete path as well as in Splash; a beam obeys its firing arc; one bearing
test owns the point-blank case; a destroyed subsystem stops being the aim point;
the simulation removes its own corpses instead of relying on Unity's loot-drop
subscription; and PendingShot drops two fields nothing read.
Cut 5b then repaired the mutation harness, which had been leaking un-restored
mutations into the working tree -- that leak, not a matcher bug, was also why two
anchors appeared unmatchable. Restore is now hash-verified with an explicit
tree-clean verdict, and ConstantWeapon's PowerRequest gate is pinned separately
from Execute's, which is where the one surviving mutation had been hiding.
Verified independently of the agent reports: 208 tests pass, all eleven
mutations die, the no-op control stays green, and every target file is
byte-identical to sha256 hashes taken before the run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Precision is currently a coin flip between landing exactly on the aimed item and
landing uniformly anywhere on the hull, so aiming at a stern thruster scatters
its misses across the bow. Operator direction: misses should cluster near what
you aimed at, like throwing a dart at the grid -- and aiming should therefore be
a real tradeoff, where a thin limb risks missing the ship entirely and centre
mass is the safe play.
One Gaussian kernel over the hull schematic answers both halves: its on-hull
mass prices the stray into HitProbability, so the risk is legible on the HUD
before the trigger, and its per-cell weights place the hit. One function, so the
probability and the placement cannot drift apart -- Cut 3's named risk was
exactly two functions answering where a shot goes.
Records the rejected draft that clamped a stray dart to the nearest hull cell,
which would have quietly removed the tradeoff, and the two consequences worth
catching early: pOnHull may double-charge with the existing pSpread term, and
every AI gates on this number so they will hold fire more once it lands.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three runs of tests/mutation_tests_fire_control_cut5.sh left mutated files on
disk (ArcAllowsFire hardcoded true in Weapon.cs, InstantWeapon.cs's arc gate
deleted, FireControl.cs still carrying 5.1's own mutation) despite its
`trap restore_all EXIT INT TERM`: a trap cannot survive a SIGKILL of the
process group, and nothing before this cut ever checked that a `cp -p`
restore actually reproduced the original bytes.
Records each target file's sha256 before any mutation runs. Every restore --
per-mutation, immediately after each check_mutation, and at exit -- re-hashes
and aborts loudly on a mismatch instead of trusting `cp -p` silently. The
final summary and the exit-time trap both print an explicit tree-clean
PASS/FAIL verdict so a reader never has to infer it. The no-op control stays
first, unchanged.
Investigated the two anchors reported as "found 0" (Zone.cs's Death
subscription, Weapon.cs's InArc line) before touching anything, per the
mandate not to hand-edit them into a shape that happens to match: on this
worktree at HEAD, both anchors are single-line literals with no embedded
newline, so CRLF-vs-LF in the surrounding file cannot affect whether the
substring is found -- the CRLF theory does not hold up. Both anchors match
exactly once against the current, clean tree (verified directly with the
same perl matcher this script uses, and confirmed by a full harness run).
The mutated replacement text for each of those two anchors is byte-for-byte
identical to the exact leaked mutation the task description names as having
been left on disk by an earlier run (Weapon.cs's `return true;`, Zone.cs's
deleted Death subscription) -- i.e. the "anchor never matches" failures were
not an independent matching-mechanism bug, but a direct symptom of the same
leaked-mutation-state problem this cut's hash verification now closes. No
anchor literal was changed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ConstantWeaponObeysArc only asserted PowerRequest()==0 after Zone.Update had
already run Execute, which safes _firing off for its own (also-correct)
reason first. PowerRequest short-circuits on _firing regardless of what
ArcAllowsFire mutates to, so a mutation dropping ArcAllowsFire from
PowerRequest alone was an unpinned survivor even though the harness's own
Execute-line mutation was already caught. Added the same assertion before
Zone.Update, while _firing is still true and StanceAllowsFire is true, so
ArcAllowsFire is the only thing it can be testing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The multi-tick, many-Update-call version of this test (5.1) was flaky under
repeated runs: stepping a 5s flight in 50 separate .1s ticks left the exact
commit-time reading of `now` sensitive to float-accumulation/JIT-tiering
drift, occasionally producing zero hits across all 30 shots even on
unmutated code. Rewritten with velocity 0 so each shot's flight time (and so
its commit horizon) is zero -- one Fire call, one Update call, no
accumulated ticks to drift. Confirmed clean across repeated standalone runs
and still kills the 5.1 fallback-to-0f mutation.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cut 6b: the combat roll stops drawing from the shared ItemManager.Random stream
and becomes a pure function of (zone identity, shot id), so adding an NPC or
opening a shop can no longer reorder an existing fight's dice. Airburst resolves
in the simulation rather than going back into Projectile, which would be exactly
the presentation authority Cut 3 spent itself deleting.
Cut 6c: reviewing Cut 6a's landed catalog found the premium targeting system
strictly worse than the cheap one, because Resolution reads as a cost while its
name promises a benefit. Operator ruling: fix the formula, not the handle -- it
becomes reciprocal, higher stays better, no rename. Also: both designs ship from
one seller, and galaxy generation draws a random subset of factions, so ship
generation still throws in any galaxy that misses that draw.
Cut 7: the mutation harnesses for Cuts 1-3 that the map declared and nobody ever
wrote, plus the fifth mutation Soul left standing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul's pass over the whole campaign confirmed eleven findings. Seven were code
defects of one shape -- a rule implemented on one path and left off the path
beside it -- and this is all seven.
5.1 Tracking falls back to GameplaySettings.UnaidedTracking (new, 10) instead of
zero, and Commit drops the zero branch that turned a Tracking-less shooter
into a hard wall. Unaided fire was meant to be really, really bad; it was
334 shots and no hits against a target drifting at 5 u/s.
5.2 A shot the shield cannot absorb now breaks it. The decision is frozen into
ShotOutcome.ShieldBroken at commit and performed in Apply, so a shield that
recharges mid-flight cannot retroactively survive. Splash already did this;
the discrete roll never did, which made the whole shield-break mechanic
unreachable for every weapon in the catalog.
5.3 ConstantWeapon gates on ArcAllowsFire alongside StanceAllowsFire. A
side-mounted beam was firing forward.
5.4 The point-blank bearing case moves out of Weapon.ArcAllowsFire and into
InArc, which used to normalize the zero vector into NaN. The trigger said
fire and the probability said impossible.
5.5 A destroyed item drops out of IsRevealed's ranking, so Precision stops
concentrating hits onto a dead subsystem's footprint.
5.6 Zone subscribes Entity.Death and removes the corpse itself. That removal
lived only in Unity's loot-drop subscription, so headless nothing ever left
the zone, shots landed on corpses and no fight could end.
5.7 PendingShot drops PredictedIntercept and FlightTime, written and read
nowhere.
208 tests pass (197 plus eleven new).
Known defect, recorded rather than hidden: the committed mutation harness is
unreliable. Across two runs it failed to restore FireControl.cs and Weapon.cs,
leaving live mutations in the working tree -- including its own 5.1 mutation,
which is how a green suite briefly reported two failures. Its verdicts are
therefore evidence only where re-run from a verified-clean tree. Two anchors
(Zone.cs's ObserveAdd line, Weapon.cs's InArc return) fail to match at all, and
5.3's ConstantWeaponObeysArc survives its own mutation, so that rule is written
but not pinned. Cut 5b owns the repair; nothing here is trusted on the harness's
word, and the suite result above was taken by hand from a hash-verified tree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Runs targeting-catalog apply: two new designs (Targeting Computer, Fire
Control Array) and six new products (LockOn / LockOn Pro by NiteLife Energy;
DragOnBreath by Lucent Media for plight; Leonid by Aeronautics Unlimited for
pswarm; Steadfast by Rossum & Douglas for Core Power; True North by Lightsail
Express for Large Drive).
Verified: census 53 designs/43 products (was 51/37); loadout over ten seeds
no longer throws for LonginusX and each gains exactly one interior targeting
system; dangling 0; a fresh census reopen carries 0 schema issues;
firing-arc-migrate dry run reports nothing left to migrate; dotnet test
tests/Aetheria.Shared.Tests is 197/197.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cut 2 shipped TargetingSystemData and its FireControl/LoadoutGenerator
plumbing but never authored the catalog data it needs: no targeting-system
design exists, so tools/AetherDb loadout throws InvalidLoadoutException for
the player's own hull. This adds targeting-catalog, in the shape of the
existing *-migrate commands (dry run unless "apply"), which authors:
- Two GearData designs, "Targeting Computer" (1-cell) and "Fire Control
Array" (2-cell), each with EnergyDrawData/HeatData behaviours alongside
TargetingSystemData (the behaviour supplies no power machinery of its own),
argued from the capacitor (the catalog's only other bare Tool gadget).
- One product per design from NiteLife Energy, the one manufacturer that
already sells the capacitor, so IsAvailable finds one without falling back
to "any manufacturer".
- One product each for the four designs nothing currently sells (plight,
pswarm, Core Power, Large Drive), matched to the manufacturer the brief
and the corporate roster already imply.
Not yet run against the catalog; the next commit lands its output.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
tools/AetherDb firing-arc-migrate was written in Cut 2 and never run: the
Turret hull's two Ballistic hardpoints still carried a 0-degree arc, so a
turret only ever engaged within 60 degrees of its spawn facing. Applying it
sets both to 360, matching the operator ruling (fire-control-cut.md Q1) that a
turret's arc is authored data, not a new hardpoint type. A fresh dry run now
reports both hardpoints already authored.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Soul's campaign pass confirmed eleven findings. Seven of them are code defects
of one shape -- a rule implemented on one path and left off the path beside it
-- and belong to this cut. Catalog authoring is Cut 6, alongside airburst and
the shared combat RNG; the missing mutation harnesses are Cut 7.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The last five weapon kinds route through FireControl; HullCollider's hit
and splash reporting is deleted. A beam rolls once per resolve interval.
Mines keep their proximity trigger (Q7) and resolve damage through the
same path. Also fixes a real defect: OnFire?.Invoke(FireControl.Fire(...))
never fired at all when nothing subscribed, which is the headless case.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tests/Aetheria.Shared.Tests/FireControlCut4Tests.cs, builds its own minimal
fixtures rather than reusing FireAuthorityTests.cs's InstantWeapon-shaped
Engagement:
- BeamRollsPerInterval: over one second a beam produces exactly
1/BeamResolveInterval outcomes totalling Damage. Interval (.25s) and tick
(.125s) are both exact dyadic fractions so float accumulation cannot drift
the count, and the tick is strictly smaller than the interval so a "roll
every tick" mutant and a "roll once for the burst" mutant both fail.
- InstantWeaponFiresWithoutAnyOnFireSubscriber: pins the InstantWeapon half
of the null-conditional short-circuit fix, which no existing test could
catch (FireAuthorityTests.cs always calls FireControl.Fire directly, never
through Trigger()/Execute()).
- SplashHitsEveryEntityInRadius / SplashIsDirectional: FireControl.Splash
called directly against a zone of bare Ship entities (Entity.MapEntity,
run from the constructor, is enough to give a Ship Armor/GearOccupancy/
Hull without Activate() or a weapon catalog).
tests/mutation_tests_fire_control_cut4.py is the committed harness: a no-op
control plus one mutation per rule above, run against
tests/Aetheria.Shared.Tests via `dotnet test -p:CultLibRoot=...`. All seven
mutations behaved as declared in this pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deletes the last live Physics.* hit queries and every remaining SendHit/
SendSplash call in Assets/Scripts/Gameplay/Weapons: ConstantLaser's
RaycastAll, ConstantLightning's SphereCastAll, ConstantParticleWeapon's
trigger-collider plumbing, HitscanEffect's RaycastAll, and Mine's blast
OverlapSphere. Each becomes presentation only, the same convention Cut 3
gave Projectile/GuidedProjectile/Laser/Lightning: ConstantLaser/
ConstantLightning/HitscanEffect draw to TargetTransform (or out to Range with
no target); ConstantParticleWeapon keeps only its visual emission.
ConstantWeapon.Execute (previous commit) already rolled the damage before any
of these ever draw.
Mine keeps its proximity-arming OverlapSphere (Q7: contact detection, not hit
detection) but Explode() now calls FireControl.Splash instead of walking
colliders and calling SendSplash itself.
HullCollider.Hit/Splash/SendHit/SendSplash and their event-arg classes are
deleted outright -- their last callers are gone. HullCollider is now purely a
physics surface for ship-on-ship collision impulse; no Physics.* query
decides damage anywhere in Assets/Scripts/Gameplay/Weapons except Mine's own
named exception.
Every manager (ConstantLaserManager, ConstantLightningManager,
ConstantParticleWeaponManager, HitscanManager) is updated to match: no more
Damage/Penetration/Spread/DamageType plumbing into a presentation component
that no longer decides anything with it, and HitscanManager now passes
ShotId and TargetTransform the way LaserManager already does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
FireControl.Fire takes an optional damageOverride so a continuous weapon can
queue a shot for less than its full Damage. FireControl.Splash is the one new
rule this cut adds: an unconditional area effect (mines, airburst) applying
the shield-absorb-or-hull-takes-it branch per target, then DamageSchematic
over the directional half of each target's own hull that faces the blast --
the rule moved verbatim from the Splash subscription EntityInstance.cs
carried before Cut 3 deleted it (git show b7743789^).
ConstantWeapon.Execute rolls once per GameplaySettings.BeamResolveInterval
(new setting) for Damage*interval, through the same FireControl.Fire/Step
pair a discrete shot uses -- a beam's authored Velocity is 0, so the flight
commits and resolves in the same tick it fires (R4's short-flight
degradation). A beam is a sequence of rolls, not a continuous truth.
Fixes a real defect found while testing the above: both InstantWeapon.Execute
(Cut 3) and the new ConstantWeapon.Execute wrote `OnFire?.Invoke(FireControl.
Fire(...))` inline. C#'s null-conditional short-circuits the whole
expression, argument included, whenever the event has no subscriber -- true
of any headless run with no Unity EntityInstance wired up
(docs/headless-playground-cut.md) -- so a shot silently never fired. Both
now evaluate FireControl.Fire into a local before the null-conditional
invoke.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 3 landed 39 compile errors in GuidedProjectile: it uses ToCultMath
throughout and lost the using directive. Caught by batchmode on merge, not
by the pass that wrote it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The raycast hit path is gone from Projectile, GuidedProjectile, Laser and
Lightning; FireControl owns whether a shot lands. A shot freezes its gun's
stats at the trigger, commits its outcome a fixed time before impact, and
resolves on arrival against how far the target deviated. Rolls are seeded,
so a seeded engagement reproduces. Five weapon kinds still on the old path
belong to Cut 4.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deletes the Unity physics hit path (HullCollider.Hit/Splash, and the
raycast/shield/SendHit blocks in Projectile, GuidedProjectile, Laser and
Lightning) and replaces it with a single owner in the same cut: FireControl
now decides whether a shot hits, Zone owns the pending-shot lifecycle, and
Entity.DamageSchematic/ApplyHit (moved verbatim from EntityInstance) apply the
result. Presentation subscribes to Zone.ShotCommitted/ShotResolved and applies
nothing.
FireControl.HitProbability is the pure targeting solution (Accuracy * pSensor
* pSpread, gated by detection/range/lock/arc). FireControl.Fire freezes the
weapon's payload and the shooter's resolved targeting stats at the instant the
trigger is pulled (Q6), computes the predicted intercept through the one
shared PredictedIntercept function (CombatState now calls the same function
instead of duplicating first_order_intercept), and queues a PendingShot.
FireControl.Step commits each shot's outcome at ArrivalTime - CommitHorizon --
deviation from the predicted intercept, measured once, live, at that instant
-- and applies it at ArrivalTime. A shot whose flight is shorter than the
horizon commits and resolves in the same tick it fires. Rolls draw from the
shooter's own ItemManager.Random (Q5, seeded and reproducible); an out-of-arc
or otherwise gated shot draws nothing.
Player fire is arc-gated through the same predicate AI and turret fire use
(Weapon.ArcAllowsFire, checked in InstantWeapon.Trigger -- the one point every
shooter's trigger passes through), closing Q2.
Adds GameplaySettings.CommitHorizon, SchematicCellSize and
AgentMinHitProbability (first guesses; the headless suite is the tuning
harness, operator play smoke is the arbiter).
FORK reported, not worked around silently: HullCollider.SendHit/SendSplash
cannot be deleted yet without breaking ConstantLaser.cs, ConstantLightning.cs,
ConstantParticleWeapon.cs, HitscanEffect.cs and Mine.cs, which still call them
from their own live Physics queries -- that's Cut 4's migration. They fire
into the void today: nothing subscribes to Hit/Splash any more for the four
weapon kinds this cut owns.
tests/Aetheria.Shared.Tests/FireAuthorityTests.cs pins the roll's seeding, the
gate-consumes-no-draw rule, resolve-on-arrival, the commit horizon and its
evasion window, short-flight degradation, the frozen-payload snapshot, aimed
hits, the item-then-hull damage order, the planar penetration march and the
shield-absorbs branch. FireControlTests.cs and InputCapacitorTests.cs are
updated for the new AI gate and OnFire(int) signature.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A new interior Tool behaviour carrying accuracy, resolution, precision and
tracking; unaided fire falls back to one authored setting. The two info
thresholds are finally read: a target's equipment reveals as target data
accumulates, and one predicate and one writer serve both the player and
the AI when aiming at a revealed subsystem.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TargetingSystemTests: selection needs a real reveal (and drops it again
on decay, with no clearing loop), the reveal ranking (hardpoint before
interior, larger before smaller), the two thresholds are the ones
actually read, unaided fire falls back correctly for no/destroyed/
disabled systems, a starved system's Accuracy halves with the grant
(a real Reactor + PowerBus tick, since PowerSupply's setter is internal
to a different assembly here), and every armed generated loadout gets a
targeting system across ten seeds.
LoadoutTests' shared fixture now authors a targeting-system product too:
its "Twin" hull can randomly draw the weapon-carrying "Gun" onto its
Sensors hardpoint, and FillInterior's new required item would otherwise
throw for a fixture that predates this cut.
174 tests before this cut; 180 after (174 unchanged + 6 new), all green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A new "Cycle Target Item" action (hand-authored into Aetheria.inputactions;
no Unity editor here to regenerate AetheriaInput.cs, so ActionGameManager
looks it up by name through Input.asset.FindAction instead of a typed
accessor) cycles the local player's aim point among the current target's
revealed items via TrySelectTargetItem. SchematicDisplay's enemy view now
filters to what FireControl.IsRevealed actually resolves for the viewer,
and marks whichever row is the current ResolvedTargetItem through a new
SchematicListElement.SelectedIndicator (unwired in any prefab yet -- a
no-op until an editor pass assigns one).
Presentation only; decides nothing. Unverified here -- Unity batchmode
is unavailable in this environment.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
LoadoutGenerator.FillInterior equips a targeting-system product,
required: true, on any entity EquipHardpoints already gave a weapon --
Q4's "not an upgrade" ruling made structural. CombatState now selects
the target's most dangerous revealed weapon item each tick through the
same IsRevealed predicate and TrySelectTargetItem writer the player path
uses, instead of never touching TargetItem at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TargetingSystem is required interior Tool gear: Accuracy/Resolution/
Precision/Tracking are PerformanceStats reached through the usual
Evaluate path, so lot quality, wear, heat and PowerSupply all apply
(a starved system rolls worse, never switches off). FireControl.Accuracy/
Resolution/Precision fall back to GameplaySettings.UnaidedAccuracy (new,
authored low per Q4) whenever an entity has no targeting system or its
own is destroyed/disabled.
FireControl.IsRevealed derives R5's reveal rule from EntityInfoGathered:
non-hull equipment ranks hardpoint-mounted first, then by size, and
reveals between TargetArmorInfoThreshold and TargetGearInfoThreshold --
the first code to read either setting. Entity.TargetItem/
TrySelectTargetItem/ResolvedTargetItem give both the player and AI one
shared, revoke-on-decay aim-point slot with a single writer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
HardpointTransforms and its renderer readback are gone; mount direction
comes from the item's rotation and arcs are simulation data, default 120
with a per-hardpoint override. Combat no longer throws headless: a real
engagement steps two ticks and decides to fire through the same arc gate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A Hands scratch console upsert, same shape as ShieldMigrate/BrownoutMigrate
(dry run unless "apply" is passed) -- follows the Cut C precedent named in
docs/fire-control-cut.md's Cut 1. Scoped to the Turret hull's own Ballistic
hardpoints, the only ones the cut names (Q1: turrets author FiringArc: 360).
Not applied here: GameData/Aetheria.cc is left untouched. Dry run against
the live catalog reports two hardpoints to migrate:
hull hardpoint position was now
Turret Ballistic (1,2) 0 360
Turret Ballistic (5,2) 0 360
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One check per rule Cut 1 lands, each with a stated mutation that would
defeat it: mount direction follows the item's authored rotation and not
the hull's facing (ArcFollowsMountRotation), FiringArc is a full width so
the boundary sits at half of it (ArcBoundaryIsHalfWidth), the arc test is
planar per R7 (ArcIsPlanar), and a per-hardpoint override wins over the
default while an authored 0 still means "use the default" rather than a
zero-width arc (HardpointOverrideBeatsDefault).
CombatStateStepsHeadless is the cut's real deliverable: a Minion with a
target in range steps through two Zone.Update ticks without Combat.cs
throwing (the KeyNotFoundException this cut exists to unbreak), and the
fight actually decides to fire, not just avoids a crash.
Builds its own hull/catalog rather than reusing IffAndCombatTests'
private BuildWorld/NewShip (fixed to one hardpoint) -- and along the way,
surfaces three catalog-authoring gotchas any headless-combat fixture
needs: Mass defaults to 0 (NaNs UpdateTemperature via ThermalMass),
default temperature bounds [0,100] don't cover wherever the heat model
settles, and Entity._orderedEquipment is only ever populated by TryEquip
so an entity that never equips anything throws on its first Update.
docs/fire-control-cut.md, Cut 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Entity.HardpointTransforms was written only by Unity (EntityInstance.cs),
so the simulation's notion of where a gun points was a readback from a
renderer that never runs headless -- Combat.cs threw KeyNotFoundException
on the first combat tick outside Unity. Deletes the field, its writer, and
the readback branch in Behavior.Direction, leaving the item-rotation path
(the same convention reaction thrusters use, R6) as the only source of
mount direction.
Adds FireControl (ServerShared), a static class owning whether a weapon
bears on a point: MountDirection (planar, from ItemRotation), ArcFor (the
per-hardpoint HardpointData.FiringArc override, falling back to the new
GameplaySettings.FiringArc default of 120), and InArc (the planar dot-cone
test, R7 -- target height never enters it, and an arc of 360+ passes
unconditionally so a turret hardpoint needs no new type, just a number).
Combat.cs and TurretController.cs now gate AI fire through InArc instead
of the old hardcoded 0.99 dot-product test against a live Unity transform.
Settings.cs drops AgentFiringMinDot, which was declared and read by
nothing.
docs/fire-control-cut.md, Cut 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A power request is what the item wants at full supply, not what it is
currently managing, so the circularity dies at the root and radiators and
drives can carry their brownout curve again. The static refusal goes; the
modifier-chain check stays. Six skipped tests pass on their merits and the
shipped catalog is legal again.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A power request may not depend on power supply (Cut 6/F6), and continuous
consumers brown out through a power term on their own performance stats
(Cut 7). For Radiator (PumpedHeat) and AetherDrive (Torque) those were the
same stat, making six shipped catalog records illegal and leaving six
BrownoutTests cases skipped.
Resolved per the operator's ruling: a power request is evaluated
nominally -- what the item wants at full supply, not what it is currently
managing. EquippedItem.EvaluateNominalPower (mirroring ConditionRatio's
own NominalContext) pins only PowerSupplyFactor to 1; Heat/Durability
stay live, since they are not the term a request would be circular
through. Every PowerRequest/RefreshReserve/RefreshInputCapacitor
implementation now reads a StatValidation.PowerRequestFields stat through
it, so the same stat can carry a brownout curve and be a request field
without the request depending on its own answer.
StatValidation.ValidateNoPowerSupplyOnRequest (F6's static half of the
rule, forbidding the term outright) is deleted -- nominal evaluation
removes the circularity at the root instead. Only the dynamic
modifier-chain half (StatModifier.ValidateNoPowerSupplyChain) still
refuses anything, since EvaluateNominalPower forwards ScaleModifier/
ConstantModifier unchanged (real, not nominal).
Un-skips BrownoutTests' six Radiator/AetherDrive cases and adds two new
regression tests (a direct EvaluateNominalPower probe, and a two-tick
Radiator demand check) backed by a new mutation harness. Updates the four
PowerCurveTests/AetheriaStoresTests cases that pinned the now-superseded
static refusal to instead assert acceptance. GameData/Aetheria.cc is
unchanged and still validates.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
F1 power supply multiplies the resolved value (operator ruling), so zero
supply means zero and a flat stat still responds. F2 the bus agrees with
Active in both directions. F3 an empty tier reports no demand instead of
full supply, and sub-epsilon demand is rationed and subtracted. F4/F5 the
shield restores while inactive and CanTakeHit no longer mutates. F6/F7 the
validator names every stat a request reads and the tools validate before
writing. F8 re-anchors two dead mutation harnesses.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both scripts aborted with "anchor must occur exactly once, found 0" -- later
cuts moved the code their anchors pinned:
- cut3's fourth mutation anchored PowerBus.Step's old flat "write GrantRatio
to every consumer" loop, which Cut 5 replaced with AllocateTiers' own
tiered closing loop (draw.Item.PowerSupply = ratios[draw.Tier]). Re-anchored
to that loop; the rule (every draw gets written, not just the first) and
its test (PowerBusTests.EveryConsumingItemReadsTheSameGrantRatio, a
single-tier fixture) are unchanged.
- cut4's shield mutation anchored CanTakeHit's old single-expression body
(`return Item == null || _reserve.CanSpend(...)`), which the 2026-09-19
shield reserve ruling split into an Item-null check, a Broken check and
this CanSpend call as its own line, and F5 (this same pass) then made the
whole method a pure query. Re-anchored to the CanSpend line; the rule (a
partial draw is allowed, not gated on covering the whole reserve) and its
test are unchanged.
Both scripts now run clean end to end against the pinned CultLib worktree:
every mutation kills its named test, the no-op control does not.
Scope note: mutation_tests_stats_power_cut0/1/2 also abort on stale anchors
(spot-checked while re-anchoring cut3/cut4, then the check was cancelled
partway through cut1 once the pattern was clear) -- not touched here, as F8
named only cut3 and cut4.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CultRecordRefs.Upsert is the one function every catalog write is supposed to
go through, but tools/AetherDb/Program.cs's Dangling, ShieldMigrate and
BrownoutMigrate commands each repair an existing record in place and must
preserve its key -- Upsert's own cache.UpsertAsync(document) cannot take a
caller-supplied key, so all three wrote raw and unvalidated through
`db.Cache.Commit(batch => batch.Upsert(document.GetType(), document, key))`.
`brownout-migrate apply` is how the shipped catalog's PowerSupply terms were
actually authored, unvalidated, the whole time.
Extracted Upsert's validation body into CultRecordRefs.Validate(object), and
call it directly from all three tool commands immediately before their own
identity-preserving commit -- the same rules, invoked where Upsert itself
cannot reach. The comment above StatValidation naming the write-bypass holes
(landed with F6) now names all four instead of two: CultCache Studio's
generic editor (still open -- not this repository's to patch) plus the three
tool commands above (closed).
Pinned in tests/Aetheria.Shared.Tests/AetheriaStoresTests.cs:
ValidateRefusesAnEquippableItemWithAZeroSpanHeatRangeTheSameWayUpsertWould and
ValidateRefusesAPowerRequestCarryingAPowerSupplyTermTheSameWayUpsertWould pin
that the extracted method actually refuses what Upsert would (fail under a
reverted Validate that is a no-op); ValidateIsANoOpForADocumentTypeItDoesNotGovern
pins that a document type it has no opinion about doesn't crash it.
Verified tools/AetherDb/AetherDb.csproj still builds clean against the pinned
CultLib worktree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
StatValidation.PowerRequestFields registered only each consumer's own
top-level request field (Thruster.EnergyUsage, Shield.Capacity, and so on),
on the premise that PowerRequest reads exactly one PerformanceStat. That was
false for five of the eight consumers: Radiator.PowerRequest also reads
PumpedHeat and WasteHeat (its own early-out gate), AetherDrive.PowerRequest
also reads Torque, LambdaMultiplier, MaximumRpm and PassiveCoupling (its
whole spin-up arithmetic), Shield.PowerRequest (via RefreshReserve) also
reads RefillDuration/RestoreDuration, InstantWeapon.PowerRequest (via
RefreshInputCapacitor) also reads Cooldown/Count, and Sensor.PowerRequest
also reads PingCooldown. Any of those could carry a PowerSupply term and sail
straight past ValidateNoPowerSupplyOnRequest, corrupting the very request it
feeds -- exactly Soul's reproduced "OK Disperser" oscillation
(SOUL_RadiatorRequestDependsOnItsOwnGrantAndOscillates).
Audited directly against each PowerRequest method's own body (and the
RefreshReserve/RefreshInputCapacitor helpers it calls into), not guessed from
the behaviour's public surface.
This correctly refuses the real catalog now: `GameData/Aetheria.cc` carries
exactly six violations --
cold like my heart RadiatorData.PumpedHeat
Arctica RadiatorData.PumpedHeat
Skiron RadiatorData.PumpedHeat
OK Disperser RadiatorData.PumpedHeat
Iapyx RadiatorData.PumpedHeat
Traction AetherDriveData.Torque
-- re-authoring them (moving the PowerSupply term off the now-covered field,
or redesigning Radiator/AetherDrive's own PowerRequest to read a
nominal/undegraded value instead) is catalog and design work for Self/roles
authoring, not done here.
BrownoutTests.cs's own Radiator/AetherDrive brownout fixtures hit the same
wall -- they authored a PowerSupply term on PumpedHeat/Torque to test Cut 7's
"curve on the performance stat," which is now correctly refused since both
behaviours' PowerRequest reads that same stat. Neither behaviour has an
untouched field left to curve, so there is currently no valid catalog shape
that gives them a working brownout curve without also feeding it back into
their own request -- a real, scoped conflict between Cut 6 and Cut 7 for
these two consumers. The six affected tests are marked Skip with a header
comment explaining the conflict rather than silently reworked or deleted;
Thruster and ConstantWeapon are unaffected (their own PowerRequest never
reads the separate field their tests curve).
Pinned in tests/Aetheria.Shared.Tests/PowerCurveTests.cs:
UpsertRefusesARadiatorWhosePumpedHeatCarriesAPowerSupplyTerm and
UpsertRefusesAShieldWhoseRefillDurationCarriesAPowerSupplyTerm -- both fail
under the reverted (top-level-field-only) registry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
F4: a hit that breaks the shield usually also cooks it (TakeHit's own
AddHeat), which can knock the item thermally offline in the same tick. The
reserve's recharge and its Broken-clearing check lived inside Execute, which
Entity.Update only runs while Item.Active is true -- so a shield broken by a
hit that also shuts it down could never come back, no matter how long
RestoreDuration allows for. That logic now lives in Shield.Update
(IAlwaysUpdatedBehavior), which Entity.Update calls every tick unconditionally
(Cooldown.cs already relies on the same interface for the same reason).
F5: CanTakeHit broke the shield and drained the reserve as a side effect of
the query itself, on the premise that every caller queries it exactly once
per hit. That premise was false: RaycastAll/SphereCastAll/OverlapSphere hand
every caller under Assets/Scripts/Gameplay/Weapons both the shield collider
and the hull collider for one shot, so the same shield's CanTakeHit was
queried twice per hit. CanTakeHit is now a pure read (Broken + the reserve's
own CanSpend); the mutation moved to a new Shield.Break(), idempotent, called
by each weapon file exactly where it already decided to route a hit past the
shield instead of absorbing it.
Pinned in tests/Aetheria.Shared.Tests/ShieldReserveTests.cs:
RestorationProceedsWhileTheItemIsInactive (F4),
CanTakeHitNeverMutatesRegardlessOfHowManyTimesItIsCalled and
BreakIsIdempotentAcrossRepeatedCalls (F5). Updated every existing test that
relied on CanTakeHit's old mutating side effect to call Break() explicitly,
matching the new caller pattern.
Cannot compile-verify Assets/Scripts/Gameplay/Weapons/*.cs here (UnityEngine,
Unity batchmode unavailable in this environment) -- reviewed by hand for
brace/control-flow correctness; Self or a Unity batchmode pass should confirm.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AllocateTiers treated any tier whose summed demand was at or below 1e-4 as
"fully met" (ratio 1) without ever subtracting it from `remaining`. Two bugs:
a tier with no consumers equipped in it at all read as 1f, the exact "fully
supplied" signal a UI is meant to trust, even on a fully blacked-out ship
(measured: Critical 0 demand reads "fine" right next to a genuinely starved
High); and a tier with a real but tiny demand (down to 5e-5) was granted out
of nothing and never charged against the ship's actual generation, letting a
lower tier get overfed by the sliver the higher one was never billed for.
AllocateTiers now tracks which tiers actually have a consumer separately from
their summed demand. A tier with no consumer reports the new PowerBus.NoDemand
sentinel (NaN), never 1f. A tier with a consumer whose own request truly is
zero this tick is trivially satisfied (ratio 1, nothing to be short of). Any
other demand, however small, goes through the same grant/remaining arithmetic
as every other tier.
Pinned in tests/Aetheria.Shared.Tests/PowerTierTests.cs:
SubEpsilonDemandGetsNoGrantAtTotalBlackout,
HigherTierSubEpsilonDemandIsStillSubtractedFromRemaining, and
EmptyTierAtBlackoutReportsNoDemandNotFullySupplied -- all fail under the
reverted (<=1e-4 free-pass) mutation. Updated
ATierWithNoDemandPassesItsShareDown's own empty-tier assertion, which had
pinned the bug itself ("empty tiers read as fully met").
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PowerBus.Step billed every equipped IPowerConsumer's request unconditionally
and admitted a reactor on Item.Online alone, but Entity.Update only ever runs
a behaviour's Execute when the item is Active (Enabled && Online). A consumer
the player switched off never executes and never spends anything, yet was
still billed, starving live consumers and draining capacitors for nobody. A
reactor switched off stayed Online and kept generating even though
Reactor.Execute (which produces the heat receipt) never ran for it: free,
heat-free power.
Both directions now gate on the same predicate the rest of Entity uses:
item.Active.Value.
Pinned in tests/Aetheria.Shared.Tests/PowerBusTests.cs:
DisabledConsumerContributesNoDemand and DisabledReactorGeneratesNoPower --
both fail under the reverted (Online-only / unconditional) mutation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
32 of 51 designs declare roles, 183 stats point at one, 28 of 37 products
author matching role quality. Adds the validation that refuses a stat
naming a role its design lacks, and roles-migrate alongside the other
catalog tools. Product means are placeholders pending operator review.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PerformanceStat.Evaluate folded PowerSupply into the same Min/Max lerp factor
as Heat/Durability/Quality, so zero supply bottomed out at Min instead of 0
(measured: a radiator at zero power still pumped 19-25% of full; three of Cut
7's eight authored terms had Min == Max and were completely inert, since
lerp(x, x, anything) == x).
PowerSupply terms are now accumulated into their own multiplier and applied to
the fully-interpolated, fully-modified value instead of to the interpolation
factor. The term still lives in PerformanceStat.Terms -- it is still authored
per stat and still censused/validated the same way -- it is just no longer
blended into `factor`. Corrected the comments that stated the old premise
(Radiator.cs, Entity.cs, ItemData.cs's IStatContext.PowerSupplyFactor doc).
Pinned in tests/Aetheria.Shared.Tests/PowerCurveTests.cs:
APowerSupplyTermedStatWithNonzeroMinResolvesToExactlyZeroAtZeroSupply and
AMinEqualsMaxStatStillRespondsToItsPowerSupplyTerm -- both fail under the
reverted (lerp-based) mutation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
roles-migrate apply, run once: 32 of 51 designs now declare roles (183
stats pointed at the role that governs them; the other 19 designs' kinds
either carry no quality-bearing behaviour at all, or every candidate stat
is authored flat with no observable role-vs-workmanship difference). 28 of
37 products authored matching ProductRole quality; the rest sell an
unrolled design or a design no product sells (Core Power, Industrial
Thermostatic Heater, pswarm, plight -- all already flagged unsold in the
Cut 1 census).
docs/stats-power-cut7-roles.md is the legible review table the map's O7
calls for: every design's roles and which stat took each, grouped by item
kind, plus every product's authored mean/deviation and its basis (one real
brand identity -- Arctica/Alakrita on heat efficiency, from
item-provenance-target.md -- the rest generic placeholders flagged for
operator review).
Verified before/after: AetherDb dangling/station-fit/hardpoint-fit output
is byte-identical; `loadout 1` output is byte-identical (no generation
behaviour moved); census reports 0 designs with roles and no matching
product spread; 144 Aetheria.Shared.Tests pass (was 141); the six-mutation
harness in mutation_tests_stats_power_cut8.py all behave as declared.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
roles-migrate declares roles on designs, points each design's own
StatTerm.Role at the role that governs it, and authors matching ProductRole
quality on every product selling a now-role-bearing design -- all through
AetherDb's own CultCache-backed types, one commit, nothing written if any
record fails to derive (same all-or-nothing contract as shield-migrate and
brownout-migrate). Field->role maps are per item kind (Weapon/Ballistic,
Weapon/Energy, Weapon/Launcher, Radiator, Reactor, Sensors, Thruster,
AetherDrive, Hull/Ship); Tool is keyed by design name since it is not a
shared kind. A stat only gets a role when it carries an unrouted
StatSource.Quality term AND still varies (Min != Max) -- Cut 1's flat
placeholder stats carry no observable role-vs-workmanship difference, so
routing one would be authoring fiction.
Product role means are deterministic generic placeholders (stable FNV hash
of product+role, not System.String.GetHashCode, which is randomized per
process and would make a dry run and the later apply disagree) flagged
"needs operator review" in the printed table, except Arctica/Alakrita's
`fins`, which item-provenance-target.md already gives a real brand identity
(heat efficiency).
census gains a role-coverage check: every sold, role-bearing design's
product must author a matching ProductRole spread, reported as "(product,
role) pairs where ... authors no matching spread" -- the gap the ruling
names directly (a newly minted lot from a product with no spread rolls the
default mean .5, silently pulling every maker toward average).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
StatValidation.ValidateRoleUsage walks every PerformanceStat field on every
behaviour and refuses a StatSource.Quality term whose Role is not in the
design's own declared Roles -- wired into AetheriaStores.Open and
CultRecordRefs.Upsert alongside the existing heat/modifier/power-supply
checks. Lot.QualityForRole already falls back to workmanship for a role an
old lot never rolled (forward-compat for a role added after minting); this
is the other direction: a term naming a role the design never declared at
all is an authoring mistake, not a lot predating the role, and must fail
loudly.
RoleAuthoringTests.cs pins three rules with both a positive and negative
control: Upsert refuses an absent-role term but accepts a declared one, and
two products of one design with different ProductRole means mint lots that
resolve a role-termed stat to different, deterministic values.
tests/mutation_tests_stats_power_cut8.py (this repo's own numbering follows
cut7.py's brownout work) mutates ValidateRoleUsage in both directions
(never-refuse, always-refuse) and QualityForRole's fallback, plus
ItemManager.CreateLot's mean read -- all six behave as declared.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One ratio per item, resolved performance against the same item in perfect
conditions (quality and lot stay part of perfect; only durability, heat and
power count as degraded). Thruster and drive particles multiply intent by
it, and presentation stops computing durability itself.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ConditionRatioTests.cs (8 Facts): a perfect item reads 1; the ratio falls
with durability, temperature-off-plateau, and a partial power grant, each
in isolation; it is 0 when the item produces nothing (both the ordinary
durability=0 case and the degenerate Max=0 nominal, which relies on
CultMath's NaN-favoring min/max rather than a manual guard); thrust force
for a given input is bit-identical to BrownoutTests' own pinned values,
proving Execute()'s physics is untouched by Condition being read every
tick. mutation_tests_condition_ratio_cut8.py: one mutation per rule,
mirroring cut7's harness; all 7 confirmed red (2 control/guard mutations
revised after the first pass showed them un-killable -- see the harness's
own comments).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ShipInstance's thruster and aether-drive emission were the operator's
actual complaint (2026-09-19): thrust intent times a raw durability ratio
that ignored heat and power and duplicated simulation arithmetic in
presentation. Both now multiply intent by Thruster.Condition /
AetherDrive.Condition -- no durability/heat/power math left in
presentation. Unity-side; not covered by the shared test suite. Small and
obvious for Self's batchmode run on merge to check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Operator ask (2026-09-19): thruster particles only reflected thrust intent,
so a cooked or starved thruster looked identical to a healthy one.
EquippedItem.ConditionRatio (Entity.cs) resolves a stat's actual value
against what the same item, same lot, same modifiers would produce with
Heat/Durability/PowerSupply pinned to their identity (NominalContext) --
quality and lot are not part of "perfect", so a cheap item still reads 1
at full health. The nominal side bypasses the resolver's cache (which is
keyed only by (owner, stat) and would otherwise collide with the real
cached value) with one direct Evaluate call, a handful of flops.
Thruster.Condition and AetherDrive.Condition expose that ratio against
each behaviour's own governing stat (Thrust, Torque) for presentation to
read next.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Removes the full-grant gates from the five continuous consumers, so a
partial grant means reduced thrust, torque, pumped heat and damage rather
than nothing, and a constant weapon keeps firing while starved. Authors a
linear PowerSupply term on the eight shipped stats that had none. Instant
items keep their all-or-nothing charge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pins, for each of the five continuous consumers: a partial grant produces a
reduced-not-zero-not-full effect, a full grant is unchanged from today, a
zero grant produces nothing, and (Thruster, ConstantWeapon) the degradation
follows the authored exponent rather than being linear by accident.
EnergyDraw is pinned separately as the one consumer with no performance
stat of its own -- its Execute return value is the whole effect. A regression
Fact pins that InputCapacitor's all-or-nothing rule for instant items
(Cut 4) is untouched.
Each rule has a matching mutation in tests/mutation_tests_stats_power_cut7.py
(modeled on mutation_tests_stats_power_cut6.py's own shape); all eight
mutations, including the no-op control, behave as declared.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 7's gate removal (previous commit) only has anything to curve if some
shipped stat actually declares a PowerSupply term. Checked: none did --
removing the gates would have made brownout invisible (still full
performance at any nonzero grant) instead of graceful.
Added tools/AetherDb's "brownout-migrate [apply]" command (ShieldMigrate's
own dry-run/apply/report shape) and ran it against GameData/Aetheria.cc,
authoring exponent-1 (linear, the gentle default) PowerSupply terms on:
design behaviour stat before after
Arctica RadiatorData pumpedHeat 3 -> 4
Iapyx RadiatorData pumpedHeat 1 -> 2
Large Drive ThrusterData thrust 2 -> 3
OK Disperser RadiatorData pumpedHeat 3 -> 4
Skiron RadiatorData pumpedHeat 1 -> 2
Traction AetherDriveData torque 3 -> 4
cold like my heart RadiatorData pumpedHeat 3 -> 4
deep space burnout ThrusterData thrust 3 -> 4
No shipped ConstantWeaponData record exists yet, so ConstantWeaponData.Damage
has nothing to author against; the tool still targets it for whenever one
ships. None of the four targeted fields is a power-request field
(StatValidation.PowerRequestFields), so this does not trip
ValidateNoPowerSupplyOnRequest.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs/stats-and-power-target.md: "Continuous consumers brown out through a
power supply curve on their performance stats." Cut 6 wired the curve
(EquippedItem.PowerSupplyFactor) but every continuous consumer still gated
its own effect on Item.PowerSupply >= 1f, so an 80% grant produced nothing
instead of 80% performance.
- Thruster/AetherDrive: gate narrowed from ">= 1f" to a true-zero floor
(> 1e-4f, PowerBus's own epsilon); Thrust/Torque are plain Evaluate()
reads, so a curved stat already degrades once the full-grant demand is
dropped.
- Radiator: the gate is deleted outright. PumpedHeat is already a curved
Evaluate() read; waste heat is unaffected, so a starved radiator now falls
behind instead of refusing to pump, and zero supply already zeroes
PumpedHeat via the curve with no special case needed.
- ConstantWeapon: the weapon no longer safes itself off (_firing = false)
on a partial grant -- only true zero still stops it. Damage already
degrades via base.Execute's own Evaluate() read.
- EnergyDraw: has no performance stat of its own -- its Execute return
value IS the effect (gates its BehaviorGroup). Narrowed the same way:
a partial grant still passes, only true zero closes the gate.
InputCapacitor and the three instant consumers (InstantWeapon, Sensor,
Shield) are untouched: all-or-nothing on a partial charge still holds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 6 of docs/stats-and-power-cut.md. A stat may declare a PowerSupply
term applying as an exponent; the bus invalidates that source so the
resolver recomputes when the grant moves. A stat that decides a power
request may not depend on power, refused at catalog load, at Upsert and
at equip, including through a modifier chain.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Seven tests: a PowerSupply-termed stat degrades by its own authored exponent when the
grant drops and reads unchanged at full grant (the second read is deliberately taken after
a real cached value exists, so the missing-invalidate mutation is actually reachable rather
than hidden behind a fixture that only ever reads once); a stat with no PowerSupply term
never asks the context for one, cache or no cache; a power-termed stat recomputes at most
once per tick, not once per read; a thruster-style EnergyUsage field carrying a PowerSupply
term directly is refused at AetheriaStores.Open and at Upsert, naming the item and the
field; and a modifier whose magnitude carries a PowerSupply term is refused at equip when
its target is a known power-request stat, naming both items.
123 tests total (116 baseline + 7). tests/mutation_tests_stats_power_cut6.py: 10 mutations
(1 no-op control, 9 rule-killing), all behave as declared.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs/stats-and-power-cut.md Cut 6, purely additive per its own map: the enum member, the
resolver's generation-cache and the bus's PowerSupply write already existed (Cuts 2/3/5)
as an identity everywhere. This lands the two things Cut 6 actually adds.
The curve: EquippedItem.PowerSupplyFactor now returns pow(PowerSupply, exponent) instead
of the Cut-3 identity stub, so a stat that declares a PowerSupply term degrades as this
tick's grant falls and reads unchanged at full supply (pow(1, n) == 1 regardless of n).
PowerBus.AllocateTiers now invalidates StatSource.PowerSupply once per draw per tick -- the
missing half of the wiring named in the cut map -- so the resolver actually recomputes when
the grant moves, using the same per-source generation scheme Heat/Durability already use
(unconditional, at most once per tick, never touching a stat that did not declare the
source).
The request-independence rule: a stat that decides a power request may not depend on power
supply, directly or through a modifier chain. Direct dependency is checked statically
(StatValidation.ValidateNoPowerSupplyOnRequest, ItemData.cs) against a fixed registry of
(BehaviorData type, field) pairs -- the one PerformanceStat each IPowerConsumer's own
PowerRequest evaluates as its request, a compile-time fact rather than something authored
per instance -- and runs at catalog load (AetheriaStores.Open), at Upsert, and again at
equip (EquippedItem's constructor) as defense against the two named bypasses that write a
catalog document without going through Upsert. The modifier-chain half is dynamic
(StatModifier.ValidateNoPowerSupplyChain): a modifier's own magnitude stat is walked
through the same magnitude->target edges ValidateNoCycle already builds for the self-cycle
check, and refused at Initialize (equip time) if it is power-tainted and targets a known
request stat, naming both items.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nine mutations (one no-op control, eight rule-breaking) against
PowerBus.AllocateTiers and EquippedItem's tier-seeding guard: tier walk
order, committing a fed tier's grant before the next tier is considered,
proportional-within-tier rationing, an empty tier reading as met rather than
starved, allocating against TotalGrant rather than raw TotalDemand, remaining
vs. the original total (the no-leak boundary), reading the item's live
stored tier every Step, and never re-seeding a kind default once set. All
nine behave as declared against PowerTierTests.cs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Six tests against the "rules worth pinning" list: critical fed in full while
a lower tier starves; two consumers in one tier rationed proportionally to
their own request, not split evenly; an empty tier passes its share down
instead of swallowing it; reactor throttling moves the line between what
starves and what doesn't; a starved tier cannot leak into a lower one; and a
tier change on an equipped item takes effect on the very next tick with no
unequip/re-equip round trip.
103 -> 109 tests. Each rule has a matching mutation in
tests/mutation_tests_stats_power_cut5.py.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PowerTiers.cs names five tiers (Critical/High/Medium/Low/Utility) and their
per-behaviour-kind defaults: Radiator is Critical (the closest thing this game
has to life support -- a starved radiator cascades into wear and shutdown
across the whole hull), Shield is High, Thruster/AetherDrive are Medium,
ConstantWeapon/InstantWeapon are Low, and Sensor/EnergyDraw are Utility.
IPowerConsumer.DefaultPowerTier lets each behaviour own its own default.
EquippableItem.PowerTier (Key 12) is the stored, player-choosable tier on the
unit rather than the design; EquippedItem's constructor seeds a kind default
the first time a unit is equipped, only when the field is still
PowerTiers.Unassigned, and never again -- a tier is a stored choice, not
something the bus re-derives.
PowerBus.AllocateTiers walks tiers lowest-number-first: a tier is granted in
full before the next tier sees anything left, an empty tier passes its whole
share down, and within a tier every consumer rations by the same
grant/demand fraction so unequal requests still divide proportionally. A new
TierGrantRatio[] exposes each tier's own ratio for a future UI (Cut 8) to
read starvation from directly, instead of reconstructing it from every item's
PowerSupply.
Deletes EquippedItem.SortPosition, IOrderedBehavior and Reactor.Order (its
only implementer): the bus already steps before any equipment's Execute, so
equip/behaviour order never decided who got fed even before this cut, and
nothing else needed the interface (subject to the map's O3 operator check).
docs/stats-and-power-cut.md Cut 5 / §1.3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Capacity, refill duration and restore duration are authored on ShieldData.
A hit the reserve cannot cover breaks the shield: the hit passes through in
full, the reserve empties, and it restores from zero on the restore
duration, so the punish window is the same every time.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Catalog migration for the shield reserve ruling (docs/stats-and-power-cut.md). Scans
every GearData- and ConsumableItemData-hosted ShieldData, derives Capacity from
EnergyUsage (a per-damage-point multiplier, not an energy quantity -- capacity =
10 raw damage points x max(1, EnergyUsage) x cell count) and flat starting durations
(2s refill, 12s restore), and prints a derivation table. Dry run unless passed
"apply"; writes nothing if commit fails partway (single Cache.Commit batch).
GameData/Aetheria.cc is unchanged by this commit: the shipped catalog holds zero
ShieldData records (the pre-Cut-4 probe's 348-instance PerformanceStat table never
lists ShieldData among its owners either -- consistent with GameData/Aetheria.cc
being the post-breach bare-minimum rebuild, not the full pre-breach consumer set).
shield-migrate reports "0 shield designs" against it today and will migrate real
designs as soon as any are authored.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ShieldReserveTests.cs covers what Cut 4's InputCapacitorTests.cs cannot: a hit within
the reserve is absorbed and spends charge; a hit beyond it breaks the shield; the
breaking hit is not partially absorbed; a broken shield absorbs nothing (gated on
Broken itself, not merely on the reserve reading empty); a break empties the reserve
regardless of pre-hit charge; a broken shield restores on RestoreDuration and not
RefillDuration; a partial bus grant stretches the refill proportionally.
mutation_tests_shield_reserve.py mutates each rule's own line and confirms the named
test goes red for exactly that reason, plus a no-op control. All 8 (7 rules + control)
verified against the pinned CultLib worktree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Operator ruling, 2026-09-19, supersedes Cut 4's derived reserve (EnergyUsage refilling
every second, flagged as a guess). ShieldData now authors the reserve size and two
recharge speeds directly; EnergyUsage stays the per-damage-point cost multiplier.
A hit the reserve cannot fully cover now breaks the shield instead of a silent,
charge-preserving refusal. Two follow-up rulings settle the fork the first left open:
the breaking hit passes through in full (no partial absorption -- TakeHit never runs
for it), and the reserve empties to zero on break so RestoreDuration means the same
thing every time regardless of how full the shield was.
InputCapacitorTests' shield fixture now authors the three new fields explicitly --
without them the reserve defaults to zero capacity and breaks on every hit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 4 of docs/stats-and-power-cut.md. Instant-activation items (weapons,
sensor ping, shield) get input-only buffers the bus fills; the direct
capacitor-spend path Cut 3 left as a named exception is gone. Size derives
from Energy and Cooldown, with ChargeEnergy as the authored rate override.
Shield's reserve sizing is Hands' judgement and wants an operator look.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deletes Entity.CanSpendCapacitorCharge/TrySpendCapacitorCharge (and the
now-dead _capacitors bookkeeping list that only fed them) -- Cut 3's
named, temporary exception for the four instant draws. InstantWeapon
(both fire sites), Sensor.Ping, and Shield.CanTakeHit/TakeHit now spend
from their own InputCapacitor, filled continuously by PowerBus like
every other IPowerConsumer instead of touching the entity's shared bus
capacitors directly.
InputCapacitor's Capacity/Rate derive from the owning item's Energy and
Cooldown, with an authored-override hook (ChargedWeapon repurposes its
previously-unused ChargeEnergy stat as that override, per the map's own
instruction, rather than adding a new field). A weapon/sensor fire
attempt only ever spends exactly Capacity, so it only ever succeeds at
full charge -- no fractional shot. Shield is the map's named exception:
its reserve is continuous, not an activation buffer, so it allows a
partial draw; its size (no Energy/Cooldown pair exists on Shield) is a
flagged judgment call, not a spec'd formula.
Adds InputCapacitorTests.cs (8 tests) and
mutation_tests_stats_power_cut4.py (7 mutations, all behave as
declared); removes the one PowerBusTests.cs test and Cut 3 mutation
case that exercised the now-deleted Entity methods.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 3 of docs/stats-and-power-cut.md. TryConsumeEnergy and the reactor's
own capacitor plumbing are gone; PowerBus owns the grant, steps once per
tick before equipment executes, and rations every consumer by the same
fraction when supply is short. Tiers are Cut 5; the four instant-draw
sites still spend capacitor charge directly until Cut 4.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deletes Entity.CanConsumeEnergy/TryConsumeEnergy and Reactor's own capacitor
draining/filling (the demand-mechanism halves of Reactor.cs:90-141); adds
PowerBus (one per Entity, stepped once per tick before equipment executes),
IPowerConsumer on the five continuous draw sites (EnergyDraw, Thruster,
AetherDrive, Radiator, ConstantWeapon), and EquippedItem.PowerSupply as the
bus's grant ratio. Reactor keeps only its heat/throttle arithmetic, now driven
by PowerBus.NetDraw instead of an accumulator any behaviour could add into.
The four instant draws (InstantWeapon's burst/shot, Sensor's ping, Shield's hit
taken) are out of scope for the bus this cut per the map; they spend capacitor
charge directly and atomically via the new Entity.TrySpendCapacitorCharge/
CanSpendCapacitorCharge, replacing TryConsumeEnergy's old partial-drain-then-
refuse bug for that path too. Named, temporary exception closed by Cut 4.
docs/stats-and-power-cut.md Cut 3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The operator's play report on FieldShieldTest ("weak hits are very
janky, clicking outside the hex still makes it tremble and shatter,
pooling seems very rough") traced to two provable defects, not one
tuning constant:
- ShieldPanel.Hit had no bounds check at all. A point handed to it was
always enqueued and injected regardless of how far outside the
panel's own panelRadius it was.
- ShieldInterceptor.FindReusable decided reuse from a raw world-space
chord distance to the panel's stale spawn-time Position, using
PanelRadius*ReuseFraction (authored up to 2x) as the threshold. On a
non-uniformly scaled/curved envelope a chord across curvature is not
the panel's own in-plane extent, so a click clearly outside the
rendered tiling could still measure inside threshold and get routed
to Hit() -- which had nothing to reject it.
- Reusing a panel never refreshed its pooled DieAt, so a panel struck
again could still expire and get returned to the pool mid-fracture
("pooling seems very rough").
Fix: ShieldPanel.ContainsWorldPoint is now the one owner of "is this
point within my own radius" (panel-local in-plane distance vs.
panelRadius), used both as Hit's hard gate and as FindReusable's
candidacy test (with ReuseFraction, now capped at 1, narrowing but
never widening past the physical radius). Reuse now also extends
DieAt from the moment of the new hit.
Pinned in ShieldPanelCut3Verify.CheckHitOutsideRadiusDoesNotReachPanel:
a direct Hit() and a routed Absorb() outside a panel's own radius must
enqueue nothing and leave its state buffer untouched, and a genuinely
outside click opens its own panel. Deterministic (checks the pending
queue and no-op state, not temper drift) because breakBudgetFraction
paces fracture over many frames on purpose, so temper keeps drifting
from an earlier hit alone -- a drift-based check would have been
measuring that, not this rule (caught this against the check's own
first run).
All four ShieldPanelCut<N>Verify probes pass (batchmode, real GPU
device, no -nographics).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 2 of docs/stats-and-power-cut.md, with Soul's gates closed: the
resolver forgets an owner on unequip and consumable expiry (F1), and
Initialize owns the modifier detach/reattach so a refit no longer
silently loses a modifier (F2).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 5's own probe passed while breaking Cut 3's reuse rule: with R7 holding
the rim, the through-thickness echo (KEcho/_EchoReflect) is the only
remaining source of sub-critical tension for a weak hit, and its magnitude
(-0.65) was weaker than the still-live residual compression velocity left
by the direct KInject kick by the time the echo fired. The echo's own
header comment says a free-surface reflection inverts with unit
magnitude, matching the pre-R7 rim's Dirichlet condition -- -0.65 was a
lossy fraction that could reduce but never flip the sign of u, so a weak
hit's u never crossed positive and KFracture's `u <= 0` guard skipped the
sub-critical erosion branch forever, no matter how many hits landed.
Raised echoReflect from -0.65 to -0.9 (ShieldPanel.cs) -- enough margin to
flip a weak hit's residual compression into net tension without needing a
delay/seeding change; echoDelaySteps and KInject's seeding were not the
defect. All four probes now pass, including Cut 3's reuse rule (temper
0.4180 -> 0.3997 on a reused panel).
Also lands the multi-hit-reuse rule Cut 5's own report flagged as unpinned:
CheckRepeatedWeakHitsBreakWhereOneDoesNot in ShieldPanelCut5Verify.cs.
Energy 15 (not 8, whose peak tension of ~0.14 sits under tensileStrength
0.2 and can never break at any repetition) breaks on hit 2 of a live,
reused panel while a cold single hit -- given equal idle time (720 steps,
matching the multi-hit run's total budget, to rule out "just needed more
time to ring out") -- never breaks. This is the Cut 3 reuse rule and the
temper mechanism producing the operator-facing payoff together.
Verified: ShieldPanelCut1Verify, Cut2Verify, Cut3Verify, Cut5Verify all
PASS via batchmode (graphics device required, not -nographics).
Operator watch: echoReflect at -0.9 is close to the physical "unit
inversion" ceiling implied by the header comment; if later tuning raises
hit energies or lowers damping, watch for the echo overshooting into an
immediate break on a single moderate hit (observed at -1.4 and even -1.0
in earlier tuning passes) rather than the intended multi-hit erosion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
R7 (operator ruling): the panel's emitters hold its edge, so the rim is no
longer a free surface. Deletes the openW ghost term end to end -- its only
consumer in ShieldSim.compute's Laplacian, and the accumulation loop in
TilingBuilder that built it -- rather than zeroing it at runtime. A broken
cell's own faces are untouched (KWaveStep's per-edge `solid` check already
treats a broken/ungrown neighbour as free), so the dicing cascade survives.
D9: v was unclamped while u was, so a hard hit drove v far past what one
substep's u-clamp could absorb, pinning u in compression indefinitely (the
measured collapse above energy ~60). Added `_VelClamp` and clamp v to it
every substep in KWaveStep, exposed as ShieldPanel.velocityClamp (100).
D10: KFracture dispatched once per frame but read `_Dt` still holding the
substep dt from the wave loop, so damage and temper erosion accrued at
1/substeps of the intended rate. Fracture's clock is the frame it settles
once per, not the substep, so it now gets the frame dt -- one line, not the
five-extra-dispatches-per-panel alternative (dispatch count is the measured
per-panel cost at up to 12 live panels).
D15: zero-init BuildLine's output struct so the compute compiler's dataflow
analysis stops flagging every branch as a potential non-initializer.
Tuning (ShieldPanel.cs Material header): tensileStrength 0.9->0.2,
temperErosion 2.2->12, damageGain 6->100, breakAt 0.75->0.08,
storedEnergyGain 0.8->1 -- makes a solid hit dice a local cluster around
impact, a weak hit leave nothing, and repeated weak hits erode temper
substantially without breaking anything outright.
Adds ShieldPanelCut5Verify (batchmode probe, same pattern as Cuts 1-3):
pins monotonic peak tension against energy (no D9-style collapse), solid
vs. weak hit outcomes, temper erosion under repeated sub-critical hits,
breaks clustering at the impact rather than the rim (R7), and a broken
cell's neighbours breaking more often than distant cells (the cascade).
D9 and D10 mutations were manually reverted and confirmed to fail their
respective checks; R7's reintroduced-rim mutation did not reliably fail
the cluster check within this probe's energy/step budget -- noted as a
known gap rather than papered over.
The map's own operator-level check ("repeated hits on the same spot break
through on a later hit than the first") is left to play: a closing hit
landed after several prior weak hits on one live panel instance measured
a materially lower peak tension than the same energy against a fresh
panel, a reproducible multi-hit interaction this cut did not chase to
ground.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
codex/cultcache-cutover (modern CultCache from CultLib, lifecycle stores,
CultMath) and codex/item-provenance (lot provenance ledger, neutral
wanderers and IFF stance, capability presentation seam, settings and
content maps) stacked on it. Operator play smoke passed 2026-09-17: new
game, combat and grudges, stance toggle, loot, save and continue with lot
brand and quality intact, death clears the run across relaunch,
FieldShieldTest behaviour (materials broken by the dead Substance plugin,
not by this work).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deletes only the two candidate groups that survived independent re-verification
and had zero collision with the live codex/aetheria-state-rebuild branch:
- GameData/Narrative/HeroOrZero.json, GameData/Narrative/Locations/TestA.json,
GameData/Narrative/Quests/TestQuest.json: StoryProcessor.cs only ever calls
DirectoryInfo.EnumerateFiles("*.ink"/"*ink") against these directories; no
.cs, .csproj Content/None glob, or other file in the tree references these
basenames. Confirmed via rg across the whole tree and voidbot search_sources
(no hits either way).
- Vault/Aetheria/scratch.md: zero-byte Obsidian vault scratch file, no
references anywhere in the tree, no voidbot search_sources hits.
Kill-attempts that came up empty (recorded for the record, not evidence of
absence beyond what's stated):
- rg for basenames HeroOrZero.json/TestA.json/TestQuest.json/scratch.md
across the whole tree excluding Library/Temp/obj/bin/.git: no hits.
- Economy.Server.csproj and Economy.Shared.csproj inspected for wildcard
Content/None includes that might implicitly sweep in the JSON files: none
found (Economy.Shared only globs ServerShared/**/*.cs as Compile).
- mcp__voidbot__search_sources queried for both candidate groups: no
consumer hits, only unrelated fuzzy matches.
NOT deleted here despite being in REC-095's candidate list (see coordinator
report for full detail): StrategyGameManager.csbak, CultClient.cs,
CultCache.cs backing-store classes, Economy.Server/appsettings.json, and
Economy.Shared/bin+obj all collide with commits already on
origin/codex/aetheria-state-rebuild (which has independently already deleted
or heavily rewritten those exact paths) and are therefore BLOCKED per the
no-touch-live-branch-paths rule, not executed from this worktree.
REC-095.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 14:02:49 +02:00
1342 changed files with 151675 additions and 32135 deletions
Copyright (c) 2022–present Jason Sofonia & Justine De Caires
Copyright (c) 2014–2021 Twitter
Graphics licensed under CC-BY 4.0: <https://creativecommons.org/licenses/by/4.0/>
Change notice (CC BY 4.0 section 3(a)(1)(B)): the SVG graphics are converted to a COLRv0 colour
font (Twemoji.ttf) by tools/emoji/fetch_emoji_font.py using nanoemoji. No artwork is edited.
Attribution Requirements (upstream README)
As an open source project, attribution is critical from a legal, practical and motivational perspective in our opinion. The graphics are licensed under the CC-BY 4.0 which has a pretty good guide on [best practices for attribution](https://wiki.creativecommons.org/Best_practices_for_attribution).
However, we consider the guide a bit onerous and as a project, will accept a mention in a project README or an 'About' section or footer on a website. In mobile applications, a common place would be in the Settings/About section (for example, see the mobile Twitter application Settings->About->Legal section). We would consider a mention in the HTML/JS source sufficient also.