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