Skip to content

fix(scenes): one engine-parity key classifier for every scene walker (#651, #652) - #798

Merged
apotema merged 3 commits into
mainfrom
fix/scene-key-classification-651-652
Sep 29, 2026
Merged

apotema merged 3 commits into
mainfrom
fix/scene-key-classification-651-652

Conversation

@apotema

@apotema apotema commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #651. Fixes #652.

What changed

All scene-key classification now goes through src/scene_keys.zig, byte-parity with labelle-engine src/jsonc/unified_format.zig (isPascalCase / isComponentKeyShape / isTargetKey / isFlatComponentKey) and with the engine JSONC parser's escape set (jsonc/src/parser.zig parseString). The duplicated classifiers in pack_refs/common.zig, scene_name_lint.zig and scene_manifest.zig are gone. Those files now delegate to it.

#652: flat pack-namespaced keys (engine#806)

<prefix>__<Pascal> at entity scope is a component key, exactly where the engine accepts it (non-empty prefix, PascalCase after the last __):

  • scene_manifest.isAllowedTopLevelKey accepts rooms__Room at a file's top level. capacity__oops, __Room and rooms_Room are still UnknownSceneKey.
  • hasFlatEntityShapeKey / checkHybridForm count namespaced keys: namespaced + wrapper is HybridForm, and a namespaced-only file is flat form.
  • scene_name_lint.collectComponentRefs collects flat namespaced refs at entity scope.
  • pack_refs pass 1 moves flat namespaced pairs into the synthesized wrapper. Before this, they stayed flat beside it, and the engine then warns "wrapper wins" and drops them.

#651: escaped key spellings

When I checked the premise against the engine, it didn't hold: the engine's JSONC parser does not decode \uXXXX. It fails the whole file with error.InvalidEscape. I checked every revision of jsonc/src/parser.zig and ran a probe against current main. The escapes it does accept (\n \t \r \b \f \ \" \/) all decode to bytes that aren't A-Z, @ or _ and don't appear in any structural key name. So a raw key span classifies exactly like the engine's decoded key. The module doc spells out why, and an exhaustive test checks it: every accepted escape at every position of a set of base keys, compared against a reference decode.

The escape rules are now shared, so:

sourceUsesTargetKeys / findTargetKeyUsage* move to src/scene_target_gate.zig (re-exported from scene_manifest), which keeps scene_manifest.zig under 1000 lines (it was at 1092).

Tests

src/scene_keys_test.zig (wired in root.zig) is table-driven:

  • engine-parity table for the decoded rules;
  • escaped spellings classify the same as their decoded key (table + exhaustive);
  • \u spellings are .unloadable, and decode returns InvalidEscape for them, as the engine does;
  • per walker: manifest top-level accept/reject table, hybrid/flat detection, the lint's ref table, the @ gate, and the pack-rewrite "which flat keys move into the wrapper" table (the output shape shows which path ran).

Locally (Windows, Zig 0.16.0): zig build exit 0, and zig fmt --check on the touched files exit 0. zig build test exits 1 only on 3 generate-based tests that fail with REPARSE_POINT_NOT_RESOLVED (Windows junctions in .zig-cache/tmp); they fail the same way on origin/main here. All the new and changed tests pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Caqyi5Snr6yKssztYFztzZ


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Summary by CodeRabbit

  • New Features
    • Scene and prefab processing now recognizes flat, pack-namespaced component keys and handles them in synthesized wrappers.
    • Added engine-version checks for scenes using target overrides, with support determined from the configured engine feature set.
  • Bug Fixes
    • Scene parsing now rejects unsupported string escapes with a location diagnostic.
    • Target-key detection no longer treats escaped \u0040 spellings as valid target keys.

…651, #652)

Every textual scene walker (pack rewrite pass 1/2, scene_name_lint, the @
version gate) and the scene manifest now classify keys through one module,
src/scene_keys.zig, byte-parity with labelle-engine's unified_format.zig and
its JSONC parser's escape set.

#652: flat-form pack-namespaced `<prefix>__<Pascal>` keys (engine#806) are
component keys. They are accepted at a scene's top level, count toward
flat-form / hybrid detection, are collected by the name lint, and ride into
pass 1's synthesized wrapper instead of being left flat beside it.

#651: the engine's JSONC parser does not decode \uXXXX; it fails the whole
file with InvalidEscape. Every escape it does accept decodes to a byte that
can't change a key's classification, so raw spans classify exactly like the
engine's decoded keys. Keys with a rejected escape are inert in the walkers,
the old raw-byte special case for the escaped-@ spelling (which assumed
the engine decodes it) is
gone, and parseSceneSource rejects such files at build time, before std.json
(which does decode \u) gives them a meaning the engine never will.

The @ version gate moves to scene_target_gate.zig (re-exported) to keep
scene_manifest.zig under 1000 lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Caqyi5Snr6yKssztYFztzZ
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-29T00:23:57.274678Z d025575 Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Warning

Review limit reached

Next included review available in 25 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available. Your 101 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: db05b90f-fa9c-40f7-ba13-01065511d856

📥 Commits

Reviewing files that changed from the base of the PR and between da31429 and d025575.

📒 Files selected for processing (5)
  • src/root.zig
  • src/scene_keys_test.zig
  • src/scene_manifest.zig
  • src/scene_name_lint.zig
  • src/scene_target_gate.zig

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: b9884199-5afa-4c4c-9873-2de7df64ee27

📥 Commits

Reviewing files that changed from the base of the PR and between 3279202 and da31429.

📒 Files selected for processing (10)
  • src/codegen/scan/pack_refs.zig
  • src/codegen/scan/pack_refs/common.zig
  • src/codegen/scan/pack_refs/pass1.zig
  • src/root.zig
  • src/scene_keys.zig
  • src/scene_keys_test.zig
  • src/scene_manifest.zig
  • src/scene_manifest_test.zig
  • src/scene_name_lint.zig
  • src/scene_target_gate.zig
💤 Files with no reviewable changes (2)
  • src/codegen/scan/pack_refs.zig
  • src/scene_manifest_test.zig

Included review availability: This review used your included allowance. 0 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 1 review per hour.


📝 Walkthrough

Walkthrough

This change adds shared scene-key classification and escape validation. Manifest checks, name linting, and pack-reference rewriting use the shared rules. Target-key detection, engine-version checks, and JSONC file scans are provided through scene_target_gate.zig.

Changes

Scene key handling

Layer / File(s) Summary
Shared key classification and escape rules
src/scene_keys.zig, src/scene_keys_test.zig, src/root.zig
Adds decoded and raw classifiers for component-shaped and @ target keys. The decoder accepts the engine’s supported escape set; the scanner identifies unsupported escapes in strings while ignoring comments. Tests cover classification, decoding, and scanning.
Manifest key validation
src/scene_manifest.zig, src/scene_keys_test.zig
Manifest form checks use the shared classifier, including for flat namespaced component keys. Parsing reports and rejects unsupported string escapes. Tests cover accepted and rejected key shapes, hybrid forms, and escape handling.
Name lint and pack-reference handling
src/codegen/scan/pack_refs/common.zig, src/codegen/scan/pack_refs/pass1.zig, src/codegen/scan/pack_refs.zig, src/scene_name_lint.zig, src/scene_keys_test.zig
Name linting collects flat namespaced component references. Pack-reference rewriting moves flat namespaced pairs into synthesized wrappers. The tests for JSON-escaped @ keys were removed; replacement tests cover supported escapes and unloadable \u keys.
Target-key engine gate and file scanning
src/scene_manifest.zig, src/scene_manifest_test.zig, src/scene_target_gate.zig
Moves target-key detection and engine-version checks into scene_target_gate.zig, while re-exporting the existing public names. Adds named-file and recursive .jsonc scans. The test for JSON-escaped @ keys was removed.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Low

Merge Risk: ⚪ Minimal · up to da314

No actionable issue is established that would block merging. Engine-parser escape parity has not been independently confirmed.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: centralizing scene-key classification for all scene walkers. It is specific, concise, and related to the pull request objectives.
Linked Issues check ✅ Passed The PR satisfies #651 and #652. scene_keys.zig centralizes target and component-shape classification. It treats accepted escapes with engine-equivalent classification and marks rejected escapes as `…
Out of Scope Changes check ✅ Passed The changes stay within the linked issue scope. scene_target_gate.zig consolidates the target-key gate needed for #651, and its re-export preserves the existing interface. The shared module, visibil…
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

A rabbit checks each key at dawn
And marks the escapes the parser will not draw
Flat names find their wrappers in the scene
The target paths are scanned between
Then hops away through fields of green

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

@apotema

apotema commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@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: da31429af9

ℹ️ 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 src/scene_manifest.zig
// loader warn-onces unknown component names at runtime; the scan
// can't see the component registry here, so any component-SHAPED key
// is accepted (the audit's option C resolution).
if (isFlatComponentKey(key)) return true;

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 Badge Gate flat namespaced keys for pre-#806 engines

When a project pins an engine revision predating engine#806, this now accepts a game-root key such as rooms__Room, but that engine still classifies the lowercase-leading key as structural and silently drops the component. Only copied pack prefabs go through the compatibility wrapper in pack_refs; game-root scenes remain flat, and the repository has no corresponding #806 engine-version gate. Preserve the previous rejection for old pins, add a feature gate like the @ override gate, or normalize these scenes before staging.

Useful? React with 👍 / 👎.

Comment thread src/scene_manifest.zig
Comment on lines +715 to +721
if (scene_keys.firstInvalidEscape(source)) |off| {
const loc = @import("scene_name_lint.zig").locOf(source, off);
stderrPrint(
"labelle-assembler: scene '{s}' line {d} col {d}: unsupported string escape. The engine's JSONC parser accepts only \\n \\t \\r \\b \\f \\\\ \\\" \\/ (no \\uXXXX) and would fail to load this file.\n",
.{ display_path, loc.line, loc.col },
);
return error.InvalidSceneJson;

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 Apply the invalid-escape check to prefab scanners

This validation only runs through parseSceneSource, while game-root and staged pack prefabs are read through scanTilemapAssets, which still feeds them directly to std.json. Consequently, a prefab key such as "\u0054ilemap" is decoded as Tilemap and can fabricate a tilemap registration or fail generation with TilemapAssetNotFound, even though the engine rejects the entire prefab as InvalidEscape. Run the shared escape check in the raw prefab parsing path as well, rather than allowing different meanings for the same source depending on which scanner consumes it.

Useful? React with 👍 / 👎.

…e files in std.json scanners

Codex on #798:
- P1: engines before v2.11.0 (engine#806) silently drop flat
  `<prefix>__<Pascal>` keys at entity scope. The existing `@` version gate
  (same v2.11.0 minimum) now also catches them via sourceNeedsV211Keys.
  Wrapped namespaced keys, which old engines always loaded, don't trip it.
- P2: scanTilemapAssets / scanScenePrefabRefs feed std.json, which decodes
  escapes the engine rejects; a file the engine can't load now yields no
  tilemaps / prefab refs instead of made-up ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Caqyi5Snr6yKssztYFztzZ
@apotema

apotema commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@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: 0f60047ebd

ℹ️ 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 src/scene_target_gate.zig Outdated
continue;
},
};
const used = sourceNeedsV211Keys(source);

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 Gate normalized pack prefabs after rewriting

When an engine older than 2.11 is pinned, this scans each pack's original source and rejects any flat namespaced key before rewritePackPrefabRefs runs. A pack prefab such as { "Worker": {}, "rooms__Room": {} }, where Worker is pack-local, is subsequently normalized by wrapFlatEntityComponents into a wrapped component map that old engines can load, so the new gate incorrectly aborts an otherwise compatible build. Apply the flat-namespaced check to the staged/rewritten prefab, or otherwise exclude source prefabs whose flat content will be wrapped; source scanning is still necessary for @ keys.

Useful? React with 👍 / 👎.

…ewritten copies

Codex on #798: the pre-staging pack SOURCE scan rejected flat namespaced keys
that pack rewrite pass 1 later wraps (which loads on every engine). The source
scan is back to `@`-only; flat namespaced keys in pack prefabs are checked on
<target>/packs/<pack>/prefabs after loadPackScans. Game-root scenes/prefabs
(never rewritten) keep the combined check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Caqyi5Snr6yKssztYFztzZ
@apotema

apotema commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@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: d0255756db

ℹ️ 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 src/scene_name_lint.zig
Comment on lines +176 to +177
.flat_namespaced => if (scope == .entity) {
if (isComponentKeyShape(content) and !scene_keys.isPascalCase(content)) return true;

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 Ignore dead flat keys beside legacy root wrappers

When an engine older than 2.11 is pinned, a prefab such as { "root": { ... }, "rooms__Room": {} } is rejected even though the namespaced sibling has no meaning to any engine: the legacy loader selects the first root object and treats other object-valued siblings as opaque dead data. The pack rewrite models this explicitly with its file_container scope, but this walker classifies the document object as .entity, so both game prefabs and staged pack prefabs can spuriously fail the new version gate. Detect root-wrapper containers (including first-match handling for duplicate root keys) before applying the flat-namespaced check.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Valid, but it only produces a loud false-positive generate error on pre-2.11 pins for legacy root-wrapped files that are malformed anyway; nothing is silently dropped. Fix rounds for this PR are used up, so it's tracked in #805.

@apotema
apotema merged commit f388198 into main Sep 29, 2026
5 checks passed
@apotema
apotema deleted the fix/scene-key-classification-651-652 branch September 29, 2026 00:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant