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>
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>
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>
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>
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>