Commit graph Aetheria/Assets/Scripts/ServerShared/Entity.cs
Author SHA1 Message Date
Meta Vens
c212ec0232 Reveal book: no scratch holds a departed entity, a destroyed lit gun is refused at the gate, one IsDestroyed rule, departure and allocation pins
Not verified yet at this commit (build and tests run next).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 02:20:46 +02:00
Meta Vens
396c02b08f Perceived item destruction ends the reveal in the observer's Sense; moving-subject bearing pinned
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>
2026-10-11 22:58:10 +02:00
Meta Vens
49ace47f34 Leave perceived item destruction to batch B: drop the Identified item pass and its tests from this cut
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:06:24 +02:00
Meta Vens
b6c2a2368b Reveal book: one gate, one removal counterpart; departure ends reveals on perception; departed subjects age out
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>
2026-10-11 22:03:20 +02:00
Meta Vens
b3c003dc3e Contact reads only the observer's book; a stale paint never recreates a reveal; Sense runs after every update; tests for the unpinned rules
- 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>
2026-10-11 20:34:58 +02:00
Meta Vens
6894c2093e Entity records reveals and derives Contact; ChunkInfo reads PassiveInfoPerSignature
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>
2026-10-11 18:25:57 +02:00
Meta Vens
dd896528cc Sensor: the pinger's swept set decides each crossing once; allies credit a published ping return inside the bistatic oval
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>
2026-10-11 15:22:29 +02:00
Meta Vens
c47ced9cfd Paints carry search or track; a ping sweep warns every ship it crosses in its beam; allies hear the return silently
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>
2026-10-11 14:05:16 +02:00
Meta Vens
c76584ee21 Entity.VisibilitySources: a ReadOnlyDictionary created once over the private field, so a cast back to Dictionary fails
Finding one-writer-castable. Test commit follows; builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:06:04 +02:00
Meta Vens
6f2dea3849 Entity.VisibilitySources is a read-only view: SetSignature and the decay loop are the only writers
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>
2026-10-11 11:37:07 +02:00
Meta Vens
1d14edea3a Signatures: every light signature names its channel and item through Entity.SetSignature
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>
2026-10-11 09:13:24 +02:00
Meta Vens
e386e1c437 Merge eureka/aetheria-release-lock-warning: a painted ship owns a typed incoming-lock warning
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 03:53:33 +02:00
Meta Vens
1c5c602054 Lock warning fix 1: publish paints in a zone phase, one lock-indicator curve, strength and progress-term pins
- 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>
2026-10-10 23:29:27 +02:00
Meta Vens
534e402fdf Throttle lock is a forward floor on the solve; allocation moves after the performance update
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>
2026-10-10 19:26:49 +02:00
Meta Vens
e5cfc0d676 Lock warning: a painted ship owns a typed incoming-lock warning carrying the lock's strength
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>
2026-10-10 19:25:22 +02:00
Meta Vens
618e53336f Delete TurnTime and IAnalogBehavior
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>
2026-10-10 13:39:23 +02:00
Meta Vens
1be1023558 Merge origin/master into throttle-lock 2026-10-10 05:26:11 +02:00
Meta Vens
d519d99b1e Consumable host: an effect lives exactly its Duration, not Duration plus one tick
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>
2026-10-09 23:48:02 +02:00
Meta Vens
29c5f7aba1 Consumable host: one initialiser for gear and running effects; tests pin overlap, lifetime, per-design cooldown, refit
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>
2026-10-09 23:24:54 +02:00
Meta Vens
aaab561f98 Consumable host: initialise at activation, dispose at expiry, instantiate consumables
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>
2026-10-09 20:19:22 +02:00
Meta Vens
c6bca1a522 ThrottleLock behaviour: an active consumable forces full forward intent, read once in Ship.Update
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>
2026-10-09 20:17:47 +02:00
Meta Vens
f28d4b4b12 Merge eureka/aetheria-release-sim-speed: one SimClock steps the zone, presenters draw ahead by Lead
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>
2026-10-09 02:29:37 +02:00
Meta Vens
38f7d73823 Sim speed: Entity.TurnRate's comment names no presenter
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 19:16:10 +02:00
Meta Vens
284231f4c1 Merge origin/master (2403a5f3, gear-without-presence) into eureka/aetheria-release-retire-longinusx
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
2026-10-08 18:59:38 +02:00
Meta Vens
c4bd6e153e Sim speed: one SimClock steps the zone; presenters draw ahead by Lead
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>
2026-10-08 18:26:02 +02:00
Meta Vens
96e96319c8 Merge origin/master (bd46f942 boss-gate, 1476cfd0 map docs) into eureka/aetheria-release-sim-mines 2026-10-08 16:31:36 +02:00
Meta Vens
baf90d34c9 Mine Launcher heat in kelvin; every shipped weapon must perform at a hull's starting temperature
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>
2026-10-08 15:04:16 +02:00
Meta Vens
2cfdc72853 Delete the aether drive: behaviour, union 12, hardpoint type, aggregates, settings, presenters, prefab
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>
2026-10-08 14:04:28 +02:00
Meta Vens
e3a457793b Merge eureka/aetheria-release-boss-gate (with eureka/aetheria-release-demo-galaxy) into master
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:03:32 +02:00
Meta Vens
179ab92abc Merge evasion-term: evasion is changes of vector plus a per-gun mount term; authored tracking rates in the catalog; the aether drive leaves the envelope
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>
2026-10-07 22:29:51 +02:00
Meta Vens
c86423936e Helm split, simulation: Ship.Turn is the only facing command; LookDirection becomes Aim
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>
2026-10-07 12:33:20 +02:00
Meta Vens
2cf980e353 Entity.Acceleration puts back what a VelocityLimit removed: the ceiling is not a change of vector
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 05:39:40 +02:00
Meta Vens
db4617d832 Boss gate: a boss zone spawns its faction's boss, and RunGoal derives whether the exit gate is open
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>
2026-10-07 01:13:08 +02:00
Meta Vens
da2957b81c Evasion term: reachable spread from the thrust and turn envelope, realised by changes of vector
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>
2026-10-07 00:48:27 +02:00
Meta Vens
d9f85dc639 Merge origin/master into mining: resolve five conflicts by owner, delete the belt-task machine
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>
2026-10-06 15:29:29 +02:00
Meta Vens
ec2042b390 Mining target queries: reticle, next, previous and nearest are Entity's best-first index queries
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>
2026-10-05 23:38:20 +02:00
Meta Vens
f803151681 Mining index: a general targeting index fed by providers; the linear chunk scan goes
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>
2026-10-03 15:02:51 +02:00
Meta Vens
5ea59ac6b5 Entity: an equipped item keeps the hardpoint it occupies
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>
2026-10-03 14:57:27 +02:00
Meta Vens
f88d1fc1b3 Mining Cut 3 fix: launchers do not count toward rock reach (Q12 A)
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>
2026-10-01 13:02:26 +02:00
Meta Vens
ff2be8e26f One placement rule for hardpoint gear
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>
2026-09-30 23:13:37 +02:00
Meta Vens
072a56f0b4 One loose fit rule for hardpoint gear
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>
2026-09-30 20:46:28 +02:00
Meta Vens
ff75b8ea34 Scenarios Cut 1: a stance survives docking; it goes only with an entity gone for good
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>
2026-09-30 15:09:12 +02:00
Meta Vens
15723f4037 Mining Cut 3: a chunk is a target, detected by reflected light
- 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>
2026-09-30 12:56:39 +02:00
Meta Vens
72c58d365f Scenarios Cut 1: a bare hull updates
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>
2026-09-30 12:52:44 +02:00
Meta Vens
fbda4c74fb Scenarios Cut 1: Zone.Admit is the one admission into a zone
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>
2026-09-30 12:32:59 +02:00
Meta Vens
4629bc6799 Fire control 12.4 closing batch: Death fires once; pin the blast loop's multi-kill and untouched-entity cases
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>
2026-09-29 07:36:04 +02:00
Meta Vens
7c99909d2b Cut 12.4(b): Detonate replaces Splash and Entity.Absorb
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>
2026-09-26 01:17:36 +02:00
Meta Vens
c07b4fee65 Cut 12.3 fix batch (F4, F6): delete unreachable guards, fix stale ArmorAbsorb comment
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>
2026-09-25 18:33:23 +02:00
Meta Vens
e9339a5673 Cut 12.3 fix batch: one apply path for every item (operator ruling)
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>
2026-09-25 17:01:05 +02:00
Meta Vens
567579136a Cut 12.3 fix batch: proportional absorption for multi-cell items shared between lanes
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>
2026-09-25 16:16:53 +02:00