Genesis

What inspired us

We wanted to build a feeling, not just a game. Sky: Children of the Light has always stuck with us for how it turns a simple walk into something spiritual, robed travelers drifting through light with no health bars, no combat, no noise. We wanted to see if we could capture even a sliver of that in 24 hours: a world you move through slowly, on purpose, with something quiet at your side.

The emotional arc came before a single line of code did. In the noise of the world you forget who you were. A companion leads you somewhere gentler, a meadow, a sleeping town, a forest, a jungle, and finally a snowy clearing, so you can find your purpose again, or make a new one, and reconnect with what's around you. Everything we built was in service of that last quiet scene: a traveler and an AI companion, sitting together in the snow, finally able to talk.

How we built it

The frontend is Vite and TypeScript, with Three.js doing all the rendering work: WebGLRenderer, an EffectComposer running UnrealBloomPass for that soft glow, custom ShaderMaterials, Points and InstancedMesh for anything that needed to exist in bulk, PCF soft shadow maps, and exponential fog to keep the horizons hazy and dreamlike. Movement runs on @dimforge/rapier3d compiled to WASM, driving a kinematic character controller against static colliders, boxes, trimeshes, ground planes, so the traveler always has solid footing no matter which world they're standing in.

The five worlds live behind a scenery registry: a shared interface where every world is a clean, independent drop-in, built once and updated per frame, swappable through a ?scene= URL parameter. That single decision let us parallelize world-building instead of fighting over one giant scene file.

  1. Meadow is a dawn valley: pulsating grass, god-rays, physically-shaded clouds that glow at the rim when backlit and darken away from the sun, a shadow-casting sun, layered parallax mountains wrapping the horizon, a pilgrimage corridor of stone arches leading to a shrine and a hero tree.
  2. Town is a snowy medieval night: consolidated house GLBs, a cathedral at the end of a cobblestone path, lamplight, mist, and snow spilling erratically over the road edges so nothing reads as a hard straight line.
  3. Forest is a dark teal wood, quiet and close.
  4. Rainforest is a dark blue-teal jungle floored with roughly 130,000 blades of GPU-instanced grass, big textured trees, and a winding sand path with soft, noisy edges.
  5. Snow is the ending: a twilight clearing, frosted trees, falling snow, a star field, a hazy horizon, and a mocked AI-companion chat with both a free-type box and suggested prompts.

The hero assets, mountains, trees, rocks, flowers, arches, shrines, were generated with TRIPO's text-to-3D API and blended with optimized marketplace GLBs that we shrank from about 600 MB down to roughly 30 MB using gltf-transform: Draco compression, textures downsized to 1024 or 512, and mesh simplification. We ended up committing about 110 MB of assets straight into the repo, force-added GLBs and all, so the whole thing runs on a fresh clone with zero asset pipeline to babysit.

The grass is the detail we're proudest of. Animating 130,000 blades on the CPU scales as $O(N)$ per frame with a real constant behind it, and at $N \approx 1.3 \times 10^5$ and 60 FPS that's:

$$ 1.3 \times 10^{5} \text{ blades} \times 60 \text{ frames/s} \approx 7.8 \times 10^{6} \text{ updates/second} $$

which is simply not a CPU's job. Pushing the wind animation into the vertex shader made it effectively free, the GPU processes it in parallel and the jungle floor stays dense without ever touching the frame budget.

We closed out with a roughly 58-second title-sequence video, a 15-beat script alternating serif title cards (rendered by a small self-contained HTML canvas engine with drifting bokeh and dissolve-through-light transitions) with in-game footage from each world, cross-dissolved together under a warm pad score.

Challenges we faced

The flight pose was our first real fight. The traveler was supposed to glide with arms out, Titanic-style, but instead jumped and tumbled like a thrown statue, because the lean was being applied on the wrong axis after the model's forward direction got rotated by a yaw offset. We fixed it by restructuring the rig around a dedicated lean-pivot: heading on the outer group, yaw offset on the inner model root, forward lean on the pivot itself, with the arms posed out through the upper-arm bones.

The companion droid had its own crisis, chasing floating orbs and trying to fly, which looked less like a calm guide and more like it was having seizures. Swapping that logic for a simple dead-zone follow fixed it instantly; it just calmly trails you now, the way it was always supposed to.

The snow scene's horizon kept showing a hard geometric line where sky met ground. Our first instinct was to tint the fog to match the bottom of the sky shader, which did nothing, because the sky actually shows its mid color at the horizon, not its bottom. Matching the fog color to that mid color, and tuning the density, finally gave us the soft hazy line we wanted.

Performance nearly took the project down. The full-detail scenes ran around 9 FPS, unrecordable, so we built a ?perf lite mode that clamps pixel ratio to 1, drops shadows, and thins out particle and instance density. That alone took us from 9 FPS to a smooth 60. The rainforest additionally crashed the browser outright from too many cloned tree, bush, and flower meshes before we cut clone counts and leaned on instanced grass instead. And one TRIPO-generated giant tree came back as an unusable wide canopy blob, so we fell back to procedural bark trunks.

Underneath all of it was the harder problem: making five very different worlds feel like one place, one character controller, one camera, one atmosphere, in 24 hours. That's ultimately what the registry pattern was for.

What we learned

The biggest lesson was how much of "feel" lives in small, almost invisible decisions: a dead zone on a follow behavior, a mid-color match on a fog shader, a pivot point on a rig. None of these show up in a feature list, but they're the difference between a world that feels alive and one that feels like a demo.

We also learned to respect the GPU as a first-class collaborator rather than an afterthought. Once we started asking "can this live in a shader instead of a loop," entire classes of performance problems, the grass, the wind, the bloom, just stopped being problems. And we learned that constraints sharpen taste: a 24-hour clock and a hard scope of five scenes forced us to commit to a small set of ideas and actually finish them, rather than half-building ten.

Most of all, we learned that a game can be gentle and still be a game. Genesis has no combat, no score, no fail state. It just asks you to walk, to fly a little, and to sit quietly with a companion at the end. Building that on purpose, and having it actually work, taught us more about restraint than any feature we shipped.

Built With

Share this project:

Updates