Load the world at runtime: terrain, forest and lights become data#10
Conversation
arena_01 and arena_02 are now schemaVersion 2, with arena_02's five point_light entities lifted into the top-level `lights` array. tools/build-world reads world.lights, falling back to the old point_light entity form so an un-migrated world still bakes. The generated runtime module is byte-identical apart from one comment: the migration is semantically a no-op for the game, and a deliberate one for the editor, which can now show and edit lights as lights. Also point editor.project.json at arena_02 (the real level; arena_01 is the old box-in-a-box), and gitignore the editor's scratch state.
The game no longer bakes its world into TypeScript. src/world-runtime.ts reads assets/worlds/arena_02.world.json at startup via the engine's loadWorld and presents it as the same flat number arrays the game loop already consumed, so the 2,200-line main loop is unchanged. src/generated/ and tools/build-world.ts are gone. The baker existed because Perry 0.4.x returned arrays from JSON.parse whose .length read as undefined. That is fixed (verified on 0.5.1208), so the workaround can go with it. The two rules that still bind are honoured in the new module: arrays are built with new Array(n) + index assignment, never .push(), and strings are parsed at load, never on a per-frame path. Three things that were code are now data: - Terrain. The heightmap lives in world.terrain, and the game feeds it straight to Jolt — so sculpting in the editor changes collision, enemy ground-following, and the scatters on the next launch, with nothing to regenerate. Only the visual mesh is still baked (bun tools/build-terrain.ts), which now READS those heights and adds the horizon skirt outside the arena rather than inventing the ground itself. - The forest. 88 trees were scattered from a fixed LCG seed at startup, so no tree had an identity and "move that one off the path" was not expressible. They are entities now (kind: prop_tree) — movable, retintable, deletable, addable — seeded from that same scatter by bake-forest-to-world.ts. - The scatters' keep-out shapes. The forest and grass rejected hardcoded rectangles copied out of the level by hand: the river at "z = 12", the building at "x -30..-12". Move the river in the editor and the grass kept avoiding the old channel while growing out of the new one. They now derive from the world itself — the water volumes, and the bounding box of the building's colliders (W.keepOut). The river channel carved into the terrain likewise comes from the water volume's own position and width, so the ground and the water can no longer disagree. Verified: the game builds, runs, and renders the arena — forest, grass, carved river, building, lighting — with no generated code anywhere in the tree.
|
Warning Review limit reached
Next review available in: 59 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (16)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
The game no longer bakes its world into TypeScript, and the level is now editable in the Bloom editor rather than being half code.
No more baker
src/world-runtime.tsreadsassets/worlds/arena_02.world.jsonat startup via the engine'sloadWorldand presents it as the same flat number arrays the game loop already consumed — so the 2,200-linemain.tsis untouched.src/generated/andtools/build-world.tsare deleted (390 lines of generated code, plus a whole build step).The baker existed because Perry 0.4.x returned arrays from
JSON.parsewhose.lengthread asundefined. That is fixed (verified on 0.5.1208), so the workaround goes with it. The two rules that still bind are honoured in the new module: arrays are built withnew Array(n)+ index assignment, never.push(), and strings are parsed at load, never on a per-frame path.npm run devis now just compile-and-run.Three things that were code are now data
Terrain. The heightmap lives in
world.terrain(128×128 over the arena) and is fed straight to Jolt — so sculpting in the editor changes collision, enemy ground-following, and the scatters on the next launch, with nothing to regenerate. Only the visual mesh is still baked (bun tools/build-terrain.ts), and that generator now reads those heights and adds the horizon skirt outside the arena, rather than inventing the ground itself.The forest. 88 trees were scattered from a fixed LCG seed at startup, so no tree had an identity and "move that one off the path" was not expressible. They are entities now (
kind: prop_tree) — movable, retintable, deletable, addable.The scatters' keep-out shapes. The forest and grass rejected hardcoded rectangles copied out of the level by hand: the river at
z = 12, the building atx -30..-12. Move the river in the editor and the grass kept avoiding the old channel while growing out of the new one. They now derive from the world itself — the water volumes, and the bounding box of the building's colliders (W.keepOut). The computed building footprint (−30.2…−11.8, −19.2…−6.8) matches the rect that was previously hardcoded, which is how I know the derivation is right.The river channel carved into the terrain likewise comes from the water volume's own position and width, so the ground and the water can no longer disagree.
Schema v2: lights out of
userDataarena_01andarena_02areschemaVersion: 2, witharena_02's fivepoint_lightentities lifted into the top-levellightsarray (see Bloom-Engine/engine#90). Verified semantically identical: every field outsideentitiesunchanged, all 85 ids accounted for, zero non-light entities touched, all five lights' values carried over exactly — and the generated runtime data was byte-identical apart from one comment before the baker was removed.The arena JSON diff is large but is mostly reformatting: the migration writes standard
JSON.stringify(…, null, 2), which is what the engine's saver produces, so this just front-loads what the first editor Save would have done anyway.Seeding tools (run once, not part of the build)
bake-terrain-to-world.tsandbake-forest-to-world.tsre-derive that data from its original recipe (tools/terrain-shape.ts). Re-running either overwrites editor work on that data — they say so, loudly.Verification
Builds, runs, and renders the arena — forest, grass, carved river, building, lighting — with no generated code in the tree. The editor, opened on the same file, shows the terrain, the river, and all 88 trees as individual editable objects.
Depends on Bloom-Engine/engine#90.