diff --git a/docs/aetheria-release-map.md b/docs/aetheria-release-map.md index 80ec3dd98..9ebc82d85 100644 --- a/docs/aetheria-release-map.md +++ b/docs/aetheria-release-map.md @@ -7014,8 +7014,9 @@ That file's Cut 4 (AI heading planner) and Cut 5 (combat facing) are carried as - **TA7. The Duel arena moves the player by itself.** With forward thrust the player's velocity swings between about +28 and -28 m/s along the nose every ~60 ticks with no `VelocityLimit` clamp (limit 100, drag 0.1), while intent zero leaves it at rest. Translation measured in the Duel is not thrust; tests that - measure translation use `RestoredHullsTests.BuildThrustedShip`. Cause not traced (the zone's field at - the spawn is the first suspect). + measure translation use `RestoredHullsTests.BuildThrustedShip`. Traced 2026-10-10 (TW1 below): a + harness artifact of stepping `player.Update` without `Zone.Update`, which leaves every orbit at the + origin and stacks the zone's gravity wells on the spawn. - **TA8. CultMath 0.3.0 re-rolls generated content.** Full suite at `80d2c7f3` with only `CultMathRevision` moved to `614fd445` (tag `cultmath-unity-v0.3.0`): 1057 pass, 5 fail. `TutorialGalaxy_unchanged` (faction homes differ), three `RestoredHullsTests` that pick zones by name @@ -7153,6 +7154,87 @@ Rationale changes: by this revision: the yaw weight is 1000 against a ridge of .1, and uniqueness of the answer depends on the tolerance as well as the ridge. +### Wiring refresh at `08d064c8` (2026-10-10) + +Pass: Imagination `imagination-aeth-allocator-wiring`, session `self-2026-10-10-ag`. Body: +`GameCult/Aetheria` origin/master `08d064c8` (thrust-allocator-core merged, CultMath pinned at 0.4.0, +`CultMathRoot` `cultlib-252e226c` on Yggdrasil), detached worktree `C:\aeth-wt\map-articulated`. Probes: +scratch partial `RunStartTests` facts `ImagProbeDuelOscillation` and `ImagProbeCatalogThrusters`, run on +Yggdrasil through the stopgap (logs `j2.log`, `j3.log` in the session scratchpad +`imagination-aeth-allocator-wiring`). The allocator cases patched the scratch clone's `Ship.Update` only: +after the mixer, a static flag overwrote every `Axis` with `ThrustAllocator.Allocate` over columns built +from live `Thrust` (equal to `NominalThrust` on the fully powered Duel ship). Each case is a fresh Duel +launch stepped 240 ticks at 1/60 s; heading in degrees from the launch heading. + +- **TW1. The Duel oscillation is a harness artifact** (follow-up `duel-velocity-oscillation`, TA7). + `Orbit.Position` starts at `float2.zero` (`Zone.cs:726`) and is set only by `Zone.Update` + (`Zone.cs:218-223`). Stepping `player.Update` alone leaves all 38 bodies of the Duel's arena zone (4 suns, + 4 gas giants, 30 planets, wells 546-9420 m wide) at the origin, which is the player's spawn + (`Duel.cs`: `stage.Player(fighter, float2(0, 0))`). Their wells stack there: `Zone.GetForce` is 0 at the + origin, -64 m/s^2 at (0,5), -982 at (0,20), -8082 at (0,80). Under thrust the ship climbs out and falls + back through the stack, which is the swing TA7 saw: + + | Case (Duel player, 240 ticks) | Along-nose speed, max / min (m/s) | Heading at tick 120 / 240 | + |---|---|---| + | A mixer, forward, `player.Update` | 31.5 / -30.6 (first below -1 at tick 36) | -12.09 / -21.94 | + | B mixer, forward, `arena.Update` | 101.7 / 1.9 | -12.09 / -21.94 | + | C mixer, forward, `player.Update`, `GravityStrength` 0 | 101.7 / 1.9 (B to the digit) | as B | + | D mixer, intent zero, `player.Update` | 0 / 0 | 0 | + | E allocator, forward, `player.Update` | 31.5 / -30.8 | .018 / .036 | + | F allocator, forward, `player.Update`, `GravityStrength` 0 | 101.9 / 0 | .018 / .036 | + | I mixer, forward .3, `player.Update` | 12.7 / -12.3 | -3.88 / -7.56 | + | J allocator, forward .3, `player.Update` | 12.7 / -12.3 | .001 / .002 | + + The first `Zone.Update` moves the bodies to their orbits: the spawn's height goes from -3316 to -69 and + the field at the player stays under 4.3 m/s^2 for the whole run (case B). The game ticks the zone before + any ship (`Zone.Update` sets orbits first), so play never sees the stack. Neither the mixer, damping, the + tick nor `Steering.Toward` is involved (no `Turn` in A to J; D and C rule out drift and drag). +- **TW2. Wiring is irrelevant to the oscillation and removes the drift.** E and J match A and I in speed + (the well decides it) and hold heading to hundredths of a degree where the mixer turns 6.4 deg/s (TA3, + reproduced at `08d064c8`: -12.09 deg in 120 ticks). Full forward: both drives at 1 and the Talaria (2,14) + at .039 falling to .027 as heat moves the drives, so the ship crabs to starboard at 1.6 m/s at top speed. + Forward .3: drives at .319 and .283, no Talaria. With `Turn` .5 (LaunchDuel's intent) the allocator + turns 107 deg/s (.5 of the 215.5 extreme, Talaria .68, both drives 1); the mixer 77. +- **TW3. Top speed overshoots the limit by one tick of thrust.** Case B settles at 101.4-101.7 m/s against + `VelocityLimit` 100 with `LimitClamp` about 1.7 m/s each tick: the limit clamps before the drives push in + the same tick. Mixer and allocator alike; nothing in this campaign's flight cuts owns the order. +- **TW4. No shipped hull reaches the allocator's large-hull regime.** The catalog holds two Ship hulls: + Djinni, 8 thruster hardpoints (Reversed 3, CounterClockwise 2, Clockwise 2, None 1), and Longinus, 4. + `GameData` holds only `Aetheria.cc`, `Narrative/` and `SoundbanksInfo.json`; no package hull is in the + tree. Finding `large-hull-degradation` starts at about 32 thrusters. +- **TW5. Every thruster's Thrust carries PowerSupply^1.** All seven catalog thruster designs (Large, + Medium, Small Drive, Talaria, Victoire, RevvITup 2.0, deep space burnout) carry `PowerSupply` with + exponent 1 on `Thrust`; none carries it on `EnergyUsage`. All thrusters share `PowerTiers.Medium` + (`Thruster.cs:91`). Under a brownout that grants one fraction to the whole tier every column scales + alike and the solved balance survives; it breaks only where grants differ within the tier. That is the + question follow-up `allocator-brownout-columns` keeps. +- **TW6. What moved since `80d2c7f3`.** `Ship.cs` gained only throttle-lock's two lines (`:236-237`): the + mixer is now `:238-261` inside the gate `:233-262`. `Thruster.cs`, `Behaviors.cs:96-99`, + `Settings.cs:214`, `SteeringTests.cs`, `BrownoutTests.cs`, `ConditionRatioTests.cs`, `SimClockTests.cs` + and `RunStartTests.cs` are unchanged. `Entity.cs`'s commented `IAnalogBehavior` field is `:2360`. + `EvasionTermTests.cs` moved +16: `TheAggregatesReadLiveThrust` `:716-734` (the `ForwardThrust` asserts + `:724-725`), `EveryAggregateAndTheCompensationReadLiveThrust` `:1013-1072`. `ThrottleLockTests.cs` + exists (274 lines, 7 facts and theories); every assertion is an exact mixer `Axis` (1 or 0) on a fixture + whose thrusters are found by mount rotation. `ThrustAllocatorTests.TheStartDoesNotChangeTheAnswer` + (`:253-265`) asserts start independence at 1e-5 on one point while finding `solver-converged-early` + stands; the CultMath stop fix (`cultlib-gaps:cut_spec:cut-bls-release-stop.r1`) owns what happens to it. + +Refresh decisions (specs `cut-thrust-allocator.r3`, `cut-envelope-from-columns.r2`): + +- **Translation may be measured in the Duel if the arena is stepped.** TA7's rule is replaced by TW1's + cause: a test that reads velocity steps `arena.Update`, or uses `BuildThrustedShip`; a test that reads + only heading may step `player.Update` (heading in A equals B to the digit). The unplaced-orbit defect + is the zone's, not flight's (follow-up `zone-orbits-unplaced-before-first-tick`). +- **`Column(thrust)` from the first cut.** The wiring cut adds `Column(float thrust)` and passes + `NominalThrust`; envelope-from-columns passes live thrust to the same method. r1 had the envelope cut + change the signature, which was churn on one owner. +- **Folded:** `allocator-residual-pins` (a micro-intent test in `ThrustAllocatorTests`). **Left, with + reasons:** `allocator-large-hull` (TW4: no shipped hull is near 32 thrusters; it gates on package hulls); + `allocator-brownout-columns` (TW5: a design question, not the wiring); `steering-feasible-direction` + (reads the envelope's clockwise and counter-clockwise terms, so it follows envelope-from-columns); + `allocator-ai-planner` (plans through the wired allocator, so it follows this cut). Answered by the + wiring when it holds: `asymmetric-drive-torque` (flight control balances the drives, TW2). + ## Spec refresh 2026-10-09: scarce consumables, the fog bank, multiply, tiered boosters, the shipped AetherDb Pass: Imagination `imagination-spec-refresh`, session `self-2026-10-09-morning`, follow-up @@ -7769,3 +7851,152 @@ maneuverable"; a gimbal per row, not per thruster) and `gimballed-thrusters`. Pr `vector-rows-presentation`) and `mind-commit-e1337a9c...` (`cut-vector-rows-record.r2` superseding r1: its ShipValidationTests anchor named the wrong class). context-pack read every anchor SAME: record r2 at `562de34c`, allocator r1 at `1fc98bdc`. +# Lock warning (cut lock-warning) and consumable-ai r2 + +Imagination imagination-aeth-lock-warning, session self-2026-10-10-ag, 2026-10-10. +Pinned to Aetheria origin/master 08d064c8dce6081bb48e1922a3090c855db2e7c6 (lock-warning) +and to branch eureka/aetheria-release-consumable-ai head f2ecc96a (consumable-ai r2, +not merged to master). For Self to fold into docs/aetheria-release-map.md. + +## Body facts (probes and source reads) + +LW1. The sim has no fact a ship owns about locks on it. `Entity.TargetedBy` +(Subject) and `Entity.TargetedByCount` (Entity.cs:174-175 at 08d064c8) count ships +whose *selected* target is this one (Entity.cs:255-259). Selecting a target emits +nothing, so that count is not something the targeted ship can sense. Its one +reader is the HUD's music switch (ActionGameManager.cs:1063). + +LW2. The HUD's lock bar is the player's own *outgoing* lock: one `LockIndicator` +per own `LockWeapon` (ActionGameManager.cs:1139-1144), driven by `targetLock.Lock` +(noise, frequency, spin; :1440-1452). No incoming-lock presenter exists. + +LW3. A lock is built only inside `LockWeapon.Execute` (LockWeapon.cs:81-110): the +lock resets when the target changes (86-90); it grows only on a hostile entity +target while the angle between the shooter's Aim and the target is under +LockAngle (100-105); outside that it decays (106). The lock speed scales with +`Entity.EntityInfoGathered[target]` raised to SensorImpact, the shooter's private +sensor state. `IsLocked` is `_lock > .99` (66). + +LW4. Behaviours execute only while their item is Active +(EquippedItem.Update, Entity.cs:1982-1998; consumable effects at 1626-1635). +A behaviour that stops executing leaves its fields as they were, so any +"painting" flag stored on the weapon goes stale when the launcher goes offline. +A warning must therefore be re-asserted each tick by the emitter, never read +from emitter state that might be stale. + +LW5. `Entity.Update` returns at once when the entity is inactive +(Entity.cs:1376-1379) and already walks `Zone.Entities` every tick for +hostility (1406-1414). `Deactivate` (772-787) disposes subscriptions and clears +the visibility collections. `Entity.VisibilitySources` (88, decayed in Update +1395-1400) is the existing precedent for state other entities push onto a ship. + +LW6. consumable-ai h1 (f2ecc96a) reads `e.Weapons.OfType().Any(w => +w.Lock > .5)` over `ship.VisibleEnemies` filtered to enemies targeting this ship +(Combat.cs:147-148), after the `target == null` early return (Combat.cs:33-34). +Finding reads-enemy-weapon-state: private emitter state, and the unseen-locker +case inverted (a locker the ship cannot see never warns). + +LW7. `Entity.TryActivateConsumable` (Entity.cs:895-907 at f2ecc96a) has no +liveness guard, and `CanActivateConsumable` (883-886) admits a Stackable design +every call. A dead or docked ship's agent still ticks (finding +deactivated-ship-spends-cargo); a stack of vents drains one per locked tick +(finding stackable-vent-drained-per-tick). + +LW8. ConsumableAiTests (f2ecc96a) holds a lock steady by turning lockers away +after one tick (`HoldLocks`, 204-210). Once warnings come from paints, a locker +looking away is the no-paint case, so every vent fixture must keep its lockers +painting. + +## Authority map: the incoming-lock warning + +- Owner: `Entity` (ServerShared) owns `IncomingLocks`, the ship's list of + `LockWarning` facts, published once per Entity.Update. +- Emission owner: `LockWeapon.Execute` is the one place that decides a beam is + on a ship (the LockAngle test it already makes). Each tick it paints, it + pushes one paint to the target: `target.ReceivePaint(Entity.Position, + Item?.Data, Lock)`. Nothing else calls ReceivePaint. +- Inputs: paints received since the ship's last Update (emitter position, + emitting gear design, lock strength). Never the emitter's weapon list, target + slot or EntityInfoGathered. +- Outputs: `IReadOnlyList IncomingLocks`, each with Bearing (unit + xz direction from this ship to the emitter at paint time), EmitterClass (the + emitting gear's `EquippableItemData`, null for a consumable-borne emitter) + and Strength (0..1; content is question lock-warning-content). +- Derived state: the paint inbox is a one-tick buffer, swapped into + IncomingLocks at the top of the receiver's Update and cleared. The list is + empty on an inactive ship and cleared by Deactivate. +- Forbidden writers and readers: CombatState and every Agents/ file read no + `LockWeapon`, `.Lock` or `IsLocked` of another ship; ActionGameManager reads + enemy lock state only through `CurrentEntity.IncomingLocks`. + `TargetedBy`/`TargetedByCount` are not lock facts and nothing new reads them. +- Shared paths: the AI's vent rule (CombatState) and the player's HUD + indicator read the same list: same ship, same information (ruling + same-ship-same-inputs, finding reads-enemy-weapon-state). +- Timing: an emitter that executes after the receiver in tick N is published at + the receiver's tick N+1, so each publish window holds each emitter's one + Execute; a warning lags its paint by at most one tick and never outlives it by + more than one (LW4 is why: no stale flags). +- Deletion line: none in this cut (additive); consumable-ai r2 deletes the + `VisibleEnemies`/`LockWeapon.Lock` read at Combat.cs:147-148. + +## Rationale + +- Push from the beam, not a scan by the receiver. A receiver-side scan of + `Zone.Entities` for lock weapons aimed at it would re-derive LockWeapon's arc + test (two owners of "the beam is on you") or read a stored painting flag that + goes stale offline (LW4). The push re-asserts the paint every tick it is true, + in the code that already decides it, and the receiver owns only what it + learns. +- Why the receiver is still the owner: the warning is a fact about what this + ship senses (ruling sensor-stat-set item 4), and a later receiver stat + (passive listening gear, the sensor-stat-set cut) belongs here, not on the + emitter. +- Search versus track is not in this cut. The only emitter in the sim is the + LockWeapon, a narrow track beam by nature (item 1). A Mode field would have + one producer and no reader that could ever see the other value. The + sensor-stat-set cut adds Mode when it lands the first wide search emitter. + This cut is the first slice of follow-up sensor-stat-set-cut: the receiver + half of item 4 for the emitter that exists. That follow-up stays open for + power, beam width, intercept factor, passive listening, shared tracks and + search mode. +- What the ship learns about lock progress is a product fork: item 4 lists + bearing, emitter class and search versus track, not progress. Question + lock-warning-content. Recommendation `strength`; see the question. +- Prior art for that fork (from general knowledge, no Eyes pass this time; + confidence moderate): a real radar warning receiver shows emitter type, + bearing, a relative signal strength (closeness and threat, not the shooter's + lock progress) and mode (search, track, launch). Flight games mostly reduce + this to stages: a "locking" tone, a "locked" tone, then a missile alert. + A continuous lock-progress readout is a game abstraction, chosen for the + reaction window it gives. +- The music switch (LW1) is a separate instruments-bound leak and a separate + product choice (gun-only enemies never paint), so it is a follow-up, not part + of this cut. + +## consumable-ai r2 + +- The vent rule reads `ship.IncomingLocks`: vent when any one warning's + Strength passes .5 (the strongest, never a sum), before the `target == null` + return so a ship painted by a locker it cannot see or has not targeted still + vents. The unseen-locker test flips from "does not vent" to "vents". +- `TryActivateConsumable` refuses on an inactive entity: the one path guards + player and AI alike (finding deactivated-ship-spends-cargo). +- The AI does not activate a consumable whose kind already has an active + effect, Stackable or not (finding stackable-vent-drained-per-tick). The + player may still stack by hand; `CanActivateConsumable` is unchanged. +- Tests pin the vent edge at .49/.51, the arc at 14/16 degrees, and an optimum + range distinct from the shortest gun's range (findings + lock-and-arc-bands-unpinned, optimum-range-not-pinned). +- Order: the consumable-only edits land on the branch at f2ecc96a, then Hands + merges origin/master (with lock-warning merged), then rewrites the vent rule; + Combat.cs is untouched by lock-warning, so its anchors read the same after + the merge. + +## Admitted (receipt mind-commit-35d69407...) + +- cut_spec cut-lock-warning.r1 (base 08d064c8), question lock-warning-content + (raised in it, recommended `strength`), cut_spec cut-consumable-ai.r2 (base + f2ecc96a, depends_on lock-warning), resolution superseding consumable-ai r1, + follow-ups music-reads-targeted-count and lock-warning-audio-cue. +- context-pack: lock-warning 9/9 anchors SAME at 08d064c8; consumable-ai r2 + 12/12 SAME at f2ecc96a (temporary detached worktrees, removed).