One color decoder, and gates that report - #566
Merged
Merged
Conversation
Seven linters were carried on an explicit allowlist in the meta-check added
last session: they were run, and nothing proved they could still fail. Each
now has a self-test with a red half per rule, registered in the contract
suite, and the allowlist is empty.
Five needed a way to be handed something other than the repository itself, so
they take -BinariesRoot / -RepoRoot / -SyncScriptPath / --repo-root. Two
already accepted a path and needed only fixtures.
Writing the red halves found three things the green runs could not:
* validate-github-pages-css.sh matched a required selector and its property
independently over the whole file, so `section { float: none }` satisfied
the rule for `header`. Removing either was invisible. The property is now
checked inside the selector's own brace-counted block.
* validate-devcontainer-config.ps1 carried a workflow-directory guard that no
input could reach: the publish workflow lives inside that directory and is
checked first.
* validate-hook-sync-calls.ps1 matches its required patterns
case-insensitively, so `-LocalSha` satisfies the requirement for
`localSha`. The fixture removal matches that semantics, or the red half
removes nothing and still reports green.
Fixes #562
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ColorQuantization.ToNormalized multiplied by the rounded reciprocal 1f/255f while ColorExtensions divided by 255f, and those are not the same float: measured on 6000.4.6f1 they disagree by one ULP on 126 of the 256 channels. Both were live on the same "is this pixel transparent" decision, across 20+ call sites. ToNormalized now divides. That is bit-identical to Unity's own Color32 to Color conversion on all 256 channels -- measured, 0 disagreements, against 126 for the reciprocal -- so the package agrees with the engine and with every consumer that writes c.a / 255f by hand. The eight alpha-cutoff comparisons in ColorExtensions now hoist ToThresholdByte out of their pixel loops, matching the 13 Editor sprite sites and answering the cutoff once per call instead of once per pixel. The eight remaining per-channel decodes route through ToNormalized. ChannelStep stays, documented as a step size for scaling a tolerance, with the reason it is not a decode. The CI boundary cutoff 0.8862745f turns out to BE a channel boundary -- it is bit-for-bit 226 / 255f -- so 226 belongs under it and ToThresholdByte now answers 226. The old 225 was the reciprocal lifting channel 226 one ULP above its own boundary. Verified on 6000.4.6f1 through the MCP bridge: 395 pass / 0 fail across the quantization, color, editor-cache and sprite fixtures. 23 [TestCaseSource] cases could not run there, including the sprite alpha-threshold ones; CI's Unity legs cover those. Fixes #565 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A relational assignment that found nothing cost 366-431 us against ~1 us for one that succeeded, paid on every assignment rather than once. For a collection field, finding nothing is a normal state -- a Collider2D[] on an object with no colliders is an error every time it is assigned -- so a scene binding a few hundred objects at load paid it repeatedly, and the only symptom was errors in the console, which reads as a content problem. All of it was Unity capturing a managed stack trace for the error log. Measured on 6000.4.6f1: Debug.LogError 178.4 us per call, the same message through LogOption.NoStacktrace 13.3 us -- 13.4x. The logger's three severities now take stackTrace: false, and LogMissingComponentError passes it. Every message survives, one per object, with its context still set. What goes is a stack that is the same internal assignment path every time and names nothing the message does not. Measured after, on the same editor with the same control: unsatisfied 366-431 us -> 25.1 us (~15x) satisfied 0.471 us control (Transform) 0.302 us (issue recorded 0.305-0.309 us) Still ~50x a satisfied field, because the log itself is 13.3 us. Coalescing per (type, field) would close more and changes what the console shows, so that stays the owner's call on #564. Local Unity evidence: 277 pass / 0 fail across all 17 relational fixtures. 51 of them use LogAssert and cannot run through the MCP bridge, which is exactly the set asserting this error -- whether LogAssert still matches a NoStacktrace error is answered by CI, not here. Fixes #564 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PLAN.md and the progress log for the session. Four MCP-bridge traps added to the devcontainer-testing skill, all found by a control rather than by inspection: * a timing cell that prints 0.000 measured nothing and reads as "free" * Convert.ChangeType throws InvalidCastException on a non-IConvertible [TestCase] argument and kills the whole sweep from inside the harness * the bridge returns every Unity console line, so a logging probe truncates its own RESULT line * the fixtures that cannot run there can BE the coverage: 277 pass / 0 fail over 17 relational fixtures, with the 51 unrunnable ones being exactly those asserting the log under change test-lint-doc-counts.ps1 no longer runs the real sync script over the whole repository. That re-answered a question lint:repo and validate:content both already ask, and cost 26.7 s of the contract suite. The stub cases are the green half for the wrapper's own contract: 26.7 s -> 2.9 s. Refs #435, #543, #540 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
unity-devcontainer-testing.md was at 497 of 500 lines and reported CRITICAL on every commit that touched it, so the next recorded trap would have broken the gate rather than being written down. It was doing three jobs: the licensed Docker legs, the MCP bridge as a fixture runner, and measuring in the MCP sandbox. The last two are read together and almost never alongside the first. unity-devcontainer-testing.md 497 -> 227 Docker, license, troubleshooting unity-mcp-fixture-runner.md -> 222 reflection, fixtures, the loop unity-mcp-measurement.md -> 93 timing, allocation, controls The partition was verified total and disjoint over all 498 original lines before anything was written, so no recorded trap was lost -- each one cost a session to find. Cross-references updated: the two links to the moved #no-license-the-mcp-editor-still-runs-the-real-fixtures anchor now point at the file that owns it. Fixes #568 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EffectHandler.ApplyEffect warns when an Instant effect also defines periodic
or behaviour data, then calls InternalApplyEffect, which tested the same two
conditions and warned again. InternalApplyEffect(AttributeEffect) is private
with exactly one caller -- that Instant branch -- so its condition was always
true and its message was always the second copy of one the console had just
shown. Each carried a {effect:json} render of the whole effect and a
stack-trace capture, on every application.
The duplicate is gone. The survivor passes stackTrace: false, since it is
reported per application and the stack is the same path every time.
It survived because the test registered one ExpectWallstopLog expectation,
LogAssert.Expect matches one message, and an unexpected WARNING does not fail
a Unity test. The test now counts emissions through
Application.logMessageReceived and asserts exactly one.
The remaining halves -- the per-application repetition of what is a static
property of the asset, and the {effect:json} render -- are #567.
Refs #567
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stackTrace <param> was written between the [Conditional] attribute list and the method, where a doc comment is not attached to the declaration, and without a <summary> to hang it on -- a floating <param> is not a doc comment, it is a comment that looks like one. All four public entry points now carry a full block before their attributes, and the three UnityLogTagFormatter methods whose signature this change widened gain the docs they never had. The package requires <summary> on every public member. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why: The package had two decoders for one color channel, and a relational field that found nothing cost 400x one that succeeded.
What:
ColorQuantization.ToNormalizeddivides by 255, matching Unity's ownColor32conversion on all 256 channels (the rounded reciprocal disagreed on 126).Log/LogWarn/LogErrornow takestackTrace: false.Instanteffect that defines periodic data warns once instead of twice.Fixes #565
Fixes #564
Fixes #562
Fixes #568
Note
Medium Risk
Color decode and threshold behavior change for many channels (intentional Unity alignment) could shift pixel filtering; logging API is backward-compatible but alters default relational and effect diagnostics at scale.
Overview
Aligns 8-bit channel decoding with Unity and removes hidden load-time cost from repeated Unity logs, plus repo tooling that can actually fail when its rules break.
Color and visuals:
ColorQuantization.ToNormalizednow divides by255f(notchannel * ChannelStep), with docs/tests pinning agreement with Unity'sColor32conversion and fixing the CI boundary case forToThresholdByte. Sprite color averaging inColorExtensionsusesToThresholdByteandToNormalizedinstead of per-pixel/ 255fcompares.Logging and gameplay paths:
Log/LogWarn/LogError(extensions andUnityLogTagFormatter) accept optionalstackTrace: false, routing throughDebug.LogFormatwithLogOption.NoStacktrace. Missing relational components log errors without stack traces (~15× faster unsatisfied assignments per changelog). Instant effects with periodic/behaviour data warn once (duplicate warn removed fromInternalApplyEffect), also without stack trace.Docs: MCP no-license testing guidance moves from
unity-devcontainer-testingintounity-mcp-fixture-runnerandunity-mcp-measurement, with cross-links updated.CI/contracts: Seven validators/linters gain self-tests with red halves;
lint-bundled-assembliesandlint-doc-countsaccept injectable paths;run-contract-testsandpackage.jsonregister the new test scripts;test-run-repo-lintclears the "missing red half" allowlist.Reviewed by Cursor Bugbot for commit 8ce7dd6. Bugbot is set up for automated code reviews on this repo. Configure here.