Skip to content

build/qa: kit-scene contamination gate — auto-strip KitRoom_* roots at build + sandbox level-file scan#1690

Open
100yenadmin wants to merge 4 commits into
mainfrom
kit-strip-gate
Open

build/qa: kit-scene contamination gate — auto-strip KitRoom_* roots at build + sandbox level-file scan#1690
100yenadmin wants to merge 4 commits into
mainfrom
kit-strip-gate

Conversation

@100yenadmin

Copy link
Copy Markdown
Member

Root cause (the withheld tavern install)

The grey slabs over the new tavern plate were the kit-tavern QA scene embedded in the player build — `strings level0` of the sandbox app shows `KitRoom_tavern` + `table_a-d_fallback`, `bar_counter_fallback`, `hearth_fallback`, `candle_door/hearth_Brazier(+Base)`, `barrel_*_fallback`. A capture flow saved the canonical scene while the kit root existed; the build shipped it; the kit fallback boxes/plinths/parapets then render in front of the (clean, byte-identical) plate. Third occurrence of this class (kit-crypt cleankit trap ×2).

Killed hypotheses, each by measurement: plate/composite defect (plate clean + sha-identical repo↔app), hot-load manifest seam (entries structurally equivalent), visible occluder proxies (client skips on missing shader, ColorMask-0 otherwise; sidecar camera-side walls are 5.0u tall yet the frame shows low kit parapets). Owner's installed app: 0 KitRoom_ strings — unaffected.

The gate (kills the class)

  • BuildMacOSPlayer.Build(): StripQAConstructions() — opens the canonical scene EXPLICITLY (also killing the wrong-open-scene trap), destroys any `KitRoom_*` root, saves, and reports `strippedQARoots=...` in build-report.txt. Kit roots are pure QA constructions; BuildRoom recreates them on demand and is already idempotent.
  • qa_sandbox.up(): _qa_roots_in_app() — Mac-side belt-and-suspenders; byte-scans the app's level files for `KitRoom_*` and refuses to bring up a sandbox on a contaminated app (fail-loud before any gate spends time).

Detector proven both ways: contaminated LEXAR app → `{'KitRoom_tavern'}`; owner app → `set()`.

Refs #85 / #83 (nav-truth chain). Next: box scene cleanup + rebuild through this gate → sandbox walk gate → tavern owner install.

@100yenadmin
100yenadmin enabled auto-merge (squash) July 23, 2026 04:51
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Priority Level: P1

  • Build contamination detection may miss embedded QA roots. The sandbox scanner only searches Data/level* files and relies on byte-regex matching, so contaminated scenes stored in differently named or serialized resources may pass the gate and launch. This undermines the stated protection against kit-scene contamination. Location: qa/qa_sandbox.py, _qa_roots_in_app. Recommended fix: scan all relevant Unity level/scene asset files or validate the built scene manifest/resources using a format-aware parser, with tests covering the contamination locations produced by the build. Required before merge: Yes.

  • Build-time stripping mutates the canonical source scene. Build() opens and saves SceneToBuild after deleting KitRoom_* roots, permanently changing the checked-out Unity scene rather than producing a build-only copy. This can destroy QA construction content needed for later editor work and make builds non-reproducible. Location: extensions/renderers/unity/scripts/BuildMacOSPlayer.cs, StripQAConstructions. Recommended fix: copy or temporarily stage the scene for stripping, build from that sanitized copy, and preserve the canonical scene; add a regression test or explicit restoration path. Required before merge: Yes.

Walkthrough

The Unity pipeline exports kit bounds, registers crypt and tavern renderer datasets, validates per-object image alignment, strips QA roots from macOS builds, and rejects contaminated sandbox applications. Public build entry points remain unchanged.

Confidence: 95%

Changes

Unity kit rendering and QA validation

Layer / File(s) Summary
Export measured kit bounds
extensions/renderers/unity/scripts/build_room_kit.cs
Adds an Editor command that measures KitRoom_<roomId> geometry and writes camera metadata plus per-object bounds.
Register kit renderer datasets
extensions/renderers/unity/boxes/*, extensions/renderers/unity/plates_manifest.json
Adds crypt and tavern kit bounds and updates manifest asset, bounds, and provenance references.
Check per-object plate alignment
qa/object_align_check.py
Adds image normalization, projected object crops, FFT phase correlation, cell-budget evaluation, and CLI exit status handling.
Contain QA roots in builds and sandboxes
extensions/renderers/unity/scripts/BuildMacOSPlayer.cs, qa/qa_sandbox.py
Strips KitRoom_* roots during macOS builds, records them in the report, and rejects packaged apps containing detected roots.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant UnityKitExporter
  participant KitRoomScene
  participant RendererManifest
  participant AlignmentChecker
  participant BaseRender
  participant StyledRender
  UnityKitExporter->>KitRoomScene: measure KitRoom geometry
  UnityKitExporter->>RendererManifest: provide kit box dataset
  AlignmentChecker->>RendererManifest: load boxes and ortho
  AlignmentChecker->>BaseRender: crop projected object regions
  AlignmentChecker->>StyledRender: crop matching regions
  AlignmentChecker->>AlignmentChecker: phase-correlate and enforce cell budget
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning Confidence 93%: The PR description explains the root cause and fix, but it omits most required template sections like Summary, Linked Issue, Validation, and Evidence. Fill the template sections, especially Summary, Linked Issue, Changes, Validation, Release Notes, Safety/Rollback, Evidence, and Visual Evidence if applicable.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Confidence 97%: The title accurately summarizes the main change set—QA kit-scene contamination gates at build and sandbox levels.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch kit-strip-gate

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Checkov (3.3.8)
extensions/renderers/unity/boxes/crypt_kit_v1_boxes.json

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

extensions/renderers/unity/boxes/tavern_kit_v1_boxes.json

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

extensions/renderers/unity/plates_manifest.json

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'


Comment @coderabbitai help to get the list of available commands.

@evaos-code-review-bot

evaos-code-review-bot Bot commented Jul 23, 2026

Copy link
Copy Markdown

evaOS review status: completed

PR: #1690 - build/qa: kit-scene contamination gate — auto-strip KitRoom_* roots at build + sandbox level-file scan
Head: 0dedfa29440ed865fe45a642e384b007a482fd44
Updated: 2026-07-23T05:00:21.415Z

evaOS review completed for this PR head.

Automation note: agents should wait for this comment to reach completed, stale_head, closed_or_merged_before_review, skipped, or failed before treating evaOS review as settled for this head. provider_deferred means evaOS still intends to retry.

PR URL: #1690

Review URL: #1690 (review)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@qa/qa_sandbox.py`:
- Around line 142-149: Move the binary validation and _qa_roots_in_app
contamination check to the beginning of up(), before creating run metadata,
provisioning the engine, or spawning subprocesses. Reject contaminated or
invalid apps immediately, preserving the existing contamination error details
while removing the need to terminate an engine that has not started.
- Around line 98-99: Update the filtering return expression in the helper that
processes found room names so it always returns the set excluding names
beginning with KitRoom_Fire, KitRoom_TombGlow, or KitRoom_CoolKey; remove the
fallback to found and preserve the existing root-name filtering behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 42d7f6d8-a45b-4acb-8416-00b79768998d

📥 Commits

Reviewing files that changed from the base of the PR and between 0506073 and 0dedfa2.

📒 Files selected for processing (2)
  • extensions/renderers/unity/scripts/BuildMacOSPlayer.cs
  • qa/qa_sandbox.py
📜 Review details
⏰ Context from checks skipped due to timeout. (9)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (python)
  • GitHub Check: qa-release-gate-tests
  • GitHub Check: viewer-tests
  • GitHub Check: test
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (csharp)
  • GitHub Check: test
  • GitHub Check: viewer-tests
🧰 Additional context used
📓 Path-based instructions (1)
**/*

📄 CodeRabbit inference engine (AGENTS.md)

**/*: Treat /Users/lume/WorldOS as the canonical local Mac app/private-art checkout for WorldOS GUI and native-app testing.
Use /Volumes/LEXAR/Codex for Codex artifacts, scratch files, screenshots, reports, and downloaded CI/VM artifacts.
Before install, build, or test commands, verify pwd; GUI/native app runs must use /Users/lume/WorldOS or an approved same-disk worktree configuration.
Use the codex/ prefix for new branches unless instructed otherwise.

Files:

  • qa/qa_sandbox.py
  • extensions/renderers/unity/scripts/BuildMacOSPlayer.cs
🪛 Ruff (0.15.21)
qa/qa_sandbox.py

[warning] 96-97: try-except within a loop incurs performance overhead

(PERF203)


[warning] 145-149: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (1)
extensions/renderers/unity/scripts/BuildMacOSPlayer.cs (1)

191-194: LGTM!

Also applies to: 266-303

Comment thread qa/qa_sandbox.py
Comment on lines +98 to +99
# child helper objects (KitRoom_Fire etc.) ride along with a real root; the root name is the signal
return {n for n in found if not n.startswith(("KitRoom_Fire", "KitRoom_TombGlow", "KitRoom_CoolKey"))} or found

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not re-add filtered child helper names.

Line 99 returns found when every match is a filtered helper, so an otherwise clean app containing only KitRoom_Fire, KitRoom_TombGlow, or KitRoom_CoolKey is still rejected. Return the filtered set directly; a real KitRoom_* root will be present independently. Confidence: 99%.

Proposed fix
-    return {n for n in found if not n.startswith(("KitRoom_Fire", "KitRoom_TombGlow", "KitRoom_CoolKey"))} or found
+    return {n for n in found if not n.startswith(("KitRoom_Fire", "KitRoom_TombGlow", "KitRoom_CoolKey"))}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# child helper objects (KitRoom_Fire etc.) ride along with a real root; the root name is the signal
return {n for n in found if not n.startswith(("KitRoom_Fire", "KitRoom_TombGlow", "KitRoom_CoolKey"))} or found
# child helper objects (KitRoom_Fire etc.) ride along with a real root; the root name is the signal
return {n for n in found if not n.startswith(("KitRoom_Fire", "KitRoom_TombGlow", "KitRoom_CoolKey"))}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qa/qa_sandbox.py` around lines 98 - 99, Update the filtering return
expression in the helper that processes found room names so it always returns
the set excluding names beginning with KitRoom_Fire, KitRoom_TombGlow, or
KitRoom_CoolKey; remove the fallback to found and preserve the existing
root-name filtering behavior.

Comment thread qa/qa_sandbox.py
Comment on lines +142 to +149
contaminated = _qa_roots_in_app(app)
if contaminated:
engine.terminate()
raise SystemExit(
f"[sandbox] app CONTAMINATED — QA kit roots baked into the build: {sorted(contaminated)} "
f"({app}). A KitRoom_* scene root was saved into the canonical scene at build time; it "
f"renders grey kit masses over every plate (kit-tavern 2026-07-23). Rebuild via "
f"BuildMacOSPlayer (auto-strips + reports strippedQARoots in build-report.txt).")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Run the contamination gate before provisioning the engine.

The code path starts and waits for the engine at Lines 127-134 before checking contamination. A rejected app therefore still seeds state and launches an engine; terminate() is non-blocking and no run metadata exists to clean up a lingering process. Move the binary and contamination preflight to the start of up(), before creating the run directory or spawning subprocesses. Confidence: 98%.

🧰 Tools
🪛 Ruff (0.15.21)

[warning] 145-149: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qa/qa_sandbox.py` around lines 142 - 149, Move the binary validation and
_qa_roots_in_app contamination check to the beginning of up(), before creating
run metadata, provisioning the engine, or spawning subprocesses. Reject
contaminated or invalid apps immediately, preserving the existing contamination
error details while removing the need to terminate an engine that has not
started.

@evaos-code-review-bot evaos-code-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Walkthrough

PR: #1690 - build/qa: kit-scene contamination gate — auto-strip KitRoom_* roots at build + sandbox level-file scan
Head: 0dedfa29440ed865fe45a642e384b007a482fd44 into main. Review event: COMMENT.
Provider: GLM/Z.ai through ZCode (zcode-glm, zcode, model GLM-5.2).

Estimated review effort: 3/5 (~34 min)

Changed Files

File Status Churn Purpose Risk
extensions/renderers/unity/scripts/BuildMacOSPlayer.cs modified +35/-0 Unity/gameplay state Elevated: validated P1 finding
qa/qa_sandbox.py modified +28/-0 Changed file Elevated: validated P1 finding

Review Signal

Validated inline findings: 6 (P0: 0, P1: 2, P2: 2, P3: 2).
Dropped findings before posting: 0. High-severity findings: 2.

Risk Taxonomy

  • Data loss: 1
  • Flaky test risk: 1
  • Release regression: 2
  • Runtime correctness: 1
  • Unity scene/prefab: 1

Validation and Proof

1 required validation/proof recommendation(s) selected from changed files.

  • required: Unity editor or Play Mode smoke - Unity asset/script/project files changed. Proof: Unity editor smoke; Play Mode log; scene/prefab screenshot or recording.
    Proof status: missing - 1 required validation/proof recommendation(s) missing from PR metadata.
    Profile validation hints: Prefer correctness, persistence, CI, release, and regression findings over style-only feedback.
    Profile proof expectations: Look for Unity editor, play-mode, fixture, or focused smoke evidence when runtime behavior changes.

Related Context

Related issues/PRs: #85, #83.
Suggested labels: bug, unity.
Suggested reviewers: none from current metadata.

Review Settings Preview

  • Profile: assertive
  • Enabled sections: Review summary (inline_review); Walkthrough (inline_review); Changed-files table (walkthrough); Effort estimate (walkthrough); Related issues/PRs (walkthrough); Suggested labels (suggestion_only); Review status comment (sticky_status)
  • Path instructions: Assets/** - Prioritize scene, prefab, save-state, asset-reference, and gameplay regressions.
  • Path instructions: ProjectSettings/** - Treat build, platform, input, graphics, and release behavior changes as high risk.
  • Label suggestions: unity, gameplay, regression-hardening
  • Reviewer suggestions: none
  • Suggestion behavior: suggestions only; labels and reviewers are not auto-applied.
  • Roadmap-only settings: auto-apply labels; auto-request reviewers; required status checks

Pre-merge checklist

  • Inline comments target current RIGHT-side diff lines.
  • No secret-like content survived into posted inline comments.
  • REQUEST_CHANGES is only used when eligible P0/P1 findings survive validation.
  • Required behavior proof is present or not applicable.
  • Labels and reviewers are suggestions only; the bot did not auto-apply them.

}
if (stripped.Count > 0)
{
UnityEditor.SceneManagement.EditorSceneManager.SaveScene(scene);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: StripQAConstructions ignores SaveScene return value; a silent save failure ships contaminated art with strippedQARoots=(none)

EditorSceneManager.SaveScene returns bool and fails non-throwing on read-only files, VCS/OneDrive locks, disk pressure, or a scene path that resolves but is not writable. Here the bool is discarded, so after DestroyImmediate has already mutated the in-memory scene, a failed save (a) leaves the canonical M1CombatV1_canonical.unity dirty on disk as built and (b) still returns the stripped list, so build-report.txt records strippedQARoots= while the on-disk/built scene still contains the roots. The build then proceeds at line 250 against the un-stripped source. Wrap the save: if (!EditorSceneManager.SaveScene(scene)) { Debug.LogError("[BuildMacOSPlayer] SaveScene FAILED after stripping " + SceneToBuild + " — aborting to avoid shipping QA roots"); return; } (or throw) so a failed strip is a hard build stop, not a silent partial.

Category: Release regression

Why this matters: The entire value of this gate is that no KitRoom_* ships. A silent save failure produces a build-report that claims success while the player renders the grey kit masses the PR exists to prevent — the exact kit-tavern contamination, just with a misleading evidence trail.

Comment thread qa/qa_sandbox.py
except OSError:
continue
# child helper objects (KitRoom_Fire etc.) ride along with a real root; the root name is the signal
return {n for n in found if not n.startswith(("KitRoom_Fire", "KitRoom_TombGlow", "KitRoom_CoolKey"))} or found

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: or found fallback re-flags helper lights as contamination, blocking legitimate builds

When the only KitRoom_* byte matches in the level file are the surviving helper-light names (KitRoom_Fire/KitRoom_TombGlow/KitRoom_CoolKey), the set comprehension yields {} and the or found clause restores ALL of found, so qa_roots_in_app returns the helper names and the up path at line 143 raises SystemExit('CONTAMINATED'). These helper GameObjects are children of a KitRoom root (build_room_kit.cs:479-500 parents them to root/Lights), so the C# strip already removes them with the root; a level file can also legitimately carry them as orphaned lights that PurgeStrayKitLights left behind (build_room_kit.cs:844-853) without implying a grey-mass root is present. The detector should only fire on a real root: filter found to names NOT in the helper set, and if none remain, return {} (clean) rather than re-flagging the helpers.

Category: Runtime correctness

Why this matters: This is the sandbox contamination gate that blocks the QA lane. A false positive here stops legitimate sweeps with a CONTAMINATED error pointing at names that are not actually roots, and the operator's remediation instruction ('rebuild via BuildMacOSPlayer') will not change anything because there is no root to strip.

Comment thread qa/qa_sandbox.py
data_dir = app / "Contents" / "Resources" / "Data"
for lvl in sorted(data_dir.glob("level*")):
try:
found.update(m.decode() for m in re.findall(rb"KitRoom_[A-Za-z0-9_]+", lvl.read_bytes()))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Helper-name exclusion can mask a real KitRoom_ root (false negative on the contamination gate)

build_room_kit.cs:1113-1127 derives roomId from WORLDOS_KIT_ROOM_ID, the geo location, or the geometry filename. A location/file named e.g. "Fire", "TombGlow", or "CoolKey" produces a REAL root named KitRoom_Fire / KitRoom_TombGlow / KitRoom_CoolKey — exactly the three names this exclusion drops. For those roomIds the gate silently classifies the contaminated root as a helper and qa_roots_in_app returns {} (the comprehension drops it and the or found only triggers when helpers are the SOLE matches; here the real root IS one of those names so it is dropped, and if no other helpers exist the function returns clean). The exclusion should be structural (is the name a known light helper AND present as a Light component), not lexical. At minimum, do not hardcode three names that overlap the roomId namespace; instead detect roots by the absence of a Light component or by matching the KitRoom root convention via the prefab/mass children.

Category: Release regression

Why this matters: The gate's purpose is to never ship a kit root. A roomId collision with a hardcoded helper name is a realistic gap that lets a contaminated build pass the sandbox gate and ship.


static string[] StripQAConstructions()
{
var scene = UnityEditor.SceneManagement.EditorSceneManager.OpenScene(SceneToBuild);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: OpenScene(path) defaults to OpenSceneMode.Single and discards an unsaved current editor scene without a guard

EditorSceneManager.OpenScene(SceneToBuild) with no mode uses OpenSceneMode.Single. This Build() MenuItem runs in the headed editor (the box forbids -batchmode, per the class doc), so if the operator has an unsaved scene open when they invoke Tools/WorldOS/Build/macOS Player, Unity raises the 'Unsaved changes in scene' dialog and the build blocks until dismissed — or, depending on editor settings, silently discards the operator's unsaved work in the open scene. OccluderVerify.cs:41 and W5bWireScene.cs:35 both call OpenScene(path, OpenSceneMode.Single) explicitly; at minimum make the mode explicit here for parity. More importantly, there is no Force/Save check, so this Build entry point can clobber operator work in a different open scene. Consider OpenSceneMode.Single plus a documented precondition or a SaveCurrentModifiedScenesIfUserWantsTo prompt before opening.

Category: Data loss

Why this matters: Build is an operator-facing MenuItem invoked from the headed editor. An unexpected scene swap mid-build is a data-loss surface for whatever the operator was editing.

}
if (stripped.Count > 0)
{
UnityEditor.SceneManagement.EditorSceneManager.SaveScene(scene);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Strip persists into the tracked source asset as a build side-effect; no smoke/rollback note

SaveScene writes the strip back into Assets/Scenes/M1CombatV1_canonical.unity — a tracked source asset — as an irreversible side effect of Build(). After Build, the canonical scene on disk is permanently altered (the KitRoom_* roots are gone), and a subsequent git status will show the scene changed by a build run. The repo profile asks that scene changes carry explicit rollback and smoke notes; this PR's body should state the strip is destructive to the source scene and that BuildMacOSPlayer is the only recovery path (re-run build_room_kit to re-add, which is the opposite of the gate's intent). Consider writing a timestamped .unity.bak before SaveScene so an operator who ran Build against a scene they did not want stripped can recover without git surgery.

Category: Unity scene/prefab

Why this matters: A build mutating a tracked scene asset without a backup couples release packaging to source-tree mutation; an operator who triggers Build expecting packaging-only behavior silently commits a scene diff.

Comment thread qa/qa_sandbox.py
import re
found: set = set()
data_dir = app / "Contents" / "Resources" / "Data"
for lvl in sorted(data_dir.glob("level*")):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: level glob can match non-scene files; byte-scan over-reads arbitrary level-manager/shared files*

data_dir.glob('level*') matches level0, level1, ... but also any file beginning with 'level' that Unity may place under Resources/Data (e.g. shared level data or auxiliary 'level'-prefixed assets). The regex KitRoom_[A-Za-z0-9_]+ is applied to every match via read_bytes(), so a non-scene file that happens to contain the byte sequence 'KitRoom_' (e.g. a debug string, a prefab reference, a sharedAssets blob) would be flagged. The current strip already removes roots from the built scene, so this is low-impact, but tightening the glob to the real Unity scene level naming (e.g. glob('level*') restricted to files with no extension and a leading 'level' + digit) or to mainDataFile/scene-info would make the detector authoritative rather than heuristic.

Category: Flaky test risk

Why this matters: A heuristic byte scan over broadened file set increases false-positive surface on the gate that blocks the QA lane.

…felt-truth fix for owner-caught silhouette-everywhere (parapets 0.55u vs authored 5.0u walls, true table footprints)
@evaos-code-review-bot

evaos-code-review-bot Bot commented Jul 23, 2026

Copy link
Copy Markdown

evaOS review status: completed

PR: #1690 - build/qa: kit-scene contamination gate — auto-strip KitRoom_* roots at build + sandbox level-file scan
Head: 3e0104f96e524a6da08473d3da9480bceb5f729e
Updated: 2026-07-23T05:46:56.510Z

evaOS review completed for this PR head.

Automation note: agents should wait for this comment to reach completed, stale_head, closed_or_merged_before_review, skipped, or failed before treating evaOS review as settled for this head. provider_deferred means evaOS still intends to retry.

PR URL: #1690

Review URL: #1690 (review)

@evaos-code-review-bot evaos-code-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Walkthrough

PR: #1690 - build/qa: kit-scene contamination gate — auto-strip KitRoom_* roots at build + sandbox level-file scan
Head: 3e0104f96e524a6da08473d3da9480bceb5f729e into main. Review event: COMMENT.
Provider: GLM/Z.ai through ZCode (zcode-glm, zcode, model GLM-5.2).

Estimated review effort: 2/5 (~30 min)

Changed Files

File Status Churn Purpose Risk
extensions/renderers/unity/boxes/tavern_kit_v1_boxes.json added +64/-0 Changed file Low
extensions/renderers/unity/plates_manifest.json modified +2/-2 Changed file Moderate: validated P3 finding
extensions/renderers/unity/scripts/BuildMacOSPlayer.cs modified +35/-0 Unity/gameplay state Moderate: validated P2 finding
extensions/renderers/unity/scripts/build_room_kit.cs modified +72/-0 Unity/gameplay state Moderate: validated P3 finding
qa/qa_sandbox.py modified +28/-0 Changed file Moderate: validated P2 finding

Review Signal

Validated inline findings: 5 (P0: 0, P1: 0, P2: 3, P3: 2).
Dropped findings before posting: 0. High-severity findings: 0.

Risk Taxonomy

  • API compatibility: 1
  • Release regression: 2
  • Runtime correctness: 1
  • Unity scene/prefab: 1

Validation and Proof

1 required validation/proof recommendation(s) selected from changed files.

  • required: Unity editor or Play Mode smoke - Unity asset/script/project files changed. Proof: Unity editor smoke; Play Mode log; scene/prefab screenshot or recording.
    Proof status: missing - 1 required validation/proof recommendation(s) missing from PR metadata.
    Profile validation hints: Prefer correctness, persistence, CI, release, and regression findings over style-only feedback.
    Profile proof expectations: Look for Unity editor, play-mode, fixture, or focused smoke evidence when runtime behavior changes.

Related Context

Related issues/PRs: #85, #83.
Suggested labels: unity.
Suggested reviewers: none from current metadata.

Review Settings Preview

  • Profile: assertive
  • Enabled sections: Review summary (inline_review); Walkthrough (inline_review); Changed-files table (walkthrough); Effort estimate (walkthrough); Related issues/PRs (walkthrough); Suggested labels (suggestion_only); Review status comment (sticky_status)
  • Path instructions: Assets/** - Prioritize scene, prefab, save-state, asset-reference, and gameplay regressions.
  • Path instructions: ProjectSettings/** - Treat build, platform, input, graphics, and release behavior changes as high risk.
  • Label suggestions: unity, gameplay, regression-hardening
  • Reviewer suggestions: none
  • Suggestion behavior: suggestions only; labels and reviewers are not auto-applied.
  • Roadmap-only settings: auto-apply labels; auto-request reviewers; required status checks

Pre-merge checklist

  • Inline comments target current RIGHT-side diff lines.
  • No secret-like content survived into posted inline comments.
  • REQUEST_CHANGES is only used when eligible P0/P1 findings survive validation.
  • Required behavior proof is present or not applicable.
  • Labels and reviewers are suggestions only; the bot did not auto-apply them.


static string[] StripQAConstructions()
{
var scene = UnityEditor.SceneManagement.EditorSceneManager.OpenScene(SceneToBuild);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: StripQAConstructions opens the canonical scene in Single mode, dropping the editor's active scene

StripQAConstructions calls EditorSceneManager.OpenScene(SceneToBuild) with no mode argument, which defaults to OpenSceneMode.Single — it replaces the editor's currently active scene. If a developer invokes Tools/WorldOS/Build/macOS Player (Universal) while editing a different scene with unsaved work, the build silently closes that scene. Use OpenSceneMode.Additive for the strip pass (and remove the stripped roots + the added scene afterward), or document that the build must be run from a clean state. The comment claims this 'opens the canonical scene EXPLICITLY (also killing the wrong-open-scene trap)' but the cost is destroying unsaved editor state on every invocation that hits the build path.

Category: Runtime correctness

Why this matters: A build menu item that silently discards the developer's active scene and unsaved changes is a real workflow hazard on a headed-editor build flow (the BOX.md-forbidden -batchmode path means this runs interactively).

}
if (stripped.Count > 0)
{
UnityEditor.SceneManagement.EditorSceneManager.SaveScene(scene);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: StripQAConstructions persists a source-controlled scene file as a build side effect

When stripped.Count > 0 the method calls EditorSceneManager.SaveScene(scene), writing Assets/Scenes/M1CombatV1_canonical.unity — a source-controlled canonical asset — during a build. This can produce an unexpected diff in the scene file (Unity may rewrite metadata/object ordering) that gets committed by accident, and there is no rollback note in the PR despite the repo's high-risk-paths policy requiring explicit rollback notes for Assets/** changes. Prefer destroying the roots in-memory only (the BuildPlayer call at line 250 opens its own copy of the scene from disk for the actual build) and NOT saving the source scene — the build does not require the source .unity to be re-saved, since BuildPlayer serializes from the loaded scene state.

Category: Release regression

Why this matters: Silent mutation of a canonical scene asset during a build is exactly the class of release-regression risk the repo profile flags as high-risk; an unintended scene diff could ship gameplay-changing object state.

Comment thread qa/qa_sandbox.py
import re
found: set = set()
data_dir = app / "Contents" / "Resources" / "Data"
for lvl in sorted(data_dir.glob("level*")):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Contamination byte-scan only globs level and can miss sharedassets/resS scene data*

qa_roots_in_app globs app/Contents/Resources/Data/level* and scans those bytes for KitRoom. Modern Unity standalone builds also serialize scene hierarchy into sharedassets.assets and .resS split files; on some Unity versions GameObject names for the baked scene live there rather than in level*. If a contaminated build stored the KitRoom_ root name only in sharedassets, this detector returns an empty set and the gate falsely passes the contaminated app. The comment asserts the byte scan is 'reliable' but does not justify why level* is exhaustive. Scan all of Data/.assets and Data/.resS (or at minimum also glob sharedassets*) to close the false-negative gap.

Category: Release regression

Why this matters: The entire value of this PR's sandbox gate is catching the contaminated-build class that drew grey kit masses over every plate; a false-negative path means the regression ships silently, exactly what the gate exists to prevent.

}
sb.Append("\n ]\n}\n");
string projectRoot = Directory.GetParent(Application.dataPath).FullName;
string outPath = Path.Combine(projectRoot, "room_kit_boxes.json");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: ExportBoxes writes room_kit_boxes.json to project root, not the boxes/ dir the build packages

ExportBoxes writes its output to Path.Combine(projectRoot, "room_kit_boxes.json") — the project root, not /boxes/. The player packaging step (BuildMacOSPlayer.EnsurePackaged lines 98-104) copies boxes/*.json from /boxes/ into StreamingAssets, and plates_manifest.json references boxes/tavern_kit_v1_boxes.json. The exported file at project root is never picked up by packaging and has the wrong filename; a developer following the menu path produces a sidecar that silently does not ship. Write directly to /boxes/_kit_v1_boxes.json (matching the manifest reference and the packaging glob) so the export→ship chain is contiguous.

Category: Unity scene/prefab

Why this matters: The PR ships a tavern_kit_v1_boxes.json that the ExportBoxes menu item cannot produce at the path the build consumes, leaving the certified chain non-reproducible from the tooling alone.

"boxes": "boxes/tavern_v2_boxes.json",
"_provenance": "KIT-TAVERN v1 (2026-07-23, #83 replication proof): SAME certified chain as KIT-CRYPT v1 \u2014 geometry tavern_v2 -> kit scene (seg gate 100.00% FIRST TRY, 0/154) -> kit 3D depth -> flux base seed 12345 (recall 0.9737+, asset_G5UBNVwMchoG84wPSFiBEsAZ) -> Gemini 3.0 Pro edit c2 w/ FURNITURE-IDENTITY grounding (job_UTwT9WJkbvZpuveXDoMso8Eh) -> phase-corr ALIGNED dx=0 -> seg-mask void composite. Blind panel 7 vs control 8 (delta -1.0 in-band; c1 without furniture grounding was 6/-2.0 \u2014 the grounding clause is load-bearing). Collision truth UNCHANGED (tavern_v2 geometry/walkmask/boxes)."
"boxes": "boxes/tavern_kit_v1_boxes.json",
"_provenance": "KIT-TAVERN v1 (2026-07-23, #83 replication proof): SAME certified chain as KIT-CRYPT v1 \u2014 geometry tavern_v2 -> kit scene (seg gate 100.00% FIRST TRY, 0/154) -> kit 3D depth -> flux base seed 12345 (recall 0.9737+, asset_G5UBNVwMchoG84wPSFiBEsAZ) -> Gemini 3.0 Pro edit c2 w/ FURNITURE-IDENTITY grounding (job_UTwT9WJkbvZpuveXDoMso8Eh) -> phase-corr ALIGNED dx=0 -> seg-mask void composite. Blind panel 7 vs control 8 (delta -1.0 in-band; c1 without furniture grounding was 6/-2.0 \u2014 the grounding clause is load-bearing). Collision truth UNCHANGED (tavern_v2 geometry/walkmask/boxes). SIDECAR 2026-07-23: kit-derived boxes (ExportBoxes per-mass true bounds; cutaway parapets 0.55u, tables 1.4u true footprints) replacing the authored tavern_v2 chunky volumes \u2014 felt-truth fix for the owner-caught silhouette-everywhere class."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Provenance claims 'Collision truth UNCHANGED (tavern_v2 geometry/walkmask/boxes)' while the boxes entry is swapped

The _provenance string still asserts 'Collision truth UNCHANGED (tavern_v2 ... boxes)' but the immediately preceding line swaps boxes from boxes/tavern_v2_boxes.json to boxes/tavern_kit_v1_boxes.json. If the runtime consumes these boxes for anything beyond silhouette occluder proxies (e.g., any movement/collision hint), the statement is now self-contradictory. Clarify in the provenance that the swap affects occluder/silhouette sidecars only and that engine collision still reads tavern_v2 geometry/walkmask — or confirm no runtime path reads boxes for collision and note it.

Category: API compatibility

Why this matters: Self-contradictory provenance on a certified plate undermines the release-evidence trail the RRI contract depends on; a reviewer cannot tell whether collision behavior changed.

…able#51) + tavern v2 plate (surgical gemini re-add -> diff-mask composite, PER-OBJECT-ALIGNED 12/12)
@evaos-code-review-bot

evaos-code-review-bot Bot commented Jul 23, 2026

Copy link
Copy Markdown

evaOS review status: completed

PR: #1690 - build/qa: kit-scene contamination gate — auto-strip KitRoom_* roots at build + sandbox level-file scan
Head: 607a165572d0600c8320ecda3672b28a4531e42e
Updated: 2026-07-23T05:57:21.303Z

evaOS review completed for this PR head.

Automation note: agents should wait for this comment to reach completed, stale_head, closed_or_merged_before_review, skipped, or failed before treating evaOS review as settled for this head. provider_deferred means evaOS still intends to retry.

PR URL: #1690

Review URL: #1690 (review)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@extensions/renderers/unity/plates_manifest.json`:
- Line 52: The provenance entries at
extensions/renderers/unity/plates_manifest.json lines 52-52 and 82-82
incorrectly claim the collision boxes are unchanged. Update both entries to
state that engine geometry and walkmask collision remain unchanged while the
visual occluder sidecar now uses new kit-derived boxes; preserve the existing
provenance context.

In `@qa/object_align_check.py`:
- Around line 83-100: Update the object-check loop around box_screen_bbox and
the checked/failures counters so undersized eligible non-wall boxes cannot be
silently skipped. Mark them as failures unless the sidecar explicitly exempts
them, and report any explicit exemptions separately; ensure an all-uncheckable
set cannot produce PER-OBJECT-ALIGNED with exit 0.
- Around line 40-52: Update local_phasecorr and its callers to reject
low-variance inputs and correlation responses below a threshold calibrated from
known aligned fixtures, returning the established UNCHECKABLE failure state
rather than (0, 0, 0). Ensure the validation path around the result handling
near the referenced lines does not convert rejected featureless crops into
err_cells=0 or a passing gate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 21ce1f6c-2e11-4d3f-a2f8-5362f4da2108

📥 Commits

Reviewing files that changed from the base of the PR and between 0dedfa2 and 607a165.

⛔ Files ignored due to path filters (1)
  • extensions/renderers/unity/plates/tavern_kit_v2_registered.png is excluded by !**/*.png
📒 Files selected for processing (5)
  • extensions/renderers/unity/boxes/crypt_kit_v1_boxes.json
  • extensions/renderers/unity/boxes/tavern_kit_v1_boxes.json
  • extensions/renderers/unity/plates_manifest.json
  • extensions/renderers/unity/scripts/build_room_kit.cs
  • qa/object_align_check.py
📜 Review details
⏰ Context from checks skipped due to timeout. (9)
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: viewer-tests
  • GitHub Check: test
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (csharp)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: viewer-tests
  • GitHub Check: test
🧰 Additional context used
📓 Path-based instructions (1)
**/*

📄 CodeRabbit inference engine (AGENTS.md)

**/*: Treat /Users/lume/WorldOS as the canonical local Mac app/private-art checkout for WorldOS GUI and native-app testing.
Use /Volumes/LEXAR/Codex for Codex artifacts, scratch files, screenshots, reports, and downloaded CI/VM artifacts.
Before install, build, or test commands, verify pwd; GUI/native app runs must use /Users/lume/WorldOS or an approved same-disk worktree configuration.
Use the codex/ prefix for new branches unless instructed otherwise.

Files:

  • extensions/renderers/unity/boxes/tavern_kit_v1_boxes.json
  • extensions/renderers/unity/boxes/crypt_kit_v1_boxes.json
  • qa/object_align_check.py
  • extensions/renderers/unity/plates_manifest.json
  • extensions/renderers/unity/scripts/build_room_kit.cs
🧠 Learnings (1)
📚 Learning: 2026-07-16T03:59:05.631Z
Learnt from: 100yenadmin
Repo: electricsheephq/WorldOS PR: 1602
File: extensions/renderers/unity/boxes/tavern_snug_v1_boxes.json:42-55
Timestamp: 2026-07-16T03:59:05.631Z
Learning: For WorldOS Unity renderer sidecar box JSON files under extensions/renderers/unity/boxes/*_boxes.json, treat boxes labeled/typed `wall_run` and `door_frame` as occlusion-depth proxies only. Do not flag them as traversal/collision obstructions based solely on visible overlap with the wall-run/jamb doorway reveal geometry. Traversal and collision should be determined only by the engine-side walkmask/room geometry and the actual engine collision path; only raise an obstruction issue if there is evidence that the walkmask or engine collision indicates a blocked traversal.

Applied to files:

  • extensions/renderers/unity/boxes/tavern_kit_v1_boxes.json
  • extensions/renderers/unity/boxes/crypt_kit_v1_boxes.json
🪛 OpenGrep (1.25.0)
extensions/renderers/unity/scripts/build_room_kit.cs

[WARNING] 150-150: File operation with dynamic path can lead to path traversal. Validate and sanitize file paths against a safe base directory.

(coderabbit.path-traversal.csharp-file-read)

🪛 Ruff (0.15.21)
qa/object_align_check.py

[error] 41-41: Multiple statements on one line (semicolon)

(E702)


[error] 43-43: Multiple statements on one line (colon)

(E701)


[error] 44-44: Multiple statements on one line (colon)

(E701)


[error] 45-45: Multiple statements on one line (semicolon)

(E702)


[error] 46-46: Multiple statements on one line (semicolon)

(E702)


[error] 47-47: Multiple statements on one line (semicolon)

(E702)


[error] 50-50: Multiple statements on one line (colon)

(E701)


[error] 51-51: Multiple statements on one line (colon)

(E701)


[error] 56-56: Multiple statements on one line (semicolon)

(E702)


[error] 62-62: Multiple statements on one line (semicolon)

(E702)


[error] 63-63: Multiple statements on one line (semicolon)

(E702)


[error] 64-64: Multiple statements on one line (semicolon)

(E702)


[error] 70-70: Multiple statements on one line (semicolon)

(E702)


[error] 70-70: Multiple statements on one line (semicolon)

(E702)

🔇 Additional comments (1)
extensions/renderers/unity/scripts/build_room_kit.cs (1)

204-206: Existing packaging-path defect remains. ExportBoxes() writes <projectRoot>/room_kit_boxes.json, but the manifest consumes boxes/crypt_kit_v1_boxes.json / boxes/tavern_kit_v1_boxes.json. Re-running the menu action therefore cannot regenerate the packaged sidecars. Write to boxes/${roomId}_kit_v1_boxes.json instead. Confidence: 100%.

],
"_effects_comment": "Animated fire over the four painted brazier fires (geometry brazier cells; bowls sit ~1.2u up the stem). Tune scale/y at the sandbox eyeball if the flame floats or washes the paint.",
"_provenance": "KIT-CRYPT v1 (2026-07-23, #83/#84 nav-truth spike): 3D-FIRST CERTIFIED CHAIN \u2014 geometry crypt_v36 -> build_room_kit scene (footprint SEG GATE 100.00%, 0/192 disagreements, qa/evidence/kit-crypt/) -> kit 3D depth -> flux depth-CN base seed 12346 (recall 0.9752, asset_qM9R3tuRH4sceUrVjCoLdPRk) -> Gemini 3.0 Pro edit (model_google-gemini-pro-image-editing, critique-targeted detail prompt, job_Lmhssjh7iWnHsUKY1dhnHB4m) -> phase-corr ALIGNED dx=0,dy=0 vs base (qa/styled_align_check.py) -> geometric void composite (seg-frame mask). Blind panel 7 vs control 8 (delta -1.0, in-band; wf_89967920). Collision truth UNCHANGED (same geometry/walkmask/boxes as crypt_v36). NOTE: model_google-gemini-pro-image-editing pending model-registry allowlist (owner sign-off)."
"_provenance": "KIT-CRYPT v1 (2026-07-23, #83/#84 nav-truth spike): 3D-FIRST CERTIFIED CHAIN \u2014 geometry crypt_v36 -> build_room_kit scene (footprint SEG GATE 100.00%, 0/192 disagreements, qa/evidence/kit-crypt/) -> kit 3D depth -> flux depth-CN base seed 12346 (recall 0.9752, asset_qM9R3tuRH4sceUrVjCoLdPRk) -> Gemini 3.0 Pro edit (model_google-gemini-pro-image-editing, critique-targeted detail prompt, job_Lmhssjh7iWnHsUKY1dhnHB4m) -> phase-corr ALIGNED dx=0,dy=0 vs base (qa/styled_align_check.py) -> geometric void composite (seg-frame mask). Blind panel 7 vs control 8 (delta -1.0, in-band; wf_89967920). Collision truth UNCHANGED (same geometry/walkmask/boxes as crypt_v36). NOTE: model_google-gemini-pro-image-editing pending model-registry allowlist (owner sign-off). SIDECAR 2026-07-23: kit-derived boxes (ExportBoxes, 73 per-mass true bounds: parapets 0.55u, pillars/tomb/buttresses at real heights) replacing the authored crypt_v36 volumes -- silhouette bands now hug the paint."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Correct the stale “boxes unchanged” provenance. Both entries replace the occluder sidecar, so claiming the prior boxes are unchanged makes the release evidence self-contradictory. Preserve that engine geometry/walkmask collision is unchanged, but explicitly state that the visual occluder sidecar changed. Confidence: 100%.

  • extensions/renderers/unity/plates_manifest.json#L52-L52: replace same geometry/walkmask/boxes as crypt_v36 with wording that distinguishes unchanged geometry/walkmask from the new kit-derived boxes.
  • extensions/renderers/unity/plates_manifest.json#L82-L82: replace tavern_v2 geometry/walkmask/boxes with the same distinction.
📍 Affects 1 file
  • extensions/renderers/unity/plates_manifest.json#L52-L52 (this comment)
  • extensions/renderers/unity/plates_manifest.json#L82-L82
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/renderers/unity/plates_manifest.json` at line 52, The provenance
entries at extensions/renderers/unity/plates_manifest.json lines 52-52 and 82-82
incorrectly claim the collision boxes are unchanged. Update both entries to
state that engine geometry and walkmask collision remain unchanged while the
visual occluder sidecar now uses new kit-derived boxes; preserve the existing
provenance context.

Comment thread qa/object_align_check.py
Comment on lines +40 to +52
def local_phasecorr(a, b):
a = a - a.mean(); b = b - b.mean()
sa, sb = a.std(), b.std()
if sa > 0: a = a / sa
if sb > 0: b = b / sb
wy = np.hanning(a.shape[0])[:, None]; wx = np.hanning(a.shape[1])[None, :]
A = np.fft.rfft2(a * wy * wx); B = np.fft.rfft2(b * wy * wx)
R = A * np.conj(B); R /= np.abs(R) + 1e-9
r = np.fft.irfft2(R, a.shape)
dy, dx = np.unravel_index(np.argmax(r), r.shape)
if dy > a.shape[0] // 2: dy -= a.shape[0]
if dx > a.shape[1] // 2: dx -= a.shape[1]
return int(dx), int(dy), float(r.max())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject featureless correlation results instead of passing them as zero drift (Confidence: 99%).

For constant/blank crops, mean-centering makes both spectra zero; argmax then returns (0, 0) with resp=0. Line 90 returns that result and Line 92 passes it as err_cells=0. Missing, fully occluded, or unrendered objects can therefore produce a green gate. Treat low-variance and low-response results as UNCHECKABLE failures, with a response threshold calibrated from known aligned fixtures.

Also applies to: 90-92

🧰 Tools
🪛 Ruff (0.15.21)

[error] 41-41: Multiple statements on one line (semicolon)

(E702)


[error] 43-43: Multiple statements on one line (colon)

(E701)


[error] 44-44: Multiple statements on one line (colon)

(E701)


[error] 45-45: Multiple statements on one line (semicolon)

(E702)


[error] 46-46: Multiple statements on one line (semicolon)

(E702)


[error] 47-47: Multiple statements on one line (semicolon)

(E702)


[error] 50-50: Multiple statements on one line (colon)

(E701)


[error] 51-51: Multiple statements on one line (colon)

(E701)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qa/object_align_check.py` around lines 40 - 52, Update local_phasecorr and
its callers to reject low-variance inputs and correlation responses below a
threshold calibrated from known aligned fixtures, returning the established
UNCHECKABLE failure state rather than (0, 0, 0). Ensure the validation path
around the result handling near the referenced lines does not convert rejected
featureless crops into err_cells=0 or a passing gate.

Comment thread qa/object_align_check.py
Comment on lines +83 to +100
for i, box in enumerate(sidecar["boxes"]):
kind = str(box.get("kind", "?")).lower()
if kind in SKIP_KINDS:
continue
x0, y0, x1, y1 = box_screen_bbox(box, ortho, args.pad)
if x1 - x0 < 48 or y1 - y0 < 48:
continue # too small for a stable local spectrum
dx, dy, resp = local_phasecorr(styled[y0:y1, x0:x1], base[y0:y1, x0:x1])
err_cells = math.hypot(dx, dy) / px_per_cell
ok = err_cells <= args.budget_cells
checked += 1
if not ok:
failures += 1
print(f" {kind}#{i} @({box['center'][0]:g},{box['center'][2]:g}) "
f"dx={dx} dy={dy} resp={resp:.3f} err_cells={err_cells:.2f} -> {'OK' if ok else 'DRIFTED'}")
verdict = "PER-OBJECT-ALIGNED" if failures == 0 else f"OBJECT-DRIFT ({failures}/{checked} out of budget)"
print(f"{verdict} budget={args.budget_cells} cells, px/cell={px_per_cell:.1f}")
sys.exit(0 if failures == 0 else 1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not silently exempt non-wall objects from the gate (Confidence: 98%).

Line 89 skips undersized projected boxes before either counter changes. If every eligible box is small, Line 98 prints PER-OBJECT-ALIGNED and exits 0 despite checking nothing. Fail uncheckable non-wall boxes, or require an explicit sidecar exemption and report it separately.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qa/object_align_check.py` around lines 83 - 100, Update the object-check loop
around box_screen_bbox and the checked/failures counters so undersized eligible
non-wall boxes cannot be silently skipped. Mark them as failures unless the
sidecar explicitly exempts them, and report any explicit exemptions separately;
ensure an all-uncheckable set cannot produce PER-OBJECT-ALIGNED with exit 0.

@evaos-code-review-bot evaos-code-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Walkthrough

PR: #1690 - build/qa: kit-scene contamination gate — auto-strip KitRoom_* roots at build + sandbox level-file scan
Head: 607a165572d0600c8320ecda3672b28a4531e42e into main. Review event: COMMENT.
Provider: GLM/Z.ai through ZCode (zcode-glm, zcode, model GLM-5.2).

Estimated review effort: 4/5 (~56 min)

Changed Files

File Status Churn Purpose Risk
extensions/renderers/unity/boxes/crypt_kit_v1_boxes.json added +81/-0 Changed file Low
extensions/renderers/unity/boxes/tavern_kit_v1_boxes.json added +64/-0 Changed file Low
extensions/renderers/unity/plates/tavern_kit_v2_registered.png added +0/-0 Changed file Low
extensions/renderers/unity/plates_manifest.json modified +5/-5 Changed file Low
extensions/renderers/unity/scripts/BuildMacOSPlayer.cs modified +35/-0 Unity/gameplay state Moderate: validated P3 finding
extensions/renderers/unity/scripts/build_room_kit.cs modified +72/-0 Unity/gameplay state Moderate: validated P3 finding
qa/object_align_check.py added +104/-0 Changed file Moderate: validated P3 finding
qa/qa_sandbox.py modified +28/-0 Changed file Moderate: validated P3 finding

Review Signal

Validated inline findings: 4 (P0: 0, P1: 0, P2: 0, P3: 4).
Dropped findings before posting: 0. High-severity findings: 0.

Risk Taxonomy

  • Data loss: 1
  • Flaky test risk: 1
  • Release regression: 1
  • Unity scene/prefab: 1

Validation and Proof

1 required validation/proof recommendation(s) selected from changed files.

  • required: Unity editor or Play Mode smoke - Unity asset/script/project files changed. Proof: Unity editor smoke; Play Mode log; scene/prefab screenshot or recording.
    Proof status: missing - 1 required validation/proof recommendation(s) missing from PR metadata.
    Profile validation hints: Prefer correctness, persistence, CI, release, and regression findings over style-only feedback.
    Profile proof expectations: Look for Unity editor, play-mode, fixture, or focused smoke evidence when runtime behavior changes.

Related Context

Related issues/PRs: #85, #83.
Suggested labels: unity.
Suggested reviewers: none from current metadata.

Review Settings Preview

  • Profile: assertive
  • Enabled sections: Review summary (inline_review); Walkthrough (inline_review); Changed-files table (walkthrough); Effort estimate (walkthrough); Related issues/PRs (walkthrough); Suggested labels (suggestion_only); Review status comment (sticky_status)
  • Path instructions: Assets/** - Prioritize scene, prefab, save-state, asset-reference, and gameplay regressions.
  • Path instructions: ProjectSettings/** - Treat build, platform, input, graphics, and release behavior changes as high risk.
  • Label suggestions: unity, gameplay, regression-hardening
  • Reviewer suggestions: none
  • Suggestion behavior: suggestions only; labels and reviewers are not auto-applied.
  • Roadmap-only settings: auto-apply labels; auto-request reviewers; required status checks

Pre-merge checklist

  • Inline comments target current RIGHT-side diff lines.
  • No secret-like content survived into posted inline comments.
  • REQUEST_CHANGES is only used when eligible P0/P1 findings survive validation.
  • Required behavior proof is present or not applicable.
  • Labels and reviewers are suggestions only; the bot did not auto-apply them.

}
if (stripped.Count > 0)
{
UnityEditor.SceneManagement.EditorSceneManager.SaveScene(scene);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: StripQAConstructions persists a destructive SaveScene on the canonical scene asset at build time

StripQAConstructions() opens Assets/Scenes/M1CombatV1_canonical.unity, DestroyImmediate's every root whose name starts with "KitRoom_", then unconditionally calls EditorSceneManager.SaveScene(scene). This mutates the tracked canonical .unity source asset as a side effect of running the build menu item, and the strip predicate is a bare prefix match (QARootPrefix = "KitRoom_") with no allowlist. Today the kit lighting helpers (KitRoom_Fire/KitRoom_TombGlow/KitRoom_CoolKey in build_room_kit.cs:554/561/567) are nested children of the room root, so they are not root objects and survive — but if any future legitimate GameObject is ever placed at the scene ROOT with a KitRoom_ name, this build step will silently destroy it AND commit that deletion to source. The qa_sandbox.py detector (line 99) already maintains a whitelist for those helper prefixes; the stripper does not, so the gate and the stripper are asymmetric. Consider (a) narrowing the strip to the exact contaminant pattern (e.g. KitRoom_) or mirroring the qa_sandbox whitelist, and (b) only saving when a strip actually occurred is already done — but add a one-line guard/comment that this intentionally rewrites the source scene, so the destructive write is not mistaken for a build-output artifact.

Category: Data loss

Why this matters: A build step that silently rewrites the canonical gameplay scene asset is a release-regression/data-loss surface: a contaminant-removal intended for build output instead commits a structural edit to the source-of-truth scene, and the broad prefix makes future false-positive destruction likely.

}
sb.Append("\n ]\n}\n");
string projectRoot = Directory.GetParent(Application.dataPath).FullName;
string outPath = Path.Combine(projectRoot, "room_kit_boxes.json");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: ExportBoxes writes room_kit_boxes.json to project root, not the boxes/ dir the manifest consumes

ExportBoxes() writes its output to Path.Combine(projectRoot, "room_kit_boxes.json") (project root), but plates_manifest.json references boxes/crypt_kit_v1_boxes.json and boxes/tavern_kit_v1_boxes.json, and the committed artifacts under extensions/renderers/unity/boxes/ are named per-room (crypt_kit_v1_boxes.json / tavern_kit_v1_boxes.json). There is no automated copy/rename step and no CI gate tying the ExportBoxes output to the committed sidecars, so the menu-produced file and the manifest-consumed artifacts can silently drift — a future re-export could be missed or hand-edited divergently. The provenance strings in the committed JSON claim "kit-derived (build_room_kit ExportBoxes)" but nothing enforces that the committed file equals an ExportBoxes run for the current geometry. Consider writing directly into boxes/_kit_v1_boxes.json (parametrized by roomId) or adding a deterministic copy step, plus a lint that re-derives and diffs.

Category: Unity scene/prefab

Why this matters: The occluder sidecar drives the walk-behind silhouette; if the committed boxes drift from what ExportBoxes produces for the current geometry, the felt-truth fix this PR ships can silently regress on the next kit rebuild with no signal.

Comment thread qa/object_align_check.py


if __name__ == "__main__":
main()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Per-object alignment gate is not wired to CI and ships no committed evidence

object_align_check.py is added as a manual instrument (sys.exit 0/1) but nothing in this diff invokes it from CI, a pre-merge gate, or a makefile target, and no evidence file (e.g. qa/evidence/...) is committed alongside it. The tavern _provenance in plates_manifest.json asserts "PER-OBJECT-ALIGNED 12/12 (table#51 0.13 cells)" as load-bearing proof for swapping the plate and boxes, but that claim is only reproducible by a human re-running the script by hand with the right base/styled PNGs. If the gate is intended to be a real release-readiness signal, it needs deterministic wiring (inputs pinned, threshold fixed at --budget-cells 0.35) and captured output; otherwise the claim is unenforced and the table#51 regression class could return undetected.

Category: Release regression

Why this matters: The PR's whole premise is that global phase-corr missed a per-object drift (table#51 deletion); shipping the detector but not enforcing it leaves the exact regression class it was built to catch unguarded.

Comment thread qa/qa_sandbox.py
except OSError:
continue
# child helper objects (KitRoom_Fire etc.) ride along with a real root; the root name is the signal
return {n for n in found if not n.startswith(("KitRoom_Fire", "KitRoom_TombGlow", "KitRoom_CoolKey"))} or found

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: QA-root detector allowlist is a hardcoded prefix tuple that can drift from build_room_kit helper names

qa_roots_in_app excludes KitRoom_Fire, KitRoom_TombGlow, KitRoom_CoolKey via a literal startswith tuple, while those names are defined in build_room_kit.cs (lines 554/561/567). The two lists are maintained in different files/languages with no shared source of truth. If a new KitRoom* nested helper is added in build_room_kit.cs (e.g. a KitRoom_Fog light), the sandbox detector will start flagging clean builds as CONTAMINATED and abort the sandbox up() flow (lines 142-149) — a false-positive CI/sandbox break with no build change. Consider deriving the allowlist from the same place the helper names are authored, or making the detector hierarchy-aware (only flag true root objects) rather than name-based, so it does not need a parallel allowlist at all.

Category: Flaky test risk

Why this matters: A detector that aborts the sandbox on a substring match needs to stay exactly in sync with the C# that emits those names; a drift silently turns clean builds into hard sandbox failures.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 607a165572

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread qa/object_align_check.py
continue # too small for a stable local spectrum
dx, dy, resp = local_phasecorr(styled[y0:y1, x0:x1], base[y0:y1, x0:x1])
err_cells = math.hypot(dx, dy) / px_per_cell
ok = err_cells <= args.budget_cells

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject low-confidence object matches

When a styled crop has no usable signal (for example, the style pass paints a checked table/hearth into a flat floor patch, or the wrong base/styled image is supplied), local_phasecorr can return (dx, dy) = (0, 0) with resp near zero because there is no meaningful correlation peak, but this line treats it as OK solely by offset. That lets the new per-object gate falsely certify missing or deleted objects—the exact result-corruption class this check is supposed to catch—so the pass condition should also fail on low response/variance rather than only err_cells.

Useful? React with 👍 / 👎.

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