Skip to content

World-format trust + generalization: lossless round-trips, unknown-field warnings, applyWorldEnvironment, world-format.md, world-viewer example#104

Merged
proggeramlug merged 5 commits into
mainfrom
fix/world-file-trust
Jul 16, 2026
Merged

World-format trust + generalization: lossless round-trips, unknown-field warnings, applyWorldEnvironment, world-format.md, world-viewer example#104
proggeramlug merged 5 commits into
mainfrom
fix/world-file-trust

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Engine-side half of the editor's "any world, any game" push (editor PLAN.md Part 4). Grew beyond the original file-trust scope as the work surfaced more:

File trust (original scope)

  1. serializePrefab dropped schemaVersion + bounds from every prefab ever saved — both now serialize; migratePrefabData backfills degenerate bounds for old files; createEmptyPrefab stamps the current version.
  2. Unknown fields were silently stripped on save. New listUnknownWorldFields/listUnknownPrefabFields walk every schema level; loadWorld/loadPrefab warn per field at load. Sanctioned extension points: metadata/userData/tags.

Generalization (added)
3. applyWorldEnvironment(world) in world/render.ts — per-frame ambient+sun+lights+fog. The renderer clears lighting in begin_frame, so instantiateWorld alone lights exactly one frame; every consumer needed this and none existed. The editor's lighting preview now runs this exact code path.
4. docs/world-format.md — the adoption contract: schema, extension points + strip-warning behavior, full editor.project.json key table (incl. new kindColors), the --world play-in-editor convention, both runtime consumption shapes, versioning promises.
5. examples/world-viewer — the FIRST consumer of instantiateWorld, verified live against shooter's arena_02.
6. getRenderTextureTexture returned the pre-0.5 { id } Texture shape — drawing any render texture fed undefined into a native f64 param (surfaced by editor thumbnails).

Verified by the editor suite (148 self-tests incl. real-world round-trips) + live runs of the viewer and editor.

https://claude.ai/code/session_01PmL9WgNMkAgvpYSHEZga8K

Summary by CodeRabbit

  • Bug Fixes

    • Loading world and prefab files now logs warnings for unsupported/unknown fields that will be dropped on save.
    • Prefabs saved without bounds now load with safe default bounds.
    • Render textures now use the texture handle shape to avoid drawing issues.
    • Windows mouse movement now respects per-window DPI scaling for correct cursor positioning.
  • New Features

    • Added an applyWorldEnvironment API to refresh ambient/directional lighting and fog each frame.
    • Added a world-viewer example and expanded the Bloom world format specification.
  • Improvements

    • Prefab saving now preserves schema version and writes bounds; new prefabs use the current schema version.
    • Unknown-field detection includes nested world/prefab content (e.g., children, lights, water, rivers, terrain).

…fields

serializePrefab dropped schemaVersion and bounds from every prefab it ever
wrote — the version was silently backfilled by migration on reload (hiding
the loss) and bounds came back undefined. Now both serialize; migratePrefabData
backfills degenerate bounds for pre-fix files; createEmptyPrefab stamps the
current schema version instead of a hardcoded 1.

New listUnknownWorldFields / listUnknownPrefabFields walk every schema level
of a parsed document and return dotted paths for fields the schema-explicit
saver would drop. loadWorld/loadPrefab console.error each one at load time —
the sanctioned extension points that DO round-trip are metadata, userData,
and tags. Verified by the editor's self-test suite (129 pass), including
real load->save->deep-compare round-trips of both shooter arenas.

Claude-Session: https://claude.ai/code/session_01PmL9WgNMkAgvpYSHEZga8K
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@proggeramlug, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 19 minutes

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).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8d0ae7a8-96af-42f1-bd81-379a9ebb015b

📥 Commits

Reviewing files that changed from the base of the PR and between 6f30bc3 and a60838e.

📒 Files selected for processing (7)
  • .gitignore
  • docs/world-format.md
  • examples/world-viewer/README.md
  • examples/world-viewer/main.ts
  • examples/world-viewer/package.json
  • native/windows/src/lib.rs
  • src/world/render.ts
📝 Walkthrough

Walkthrough

World and prefab schema handling now reports unknown fields and preserves prefab metadata and bounds. A world-viewer example loads, instantiates, and renders worlds with applied environment settings. Render-texture conversion and Windows mouse coordinate handling now use compatible runtime representations.

Changes

World schema and prefab persistence

Layer / File(s) Summary
Unknown field detection and load warnings
src/world/validate.ts, src/world/loader.ts, src/world/prefab.ts
Schema allowlists identify unknown dotted paths across world and prefab structures, and loaders log fields that will be dropped on save.
Prefab schema and bounds persistence
src/world/version.ts, src/world/prefab.ts, src/world/serialize.ts, docs/world-format.md
Missing prefab bounds are initialized during migration, new prefabs use WORLD_SCHEMA_VERSION, serialization emits schema version and bounds, and the format documentation defines schema, extension, runtime, and versioning contracts.

World viewer runtime

Layer / File(s) Summary
World environment application
src/world/render.ts
Ambient, directional, point-light, and fog settings are applied from world environment data.
Reference world-viewer flow
examples/world-viewer/main.ts, examples/world-viewer/package.json, examples/world-viewer/README.md
The example loads worlds and optional prefabs, instantiates content, runs an interactive camera loop, applies environment settings, and documents setup and controls.

Render and Windows input compatibility

Layer / File(s) Summary
Render texture object shape
src/textures/index.ts
Render-texture conversion now returns handle instead of id for compatibility with draw calls.
Windows mouse DPI conversion
native/windows/src/lib.rs
Standalone and embedded Windows mouse movement converts physical client pixels to logical engine coordinates using per-window DPI scaling.

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

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant loadWorld
  participant instantiateWorld
  participant applyWorldEnvironment
  participant Renderer
  CLI->>loadWorld: load the world path
  CLI->>instantiateWorld: provide model lookup and prefab registry
  instantiateWorld-->>CLI: return instantiation warnings
  CLI->>applyWorldEnvironment: apply environment each frame
  applyWorldEnvironment->>Renderer: set lights and fog
  CLI->>Renderer: render the camera view and HUD
Loading

Possibly related PRs

  • Bloom-Engine/engine#90: Adds related world-schema and rendering functionality used by the environment application and validation changes.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is specific and matches the main changes, though it is longer than ideal and lists several secondary items.
Description check ✅ Passed The description covers the main changes and verification, though it omits the template's explicit test-plan checkboxes and notes section.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/world-file-trust

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Ralph Kuepper added 2 commits July 16, 2026 04:01
…e-0.5 { id }

Texture renamed its field to handle in v0.5; this function was never
migrated. Every draw call reads texture.handle, so drawing a render texture
passed undefined into a native f64 parameter — nobody had drawn one until
the editor's asset thumbnails (PLAN §G).

Claude-Session: https://claude.ai/code/session_01PmL9WgNMkAgvpYSHEZga8K
…ct doc, world-viewer example

- applyWorldEnvironment(world) in world/render.ts: per-frame ambient + sun +
  point lights + fog, the call every instantiateWorld consumer needs (the
  renderer clears lighting in begin_frame — instantiateWorld alone lights
  exactly one frame). The editor's syncEnvironment and the new example both
  use it, so the editor's lighting preview is the same code path games run.
- docs/world-format.md: the adoption contract — schema summary, extension
  points that round-trip vs fields that get dropped (with the load-time
  warning), the full editor.project.json key table incl. kindColors, the
  --world play-in-editor convention, both runtime consumption shapes, and
  versioning promises. A third game can adopt from this doc alone.
- examples/world-viewer: the FIRST consumer of instantiateWorld. Loads any
  world, fly camera, warnings surfaced. Verified live against shooter's
  arena_02 (terrain, models, water, rivers, lights spawn; sentinel-model
  entities warn-and-skip as documented).

Claude-Session: https://claude.ai/code/session_01PmL9WgNMkAgvpYSHEZga8K
@proggeramlug proggeramlug changed the title fix(world): lossless prefab round-trips + loud unknown-field warnings World-format trust + generalization: lossless round-trips, unknown-field warnings, applyWorldEnvironment, world-format.md, world-viewer example Jul 16, 2026

@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 `@docs/world-format.md`:
- Around line 66-80: Make the editor.project.json example valid JSON by removing
all inline // comments from the fenced json block in the documentation. Preserve
the existing property names and example values without introducing JSONC syntax
or loader changes.

In `@examples/world-viewer/main.ts`:
- Around line 149-154: Update the background color setup in the world-viewer
initialization flow to handle an undefined world.environment before accessing
skyColor. Reuse the same safe fallback behavior established by
applyWorldEnvironment in render.ts, while preserving the current skyColor-based
values when environment is present.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7fabf17e-56eb-47a3-a2e2-1ee32d59a2fe

📥 Commits

Reviewing files that changed from the base of the PR and between 6a14ed6 and 69515ec.

⛔ Files ignored due to path filters (1)
  • examples/world-viewer/world-viewer.exe is excluded by !**/*.exe
📒 Files selected for processing (6)
  • docs/world-format.md
  • examples/world-viewer/README.md
  • examples/world-viewer/main.ts
  • examples/world-viewer/package.json
  • src/textures/index.ts
  • src/world/render.ts

Comment thread docs/world-format.md
Comment on lines +66 to +80
```json
{
"name": "My Game", // ("Untitled Project") window title
"gameId": "mygame", // ("") shown in the title bar
"modelsDir": "assets/models", // (that) flat dir of .glb/.gltf
"prefabsDir": "assets/prefabs", // (that) *.prefab.json
"worldsDir": "assets/worlds", // (that)
"texturesDir": "assets/textures", // (that) splat-layer sources, listed not loaded
"defaultWorld": "level1.world.json", // ("") opened at launch
"playCommand": "main.exe", // ("") enables the Play button — see §4
"kindColors": { // (none) placeholder colors for YOUR kinds
"spawn_point": "90, 220, 120" // "r, g, b" 0-255
}
}
```

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

Make the editor.project.json example valid JSON.

This block is labeled json, but // comments make it invalid JSON. A user who copies it into editor.project.json will get a parse error. Remove the inline comments, or explicitly document a JSONC format and ensure the loader supports it.

🤖 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 `@docs/world-format.md` around lines 66 - 80, Make the editor.project.json
example valid JSON by removing all inline // comments from the fenced json block
in the documentation. Preserve the existing property names and example values
without introducing JSONC syntax or loader changes.

Comment on lines +149 to +154
clearBackground({
r: Math.floor(world.environment.skyColor[0] * 255),
g: Math.floor(world.environment.skyColor[1] * 255),
b: Math.floor(world.environment.skyColor[2] * 255),
a: 255,
});

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

Prevent potential crash if world.environment is undefined.

applyWorldEnvironment in render.ts safely guards against world.environment being undefined. If older worlds or prefabs can lack this field, accessing world.environment.skyColor here will result in a runtime crash. Consider adding a defensive fallback to ensure the reference viewer remains stable.

🛡️ Proposed defensive fix
-  clearBackground({
-    r: Math.floor(world.environment.skyColor[0] * 255),
-    g: Math.floor(world.environment.skyColor[1] * 255),
-    b: Math.floor(world.environment.skyColor[2] * 255),
-    a: 255,
-  });
+  const env = world.environment;
+  clearBackground(env ? {
+    r: Math.floor(env.skyColor[0] * 255),
+    g: Math.floor(env.skyColor[1] * 255),
+    b: Math.floor(env.skyColor[2] * 255),
+    a: 255,
+  } : { r: 0, g: 0, b: 0, a: 255 });
📝 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
clearBackground({
r: Math.floor(world.environment.skyColor[0] * 255),
g: Math.floor(world.environment.skyColor[1] * 255),
b: Math.floor(world.environment.skyColor[2] * 255),
a: 255,
});
const env = world.environment;
clearBackground(env ? {
r: Math.floor(env.skyColor[0] * 255),
g: Math.floor(env.skyColor[1] * 255),
b: Math.floor(env.skyColor[2] * 255),
a: 255,
} : { r: 0, g: 0, b: 0, a: 255 });
🤖 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 `@examples/world-viewer/main.ts` around lines 149 - 154, Update the background
color setup in the world-viewer initialization flow to handle an undefined
world.environment before accessing skyColor. Reuse the same safe fallback
behavior established by applyWorldEnvironment in render.ts, while preserving the
current skyColor-based values when environment is present.

…sts were off by the DPI scale

WM_MOUSEMOVE stored raw physical client pixels while everything else on
Windows speaks logical units (WM_SIZE derives logical = physical / dpi_scale
precisely so 'the rest of the engine sees the same logical/physical split
macOS does'). On a 125%/150% display every hover and click landed scale-x
away from the pointer, worse with distance from the top-left — the editor's
UI was unusable at 150%.

Both window procs (standalone + attached-view) now divide by dpi_scale before
set_mouse_position. The FPS-capture path keeps its centre math and
SetCursorPos in physical (those APIs are physical) and converts only the
delta handed to the engine, so look sensitivity matches macOS point-deltas.

Claude-Session: https://claude.ai/code/session_01PmL9WgNMkAgvpYSHEZga8K
The rule was examples/**/main.exe | main | main.app, which assumed every
example compiles to `main`. The world-viewer compiles to world-viewer.exe,
so it matched nothing, got committed at 26 MB, and was one merge away from
being in main's history forever (where deleting it later would not have
removed it -- the blob stays reachable).

Match by extension instead. The .exe itself is dropped from this branch's
commits in the same push, so the blob never reaches main.
@proggeramlug
proggeramlug force-pushed the fix/world-file-trust branch from 6f30bc3 to a60838e Compare July 16, 2026 07:33
@proggeramlug
proggeramlug merged commit 898cb4c into main Jul 16, 2026
10 checks passed
@proggeramlug
proggeramlug deleted the fix/world-file-trust branch July 16, 2026 07:40
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