X-20 is answered, and the player could not be built to answer it - #183
Merged
Conversation
…cent file `com.ivanmurzak.unity.mcp` 0.90.0 and its ten extension packages (#175) installed McpPlugin.dll and McpPlugin.Common.dll with `defineConstraints: []`, while every other DLL of the same closure -- R3, ReflectorNet, SignalR, Microsoft.CodeAnalysis -- carried `- UNITY_EDITOR`. McpPlugin.dll references R3, so a player build included the first, excluded the second, and UnityLinker died with Mono.Cecil.AssemblyResolutionException: Failed to resolve assembly: 'R3' which names R3, the file that is correct, and never names McpPlugin.dll, the file that is wrong. Two .meta lines fix it: same tree, same command, Failure -> Success. Never only a compile problem. defineConstraints are evaluated per target and UNITY_EDITOR is false in every player, so the shipping Linux dedicated server was equally unbuildable -- and a build that HAD linked would have carried an HTTP bridge hosting Roslyn into the game server image, the exact thing EditorBuild.StripEditorOnlyDefines was written to prevent. Nothing in CI could see it: no job builds a Unity player, images.yml downloads a pre-built Linux artifact from a release, and build-test is `dotnet build` over the pure-.NET libraries, which never reads Assets/Plugins. Four days, unnoticed. And because these .meta files are generated by the MCP package's own NuGet installer, the next upgrade would have silently undone a hand-fix. So the fix ships with the gate. check-plugin-define-constraints.ps1 reads the bridge's package list from .nuget-installed.json rather than hardcoding DLL names, so a fourth bridge DLL is covered the day it lands. Mutation-proved, one fault, one mutant, one red: reverting McpPlugin.dll.meta turns it red naming that exact file. A first version of the gate was wrong in a way worth recording. It walked assembly references and failed any player-included plugin referencing an editor-only one -- 22 violations against a tree whose build succeeds. The BCL shims in that folder reference each other, and in a player those names resolve against Unity's own netstandard2.1 profile, not the excluded plugin copies (the same fact build-libs.ps1 records from the other direction). Separating a real break from a backfilled shim needs the Editor's playback-engine assembly set, which CI does not have. That gate would have blocked every PR on day one. The shipped one answers the narrower question truthfully, and its header says which question it declines.
…ds a body seam The row said the collider NAME would decide between two readings -- terrain or a building for real cover, the victim's own body for the mask-2049 reading. It is neither, because there was no occlusion to name. artifacts/lane-b/x20-occlusion-01, spawn pinned to slot 0, 3 of 3 clients exit 0: 240 trigger frames, every one occlusionHit[none-this-shot], occluded=0 presentFallbacks=0 resolved=6 hits=0, victim on 100 health at all 7 checkpoints. `none-this-shot` is the freshness path added on 2026-08-25, not a default -- the instrument worked and reported that the thing it was built to describe did not happen. This does NOT retroactively settle the baseline. x19-fixed-04 held at 10.1 m on an unpinned spawn; this run stands at 4.7 m pinned. Different geometry, so the question is retired rather than won, and both readings die for this run rather than one winning. What the run exposed instead. Everything upstream is now correct and the artifact says so: present.torso == frame.torso to seven digits, frame.tick == wanted.tick, aim.resolved on actor 42 at 4.65 m, nothing in the way. And the shot still misses. HitboxSet.FromSize stops torso AND arms at 1.550 and starts head at 1.580, so nothing covers 1.550..1.580 -- a 3 cm seam across a standing body. A ray through it returns Miss before the occlusion test runs, which is exactly why occluded stays 0. The logged ray arrives 1.5684 m above the target's feet, inside the seam. ScriptedTargetSolver raises BOTH endpoints by EYE_HEIGHT 1.6, which is 0.02 m inside the head box's lower edge rather than centre of mass -- so the aim point is 1.6 m at every range and the margin is two centimetres everywhere. Stated honestly, because the arithmetic has a limit: the seam is a fact of the source and needs no run. That THIS ray threaded it is derived from a log line whose aim is rounded to two decimals, and +/-0.005 in y is +/-0.024 m at 4.74 m -- wider than the seam. Consistent, not proven. Next measurement, one line: LagCompensator.Resolve already loops all four boxes; on a miss, record the nearest box and its signed miss distance instead of discarding them. The same move 3F.1 made for X-19 and #180 made for occlusion. Fix nothing before it exists -- widening a hitbox is a balance change, and guessing at 0.03 m is how a box stops matching the mesh. Filed rather than fixed, per phase-3 section 6: X-24 the 3 cm torso/head seam. A game defect: a human aiming at chest-to-chin height on a standing body gets the same nothing. X-25 the aim point. A harness defect, and the reason the seam is hit every time rather than occasionally -- it is why no lane-B combat run has ever scored a hit. Also recorded, not filed: the shooter held SL_DEFENDER (clip 8, cooldown 1.5 s) while both observers held RK44 (clip 30). Nothing in the programme selects a weapon, so the loadout follows the slot the server hands out and pinning the spawn changed which slot that was. The catalog is applied correctly -- 8-round clip and 1.5 s cadence to the value -- but two combat runs are not comparable shot-for-shot until the weapon is pinned too.
This page declined to require build-test because build-test was red, and build-test stayed red because nothing required anyone to look -- eleven merges onto a red develop. X-23 (#178) and #181 fixed the red, so the condition the page set for itself was met and the rule is now on. Done in the order section 1 asks for, with each step checked rather than assumed: the red fixed, build-test watched green once on develop at 76734c5, the two check names read back from GET /commits/{sha}/check-runs rather than copied off this page, and ci.yml confirmed to run on pull_request with no workflow-level paths filter -- the deadlock this page warns about twice. Added to the existing protect-shared-branches ruleset so main and develop stay one object rather than two that drift. Verified after the write by asking which rules apply to each ref rather than trusting the PUT: both shared branches return deletion, non_fast_forward, required_status_checks; a feature branch returns none. Two consequences recorded before they surprise someone. A direct push to main or develop is now refused -- a fresh commit has no checks and there is no bypass -- which suits a PR-only workflow and would block a hotfix pushed straight at the branch. And with no bypass actor, a CI outage blocks merging entirely; that is section 1's deliberate choice, not an oversight. Not proven, only configured, and the page says so -- the same caveat the force-push row already carries. Nobody has opened a PR with a FAILING build-test and watched the merge refuse. What is observed is the positive half. Two rules stay off, with reasons rather than silence. Required approvals would deadlock a single-owner repository, since GitHub forbids approving your own PR -- section 2's one-approval row was written for the four-person team. And strict (up-to-date) mode costs an update-and-re-run on every PR that lands after another, to prevent a failure that has not happened here; the eleven-merge streak was a branch nobody was required to look at, which is what this change fixes.
…it hid The plan called X-20 "the only thing between here and 3D — a RUN away, not an investigation away". The run happened. X-20 is closed and the arrow did not shorten: X-25 now occupies that position, and it is the same shape of work, one harness edit rather than an investigation. So `X-20 ──▶ 3D` becomes `X-25 ──▶ 3D`. X-24 (the 3 cm torso/head seam) is a game defect routed to phase 6 and deliberately NOT a blocker for 3D — X-25 stops the harness aiming into the seam, which is exactly why X-24 needs its own row rather than vanishing when the runs go green. Also corrects a claim this plan has been carrying: that the pinned re-run yields "3D's 11 verdicts". It does not. tools/lane-b/ holds combat-* and smoke and nothing else, and the combat programmes contain no grenade step and no vehicle step, so checks 4, 7, 9 and 12 are not exercised by a `-Set combat` run at all — check 7 also needs `-Sim typical`, which it does not pass. A combat run can grade checks 1, 2, 3, 5, 6 and 13. A vehicle programme set does not exist yet and is unbuilt work; AC-2 grades all eleven, so it belongs in 3D's scope rather than being rediscovered at verdict time.
Nghaiz
force-pushed
the
fix/mcp-plugin-editor-only-meta
branch
from
August 25, 2026 10:32
d343ff2 to
875db71
Compare
…h was derived The report claimed the shipping Linux dedicated server "was equally unbuildable". The mechanism is sound -- defineConstraints are evaluated per target, UNITY_EDITOR is false in every player, and these .meta files carry no Server-specific platform entry -- but no server build was attempted, so that sentence asserted as measured something only reasoned. What IS measured: the Windows player's linker input listed McpPlugin.dll and McpPlugin.Common.dll among its allowed assemblies and did not list R3. Reworded to separate the two, in the same shape the section 2 verdict already uses for the ray arithmetic. The fix does not change either way, which is why this is a wording correction and not a reason to run a Linux toolchain today.
Nghaiz
added a commit
that referenced
this pull request
Aug 25, 2026
…hat is not The page recorded require-status-check as "configured, not proven". PR #183 is a stronger datapoint than that and it should be written down: while build-test (windows-latest) was still running, mergeStateStatus was BLOCKED against mergeable: MERGEABLE, and it flipped to CLEAN the moment that job finished. The rule is load-bearing, not a setting that saved and did nothing. But it is a stronger datapoint, not the proof. What was observed is that a PENDING required check blocks. The case this rule exists for is a FAILING one, and GitHub could treat a concluded failure differently from a not-yet-reported check. Recording the distinction rather than rounding it up, because rounding it up is how a gate gets trusted for something it does not do -- the same failure mode as the force-push row two sections down, which is honest about never having been tested. Closed cheaply by noticing the next real CI failure; not worth manufacturing one.
Nghaiz
added a commit
that referenced
this pull request
Aug 25, 2026
…hat is not (#184) The page recorded require-status-check as "configured, not proven". PR #183 is a stronger datapoint than that and it should be written down: while build-test (windows-latest) was still running, mergeStateStatus was BLOCKED against mergeable: MERGEABLE, and it flipped to CLEAN the moment that job finished. The rule is load-bearing, not a setting that saved and did nothing. But it is a stronger datapoint, not the proof. What was observed is that a PENDING required check blocks. The case this rule exists for is a FAILING one, and GitHub could treat a concluded failure differently from a not-yet-reported check. Recording the distinction rather than rounding it up, because rounding it up is how a gate gets trusted for something it does not do -- the same failure mode as the force-push row two sections down, which is honest about never having been tested. Closed cheaply by noticing the next real CI failure; not worth manufacturing one.
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.
Two things the ledger was waiting on, and one that had to be cleared first.
X-20 — the collider name decides nothing, because nothing was blocked
The row said the collider NAME would separate the two readings: terrain or a building means real
cover, the victim's own body means the
-2049mask reading. It is neither.artifacts/lane-b/x20-occlusion-01, spawn pinned to slot 0, 3 of 3 clients exit 0, 7 of 7checkpoints each:
none-this-shotis the freshness path added by #180, not a default — the instrument worked andreported that the thing it was built to describe did not happen. Both readings die for this
run rather than one winning, and this does not retroactively settle the baseline:
x19-fixed-04held at 10.1 m unpinned, this run stands at 4.7 m pinned. Different geometry, so the question is
retired, not won.
What the run exposed instead. Everything upstream is now correct, and the artifact says so
rather than assuming it —
present.torso == frame.torsoto seven digits,frame.tick == wanted.tick,aim.resolvedon actor 42 at 4.65 m, nothing in the way. The shot still misses.HitboxSet.FromSizestops torso and arms at 1.550 and starts the head at 1.580, so nothingcovers
1.550..1.580— a 3 cm seam across a standing body. A ray through it returnsMissbefore the occlusion test runs, which is exactly why
occludedstays 0. The logged ray arrives1.5684 m above the target's feet: inside the seam.
The limit of that claim, stated rather than buried. The seam is a fact of the source. That
this ray threaded it is arithmetic over a log line whose
aimis rounded to two decimals, and±0.005 in y is ±0.024 m at 4.74 m — wider than the seam itself. Consistent, not proven. The next
measurement is one line in
LagCompensator.Resolve, which already loops all four boxes: on amiss, record the nearest box and its signed miss distance instead of discarding them. Nothing
should be fixed before it exists.
Filed rather than fixed, per phase-3 §6 — X-24 (the seam; a game defect, a human aiming at
chest-to-chin height gets the same nothing) and X-25 (the aim point;
ScriptedTargetSolverraises both endpoints by
EYE_HEIGHT, landing 0.02 m inside the head box's edge instead of centreof mass — which is why no lane-B combat run has ever scored a hit).
The blocker: no Unity player could be built at all
com.ivanmurzak.unity.mcp0.90.0 and its ten extension packages (#175) installedMcpPlugin.dlland
McpPlugin.Common.dllwithdefineConstraints: [], while every other DLL of the same closure— R3, ReflectorNet, SignalR,
Microsoft.CodeAnalysis— carried- UNITY_EDITOR.McpPlugin.dllreferences R3, so the player included the first, excluded the second, and UnityLinker died naming
R3 — the file that is correct — and never naming
McpPlugin.dll, the file that is wrong.Two
.metalines fix it: same tree, same command,Failure→Success.This was never only a compile problem.
defineConstraintsare evaluated per target andUNITY_EDITORis false in every player, so the shipping Linux dedicated server was equallyunbuildable — and a build that had linked would have carried an HTTP bridge hosting Roslyn
into the game server image, the exact thing
EditorBuild.StripEditorOnlyDefinesexists to prevent.Nothing in CI could see it: no job builds a Unity player,
images.ymldownloads a pre-built Linuxartifact from a release, and
build-testisdotnet buildover the pure-.NET libraries, whichnever reads
Assets/Plugins. Four days, unnoticed. And these.metafiles are generated by theMCP package's own NuGet installer, so the next upgrade would have silently undone a hand-fix.
So the fix ships with a gate.
tools/check-plugin-define-constraints.ps1reads the bridge'spackage list from
.nuget-installed.jsonrather than hardcoding DLL names, so a fourth bridge DLLis covered the day it lands. Mutation-proved — reverting
McpPlugin.dll.metaturns it rednaming that exact file; restoring it turns it green.
A first version of that gate is recorded in the report because it was wrong in a useful way: it
walked assembly references and reported 22 violations against a tree whose build succeeds. The
BCL shims in that folder reference each other, and in a player those names resolve against Unity's
own netstandard2.1 profile rather than the excluded plugin copies. Telling a real break from a
backfilled shim needs the Editor's playback-engine assembly set, which CI does not have. That gate
would have blocked every PR on its first day. The shipped one answers the narrower question
truthfully and says in its header which question it declines.
Branch protection — require-status-check is on
The page declined to require
build-testbecausebuild-testwas red, and it stayed redbecause nothing required anyone to look. X-23 (#178) and #181 cleared that, so the condition the
page set for itself was met.
Done in §1's order, each step checked: red fixed →
build-testwatched green once ondevelopat76734c5→ the two check names read back fromGET /commits/{sha}/check-runsrather than copiedoff the page →
ci.ymlconfirmed to run onpull_requestwith no workflow-levelpaths:filter,the deadlock that page warns about twice. Added to the existing
protect-shared-branchesruleset;verified afterwards by asking which rules apply to each ref rather than trusting the
PUT.Two consequences recorded before they surprise anyone: a direct push to
mainordevelopisnow refused, and with no bypass actor a CI outage blocks merging entirely (§1's deliberate
choice). Required approvals stay off — GitHub forbids approving your own PR, so on a
single-owner repository that would deadlock every merge; §2's one-approval row was written for the
four-person team.
This PR is the first thing the new rule applies to, which is also the first honest test of it.
Verification
programme completeBuild Finished, Result: Success(wasFailureon the same tree)check-plugin-define-constraints.ps1McpPlugin.dllcheck-unity-meta.ps1.metaUnityEngine.Random=20260821,NetworkSimulator=off/12345No C# changed, so the test suite is untouched by this PR.