Skip to content

Release 0.9.5 - #20

Merged
CoderGamester merged 26 commits into
masterfrom
develop
Aug 4, 2026
Merged

Release 0.9.5#20
CoderGamester merged 26 commits into
masterfrom
develop

Conversation

@CoderGamester

@CoderGamester CoderGamester commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Changed:

  • Replaced the Unity Package Starter Kit README with accurate Statechart usage and API guidance based on the package's actual runtime surface.
  • Improved public API documentation and automated coverage of existing state creation, transition, nested/split, waiting, and validation behavior; this release does not change runtime behavior.

Bumps to 0.9.5 (published at 0.9.4; this is a real release). This
package had only a README.md, and that README was the unedited Unity
"Package Starter Kit" boilerplate template.

- New AGENTS.md, verified against every file in Runtime/: the full
  state-type reference (Initial/Final/State/Transition/Choice/Wait/
  TaskWait/Nest/Split/Leave), the capability-interface composition
  pattern in IState.cs, nesting/splitting/leaving semantics, and the
  gotchas that fell out of reading the source closely (event identity
  is per-instance not per-name, Trigger is a silent no-op when not
  running, TaskWait cannot receive events while waiting).
- New CLAUDE.md (thin @AGENTS.md wrapper).
- README.md rewritten with a verified-working Quick Start example
  (built from the same setup pattern StatechartTest.cs uses) and real
  installation/API content, following the repo-wide section order.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@CoderGamester CoderGamester self-assigned this Jul 29, 2026
CoderGamester and others added 25 commits August 2, 2026 14:38
Adds (or updates) Tests/AGENTS.md and its Tests/CLAUDE.md wrapper, and points
the package-root AGENTS.md at it as required reading before touching Tests/.

Sections 1-2 (ADMIT test-admission criteria, RCR revert-and-confirm-red) are
shared verbatim across all six GameLovers packages and must be changed in
lockstep. This revision anchors mutation references on file + symbol rather than
file:line, which rots, and caps the on-test comment budget - change narration and
investigation transcripts belong in the commit body, not on the test.

No test or production code changes in this package.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… state)

Second slice of the suite-wide RCR backfill. Covers StatechartTest (8),
StatechartTransitionTest (4) and StatechartStateTest (6). All 17 distinct
mutations were applied, observed red, and reverted by the harness; the three
production files touched were verified byte-identical afterwards. Suite green
at 89/89.

Where two tests share one guard, the mutation is split so each pins its own half
- e.g. InitialState.Validate's `_transition?.TargetState == null` covers both
"no transition at all" and "transition without target", and the two tests now
carry mutations that redden one and leave the other green.

One dud found: State_TriggerNotConfiguredEvent_NoEffect has no single-line
mutation. SimpleState.OnTrigger's miss path is a plain Dictionary TryGetValue
returning false, and StatechartEvent's Equals/GetHashCode are both Id-based, so
forcing a lookup hit needs coordinated edits to two members. The behaviour it
pins is the BCL's rather than this package's (A3). Annotated as a review
candidate rather than deleted.

Overlaps are recorded on the tests that have them: the SimpleState.Exit and
Statechart.Run mutations each redden several siblings, because those siblings
assert the same exit hook / all call Run() first.

RCR: SimpleTest (StatechartTest) <- InitialState.cs Exit fan-out loop bound i < 0
RCR: InitialState_MissingTransition_ThrowsException <- InitialState.cs Validate guard to _transition != null && _transition.TargetState == null
RCR: InitialState_TransitionWithoutTarget_ThrowsException <- InitialState.cs Validate guard to _transition == null
RCR: InitialState_StateTransitionsLoop_ThrowsException <- InitialState.cs Validate self-target check to if (false)
RCR: InitialState_MultipleTransitions_ThrowsException <- InitialState.cs Transition duplicate guard to if (false)
RCR: NoInitialState_ThrowsException <- Statechart.cs ctor InitialState null guard to if (false)
RCR: MultipleInitialStates_ThrowsException <- StateFactory.cs Initial duplicate guard to if (false)
RCR: MultipleFinalState_ThrowsException <- StateFactory.cs Final duplicate guard to if (false)
RCR: SimpleTest (StatechartTransitionTest) <- TransitionState.cs Enter fan-out loop bound i < 0
RCR: TransitionState_TransitionWithoutTarget_ThrowsException <- TransitionState.cs Validate guard to _transition == null
RCR: TransitionState_TransitionWithoutTransition_ThrowsException <- TransitionState.cs Validate guard to _transition != null && _transition.TargetState == null
RCR: TransitionState_TransitionsLoop_ThrowsException <- TransitionState.cs Validate self-target check to if (false)
RCR: SimpleTest (StatechartStateTest) <- SimpleState.cs Exit fan-out loop bound i < 0
RCR: State_TransitionWithoutTarget_Succeeds <- StateInternal.cs Trigger nextState null early-return to if (false)
RCR: State_TriggerNotConfiguredEvent_NoEffect <- (no single-line mutation; A3 review candidate)
RCR: State_PauseRunStatechart_Success <- Statechart.cs Run delete _isRunning = true
RCR: State_ResetRunStatechart_Success <- Statechart.cs Reset delete the _currentState rewind
RCR: StateTransitionsLoop_ThrowsException <- SimpleState.cs Validate self-target check to if (false)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Third slice. 6 of 7 mutations verified red and reverted; suite green 89/89.

Dud found: ChoiceState_MissingTransitions_ThrowsException has no single-line
mutation. A choice state with zero transitions trips BOTH independent Validate
guards (!hasTransitionWithCondition and noTransitionConditionCount == 0), so
disabling either leaves the other throwing - verified by narrowing the first
guard and observing the test stay green. Double-covered belt-and-braces.

The two tests that share the !hasTransitionWithCondition guard are split so each
pins its own input: one narrows the guard to require transitions, the other to
require none.

RCR: SimpleTest <- ChoiceState.cs OnTrigger invert CheckCondition()
RCR: ChoiceState_MultipleTrueConditions_PicksFirstTransition <- ChoiceState.cs OnTrigger reverse the scan order
RCR: ChoiceState_MissingTransitions_ThrowsException <- (none; double-guarded)
RCR: ChoiceState_MissingConditionTransition_ThrowsException <- ChoiceState.cs Validate narrow guard to _transitions.Count == 0
RCR: ChoiceState_OnlyConditionTransition_ThrowsException <- ChoiceState.cs Validate noTransitionConditionCount guard to if (false)
RCR: ChoiceState_WithoutTarget_ThrowsException <- ChoiceState.cs Validate TargetState null guard to if (false)
RCR: StateTransitionsLoop_ThrowsException <- ChoiceState.cs Validate self-target guard to if (false)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fourth slice. 9 of 14 mutations verified red and reverted; suite green 89/89.
Running total for this package: 46 of 89 annotated.

Harness fix landed alongside: verify.py deleted a stale results file before each
run and retries when Unity produces none. Without it a failed Unity invocation
silently reported the PREVIOUS spec's XML, which produced four bogus verdicts
naming tests from a different fixture. Those verdicts were discarded and the
slice re-verified from scratch.

Five tests have no single-line mutation and are annotated as such:
- WaitState_MissingConfiguration / TaskWait_MissingConfiguration - the
  unconfigured state in both fixtures also has no transition, so it trips two
  independent Validate guards; disabling either leaves the other throwing.
- TaskWait_EventTrigger_DoesNothing - making OnTrigger honour the event leaves
  it green, because the awaited task reaches the same final state either way.
  The name claims more than the body checks (D2).
- UniTaskWait_EventTrigger_DoesNothing - shares OnTrigger with the above; the
  two overloads are not separately guarded, which is itself worth recording.
- SplitActivity_CompleteOnlyOneActivity_OnHold needed the inverse mutation to
  the one first tried: dropping the inner term from IsCompleted does nothing
  because Complete() has already set the surviving term.

RCR: SimpleTest (Wait) <- WaitState.cs OnTrigger return null instead of the completion-gated transition
RCR: SplitActivity_CompleteBoth_Success <- WaitActivity.cs AreInnerCompleted if (true)
RCR: SplitActivity_CompleteOnlyOneActivity_OnHold <- WaitActivity.cs AreInnerCompleted if (false)
RCR: WaitState_EventTrigger_ForceCompleted <- WaitState.cs OnTrigger suppress the targeted-event return
RCR: WaitState_EventTriggerWithoutTarget_OnlyEvokesOnTransition <- WaitState.cs OnTrigger suppress the targetless-event return
RCR: WaitState_MissingConfiguration_ThrowsException <- (none; double-guarded)
RCR: WaitState_MissingTarget_ThrowsException <- WaitState.cs Validate TargetState guard to if (false)
RCR: WaitState_TransitionsLoop_ThrowsException <- WaitState.cs Validate self-target guard to if (false)
RCR: SimpleTest (TaskWait) <- TaskWaitState.cs OnTrigger return null instead of the completion-gated transition
RCR: TaskWait_EventTrigger_DoesNothing <- (none; D2 review candidate)
RCR: UniTaskWait_EventTrigger_DoesNothing <- (none; shares OnTrigger with the above)
RCR: TaskWait_MissingConfiguration_ThrowsException <- (none; double-guarded)
RCR: TaskWait_MissingTarget_ThrowsException <- TaskWaitState.cs Validate TargetState guard to if (false)
RCR: TaskWait_TransitionsLoop_ThrowsException <- TaskWaitState.cs Validate self-target guard to if (false)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ed §2)

§2 declared any test without an RCR line "suspect by default", but some correct
tests provably have no one-line mutation - double-guarded validation, where an
unconfigured object trips two independent guards so disabling either leaves the
other throwing. The rule was mislabelling tests that are right and unbreakable.

Adds an UNFALSIFIABLE exemption on §13's terms: the reason must be falsifiable,
must name both guards, and must record that a mutation was tried and observed
green. "Couldn't find one" is explicitly not a reason - that is an unfinished
RCR, not an exemption.

Also adds a verdict table for tests that resist mutation, because they are not
one problem: A5 duplicates get deleted (naming the surviving sibling), D2
overclaims get a strengthened assertion or an honest rename, and UNFALSIFIABLE
tests are kept with the exemption comment. The class must be proven before
acting - an A5 duplicate by observing the sibling's mutation redden both, a D2
overclaim by observing the implied mutation leave the test green.

§1 and §2 remain byte-identical across all six packages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… exemption

TaskWait_EventTrigger_DoesNothing was a D2 overclaim: it triggered the event and
then unblocked the task, so both "the event advanced it" and "the task did" reached
the same final state and no assertion could separate them. Moved the discriminating
assertions into the window after Trigger and before the unblock, where an honoured
event would show up. TaskWaitState.OnTrigger returning _transition on a non-null
event now reddens it in isolation (verified).

State_TriggerNotConfiguredEvent_NoEffect was mislabelled A3. SimpleState.OnTrigger
and its _events map do participate, so the behaviour is this package's - it is
simply double-guarded. Reclassified as UNFALSIFIABLE with the sanctioned wording,
and both halves were actually observed green rather than asserted:
- StatechartEvent.GetHashCode => 0 : still misses, Equals rejects in the bucket
- StatechartEvent.Equals => always true : never consulted, differing hash probes
  another bucket

Also picks up the section 2 fourth verdict class (A3 reject -> delete), applied
verbatim across all six Tests/AGENTS.md.

RCR: TaskWait_EventTrigger_DoesNothing <- TaskWaitState.cs OnTrigger return _transition when statechartEvent != null

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Section 13 now carries a dated baseline for this package's runtime assembly, plus
the reason to steer by that number rather than the combined one.

Every earlier coverage figure in this repo was an artifact and must not be compared
against:
- reports before today ran without -debugCodeOptimization, so Unity compiled Release
  and emitted ~40% fewer sequence points (MathfloatP showed 637 coverable lines
  instead of 1002) - a silently shrunken denominator
- some runs leaked test and sample assemblies into scope, and some covered only 3 of
  the 6 packages

The current run covers all 11 production assemblies with none leaking, verified via
the MathfloatP denominator check now documented in Tools/coverage.sh.

Repo-wide: runtime 73.9%, Editor 5.5%, combined 41.0%. Editor is 48.1% of all
coverable lines and is accepted-untestable per the ACCEPTED (iii) rows in section 13,
which is the whole reason the combined figure is not the one to track.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
All 8 mutations applied, observed red, reverted; both production files verified
byte-identical afterwards.

The two layer-guard tests are separated arithmetically rather than by disabling the
guard: LeaveState.Validate requires `TargetState.RegionLayer == RegionLayer - 1`, so
mutating the constant to `RegionLayer` admits the same-layer target (reddening only
the same-layer test) and mutating it to `RegionLayer - 2` admits the two-layer jump
(reddening only the wrong-layer test). Each leaves the other green, so the two halves
of one guard are pinned independently rather than collapsing into one claim.

The same split applies to the missing-configuration vs missing-target pair on the
null-conditional guard above it.

The three behavioural tests share the leave mechanism and redden together; that
overlap is recorded on each rather than papered over.

RCR: SimpleNestTest <- LeaveState.cs Enter fan-out loop bound i < 0
RCR: SimpleSplitTest <- SplitState.cs ProcessInnerStates leaveState.LeaveTransition to _transition
RCR: SplitState_OnlyLeaveInnerStates_LeaveFirstState <- SplitState.cs ProcessInnerStates disable the is LeaveState capture
RCR: LeaveState_MissingConfiguration_ThrowsException <- LeaveState.cs Validate guard to LeaveTransition != null && ...
RCR: LeaveState_MissingTarget_ThrowsException <- LeaveState.cs Validate guard to LeaveTransition == null
RCR: LeaveState_MultipleTransitions_ThrowsException <- LeaveState.cs Transition duplicate guard to if (false)
RCR: LeaveState_SameLayerTarget_ThrowsException <- LeaveState.cs Validate layer check RegionLayer - 1 to RegionLayer
RCR: LeaveState_WrongLayerTarget_ThrowsException <- LeaveState.cs Validate layer check RegionLayer - 1 to RegionLayer - 2

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mutations were mapped by probe rather than guessed: seven candidate one-line edits
were applied in turn and the resulting red set recorded, then each test was assigned
the edit that best characterises what it uniquely pins.

The ExecuteExit/ExecuteFinal flags separate cleanly in both directions, which is what
makes the four force-complete variants independently falsifiable: setting a flag's
guard to false reddens only the flag-on tests, ignoring the flag reddens only the
flag-off tests. Each variant now carries the direction that matches its own claim.

Two results worth recording:

- NestedState_MissingConfiguration_ThrowsException is UNFALSIFIABLE, verified from
  both sides. An empty nest trips NestState.Validate's Count != 1 AND
  SplitState.OnValidate's Count == 0; disabling either leaves the other throwing, so
  neither guard alone is a valid mutation.

- The four InnerEventTrigger variants (and the two MultipleNestedStates equivalents)
  are indistinguishable by every probed mutation. The execute flags are unreachable
  on that path, because the inner state is already a FinalState by the time Exit runs
  and the ExecuteFinal guard explicitly excludes FinalState. That makes the three
  Disable* variants suspected A5 duplicates of the plain CompleteSuccess test. They
  are annotated as suspected, not deleted - proving A5 needs evidence that NO mutation
  separates them, and a probe over seven candidates is not that.

RCR: SimpleTest <- SplitState.cs ProcessInnerStates is-not-FinalState branch to else if (false)
RCR: NestedState_WithoutTarget_Successful <- SplitState.cs Enter empty the _onEnter fan-out
RCR: NestedState_InnerEventTrigger_CompleteSuccess <- SplitState.cs ProcessInnerStates inner drain loop to while (false)
RCR: NestedState_InnerEventTrigger_DisableExecuteFinal_CompleteSuccess <- same (suspected A5)
RCR: NestedState_InnerEventTrigger_DisableExecuteExit_CompleteSuccess <- same (suspected A5)
RCR: NestedState_InnerEventTrigger_DisableExecuteExitFinal_CompleteSuccess <- same (suspected A5)
RCR: NestedState_InnerEventTrigger_RunResetRun_CompleteSuccess <- SplitState.cs Enter keep CurrenState instead of rewinding to InitialState
RCR: NestedState_EventTrigger_ForceCompleteSuccess <- SplitState.cs Exit ExecuteExit guard to if (false)
RCR: NestedState_EventTrigger_DisableExecuteFinal_ForceCompleteSuccess <- SplitState.cs Exit ignore the ExecuteFinal flag
RCR: NestedState_EventTrigger_DisableExecuteExit_ForceCompleteSuccess <- SplitState.cs Exit ignore the ExecuteExit flag
RCR: NestedState_EventTrigger_DisableExecuteExitFinal_ForceCompleteSuccess <- SplitState.cs Exit ignore the ExecuteFinal flag
RCR: MultipleNestedStates_InnerEventTrigger_CompleteSuccess <- SplitState.cs ProcessInnerStates inner drain loop to while (false)
RCR: MultipleNestedStates_InnerEventTrigger_DisableExecuteExitFinal_CompleteSuccess <- same (suspected A5)
RCR: MultipleNestedStates_EventTrigger_ForceCompleteSuccess <- SplitState.cs Exit ExecuteFinal guard to if (false && ...)
RCR: MultipleNestedStates_EventTrigger__DisableExecuteExitFinal_ForceCompleteSuccess <- SplitState.cs Exit ignore the ExecuteExit flag
RCR: NestedState_MissingConfiguration_ThrowsException <- (none; double-guarded, verified both sides)
RCR: NestedState_TransitionsLoop_ThrowsException <- SplitState.cs OnValidate self-target check to if (false)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tests/AGENTS.md §8 requires private fields to be _-prefixed. The local
`activitySplit` variable is untouched - the convention applies to fields only.

No behavioural change; EditMode green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sections 1-2 are shared verbatim across all six packages; this adds a sixth admission
question and the worked instance behind it.

A6 asks whether an assertion's outcome would change if project configuration changed -
a renderer feature installed or removed, an Addressables catalog built, a sample
imported. If so the test must READ that state rather than assume one value of it.

A6 is not A3. A3 asks whether the package computed the value; A6 asks whether the test
assumed which value it would be. A test can satisfy A3 and still fail A6, which is
exactly how the gap went unnoticed: UiBackdropBlurPresenterFeatureTests read a
package-computed flag (UiBackdropBlurRendererFeature.IsInstalled) but hard-coded the
expectation that it was false. Batchmode never instantiates the URP renderer, so the
flag was false there and all five tests passed; in the Editor the feature registers from
the project's renderer asset and all five failed. The fixture was asserting a fact about
the repo, not about the code under test.

Validated against the existing corpus before being written, per root AGENTS.md 2.2: the
blur fixture was the only violation and is already fixed. AddressablesUiAssetLoaderTests
asserts on a key that is unresolvable either way, and UiCameraStackFeatureTests builds
its own cameras rather than reading project renderer state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t 89/89

Covers StatechartSplitTest (19) and StatechartNestSplit_IntegrationTest (6). 24 of 26
mutations observed RED and reverted; all production files byte-identical afterwards.

SplitState_MissingConfiguration_ThrowsException is UNFALSIFIABLE, and this time proven
rather than argued: an empty split trips both SplitState.Validate's Count < 2 and
SplitState.OnValidate's Count == 0, and each guard was disabled INDEPENDENTLY with the
test observed staying green both times. Its sibling SplitState_SingleConfiguration
splits the same guard cleanly (< 2 -> < 1) and is isolated.

Deliberate avoidance worth recording: the drain-loop mutation used in the Nest slice
(while (nextState != null) -> while (false)) is unusable in this filter. It prevents
TaskWaitState from ever being triggered, so the awaited task never completes and three
async integration tests hang for the full harness timeout rather than failing. The
substitutes - Trigger(statechartEvent) -> Trigger(null), and narrowing the loop
condition - leave task start-up intact.

Suspected A5 duplicates, annotated not deleted: the three
SplitedState_InnerEventTrigger_Disable*_CompleteSuccess variants have assertion sets
identical to the plain CompleteSuccess test, because both NestedStateData flags are
unreachable on the inner-event path - at SplitState.Exit the regions are already
FinalState, which ExecuteFinal's own guard excludes, and ExecuteExit only reaches
FinalState.Exit(), a no-op. Same shape as the NestTest finding. Proving A5 needs
evidence that no mutation separates them; a candidate sweep is not that.

Unpinned surface noticed in passing: SplitState.DelayForceComplete's
waitState.ForceComplete() call has no test that would notice its deletion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sections 1-2 are shared verbatim across all six packages, so this lands here even though
no test in this package changed.

The verdict table offered only delete-or-strengthen for a test with no unique one-line pin.
That is wrong for integration tests: blast radius measures SPECIFICITY, not value. A test
observed reddening under a broad mutation genuinely catches that regression even though no
single line is its own.

Concrete cost of the omission: mining the mutation results for A5 duplicates initially
produced 56 "confirmed" candidates. Measuring blast radius cut that to 16 - the other 40 were
simply tests sharing a code path, e.g. eight dying to one UiService.CloseUi mutation. Without
this row they had no home and the table pointed at delete.

The row carries its own abuse guard: the radius must be a recorded observation, "some
mutation somewhere reddened it" is explicitly not the standard, and an over-provisioned
cluster is a human editorial decision rather than an automatic verdict.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…it proved

First trustworthy coverage figure for this repo. Regenerated with -debugCodeOptimization,
all 11 GameLovers assemblies in scope, test and sample assemblies excluded. Repo-wide
runtime coverage is 74.1% (6609/8922).

Do not compare against any earlier number. 41.8% was stale, wrongly scoped to 6 assemblies,
and diluted by Editor code; 38.3% was compiled in Release, which silently shrank the
denominator ~40%. The register now names the sanity check that catches a repeat: MathfloatP
must report ~1002 coverable lines, not 637.

The OPEN rows added here are findings the mutation pass PROVED rather than suspected - each
one is a mutation that was applied and observed leaving its test green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
§13 asserted every named symbol was 'either ACCEPTED or OPEN' while four packages had
already grown CLOSED rows — the spec forbade rows it contained. CLOSED is now first-class,
and it carries a contract: name the commit AND the observation, including the environment
the observation came from. A row closed on 'the fix landed' is still OPEN, because the fix
is the edit and the closure is the evidence.

Second rule: closing a row means re-deriving its claim against current source, never
reading the commit that claimed to fix it. A partial fix and a complete one produce the
same green suite and the same confident commit message, so the commit cannot be evidence
for its own completeness.

§1 and §2 are shared verbatim across all six packages; §13's preamble is too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mechanical pass, no behaviour change:
- dropped the empty `<param name="states"></param>` on IStateFactoryInternal.Add
  and the empty `<returns></returns>` on ITransitionInternal.CheckCondition —
  both are internal interfaces, where §6.6 bans these tags outright, and both
  tags carried no text
- replaced the WaitActivity(Action<uint>) doc block with a `//` rationale
  comment; §6.6 never XML-documents constructors, and the useful part of that
  block was why the ctor is public on an internal type
- added `/// <inheritdoc />` to StatechartEvent's Equals / GetHashCode / ToString
  overrides, which inherit System.Object's documentation

Verified with Tools/style-audit.py: this package now reports 0 of the mechanical
rule classes (A/B/C/D/K/N and documented-base overrides).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… unowned-edit measurement

Tests/AGENTS.md section 2 said a test with no // RCR: line is a suspect by default, without
carving out Smoke/. Section 1 already exempts that directory (its defect class is "the assembly
no longer loads", which has no one-line mutation), so the omission flagged those fixtures
forever. Exemption is now explicit, on the same directory basis.

Also records that "unannotated" is three states, not one: observed RED with the write-back
lost, seen reddening only as collateral, or never probed. Only the last needs a probe, and
prepared annotation text must never be written without a matching RED-OK - it exists for tests
that were never probed, and writing it fabricates a verified claim.

Adds a section 13 row for the measured count of production edits that redden only collaterally
(223 repo-wide, from .test-all/rcr/unowned-edits.json). Recorded with the caveat that it is NOT
that many missing tests: for foundational primitives and the UiService integration hub, having
no isolated owner follows from centrality, not neglect.
Completes this package — Tools/style-audit.py reports 0 items.

The one worth reading is StatechartEvent.Equals: equality is by the
auto-incrementing Id, so two `new StatechartEvent("Jump")` instances are never
equal to each other. That is the package's most surprising behaviour (AGENTS.md
§4 calls it out) and the signature gave no hint of it.

Also documented StateInternal.OnTrigger (returns null when the state does not
handle the event), IsStateLogsEnabled, SplitState.OnValidate, the internal
LeaveState.LeaveTransition, the editor-only Statechart.CurrentState, and the
NestedStateData conversion operator.

Four of these summaries were initially written with the first line double-indented;
a repo-wide scan for doc blocks whose lines disagree on indentation now reports
only one, which predates this work.

Verified: batchmode green — EditMode 805/805, PlayMode 295/295.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`IStateMachineDebug`'s summary block indented two of its three lines with tab+space
instead of tab, leaving the block internally inconsistent. Not notable enough for a
CHANGELOG entry, but it was the single finding standing between the repo and a clean
run of the code-standards post-edit scan — and carrying a baseline file to excuse
two characters costs more than fixing them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A docs pass rewrote the file with LF, changing every historical byte and
breaking the release-notes validator's baseline comparison. Restores the
committed convention per AGENTS.md; no content change.
Unity's packer uses .gitignore as its pack-ignore list, so listing .github/
drops the CI workflow from the tarball while git keeps tracking it (gitignore
does not untrack existing files). Verified on a real clone: 434 -> 433 entries,
.github 1 -> 0, Runtime unchanged.
@CoderGamester
CoderGamester merged commit 21aabc1 into master Aug 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant