CHIVA / Adventure Games

3D Casual Adventure

A 3D casual adventure that runs by opening a file.

8/7/2026Views: 0Launches: 0

Sakura Stars — Project Report

1. The Concept

A 3D casual adventure that runs by opening a file.

Aiko comes home for the blossom festival and finds the valley out of joint: a hungry giant in the northern grove, a shrine gone dark, a temple whose turbines have stood still for a century, four mirrors drifted out of true in a frozen cave, and — on the last night — a moon that has stopped rising.

Five worlds, five guardians. The design rule the whole game hangs off is that **nothing is defeated.

** Ponpoko is hungry, so you feed him. Amatsu is guarding something it loves, so you stand still and let it. Tsukikami is frightened, so you reach out — the fight has no attack in it anywhere. Every world ends with Aiko giving something back, and the abilities you earn are for reaching places, never for hurting anything.

The technical concept was just as narrow: **no build step, no package manager, nothing to install.

** Double-click index.html and play.

2. The Stack

| | | |---|---| | Renderer | three.js r160.1, UMD build, from jsDelivr via a <script> tag | | Language | Plain ES5-flavoured JavaScript. No modules, no bundler, no transpiler | | Structure | 16 scripts, each adding exactly one global | | Assets | **None.

** Nothing but code ships | | Physics | Custom swept-AABB world. No PhysX, no Rapier, no Cannon | | Tests | Node harnesses driving the real game code against a three.js mock |

r160.1 is not an arbitrary pin — it is the last version of three.js with a UMD build. r161 is a 404 on the CDN path. Modules would have meant a server; UMD means file:// works.

"No assets" is literal. Textures are painted into offscreen canvases at load. Sound is synthesised through WebAudio — every footstep, every chime, the music. Meshes are boxes, spheres and cylinders assembled in code. Levels are generated by buildLevel(n). There is no .png, .mp3, .glb or .json in the tree.

What that adds up to: 11,652 lines across 16 scripts, generating 5 worlds, 318 props of 43 kinds carrying 868 colliders, 106 platforms, 155 stars, 230 coins, 20 characters and 53 enemies.

3. The 1 Day Sprint

Morning (Hours 1–3):

Demolition and rebuild. The project arrived as Vite + TypeScript + PlayCanvas with a package.json, a dist/ and a lockfile. All of it came out.

Rebuilt from nothing as index.html plus 16 flat scripts: config, save, audio, input, textures, props, world, effects, player, items, enemies, NPCs, boss, UI, game loop, level generator. Every screen as static markup — title, level select, pause, settings, journal, results, credits. Five worlds generated in code. Player controller with coyote time, a jump buffer, variable jump height and air control. By the end of the morning it ran, and it looked like a game.

Afternoon (Hours 4–6):

The collision war, and it was a war.

The first bug report was *"the main character can go through another 3D object"*, and the cause was blunt: **all 318 props had no collider at all.

** Trees were painted air. So were houses, bridges, lanterns and — the thing that was actually being walked through — the enemies.

That opened six hours of collision work. A per-kind collider table. Continuous detection so a full-speed fall cannot tunnel a 0.4m platform. Depenetration with validated escape directions. Step-up against the *tallest* overlapping surface rather than the first one found. Five-point foot sampling, so standing with your centre past a ledge drops you instead of leaving you on air. Shader vertex displacement so crops bend around the player rather than skewering her. Ankle groups so her soles stay flat on the floor.

Then the specific ones: a water wheel ploughing 0.65m through a mill wall on every turn; a festival bridge whose 33° end ramp demanded a 0.71m step against a 0.45m stride; strafe inverted; a jump seven centimetres shorter than the stairs it had to climb.

The Finish Line (Hours 7–8):

Traversal, then presentation.

Traversal turned up worse than bugs — **whole regions that had never been reachable.

** The sky temple's upper half sat 13.7m above anything below it, because the level was written expecting updrafts that were never built (its own dialogue promises them). I built them. The summit staircase demanded 2.93m of travel in a 0.44s window and could only be climbed at a run; I searched 1,700 candidate designs through the real movement code and took the one that walks. Three of five ladders climbed to open air; all five were re-placed to meet a ledge, and topping out became a swept-checked step instead of a teleport.

Then the render faults the user could see and I could not: crates fighting themselves over the depth buffer, a chest lid that was an open shell, every river in the game sitting 20–50cm *on top of* the land.

Finished with the story documents, 25 scene renders generated from the level data, and the first commit.

4. The Roadblocks

**No eyes.

** node_modules went in the hour-one cleanup and took puppeteer with it. For the entire build there was no way to see the game. Every claim was verified at the Node level, and the user's screenshots became the only visual feedback loop — which is exactly how a blinking crate, a floating chest lid and a sail sawing through a plank all shipped past me.

**Testing a reconstruction instead of the real thing.

** The worst failure of the day. Early collision tests were written against a hand-rebuilt set of boxes rather than the game's own. They passed, confidently, while the game was visibly broken — which is how the user came to write *"Why does the player still pass through? You shit!"* and was right to. The fix was real-build.mjs, which runs the genuine World.build(). **A test that does not run the real code is worse than no test, because it manufactures confidence.

**

**My own harnesses lying, over and over.

** This was the running theme:

measured empty air a hundred metres from the prop and passed everything

pyramid roof in the game

arm drawn four times and reported 60 overlapping pairs

arena builds its own floor — it declared all five arenas suspended over the void, and I nearly told the user their game was unwinnable

**Rotated bounding boxes.

** The recurring geometric villain. A 0.34m roof edge on a house standing at −113° bounds to a box nearly 3m across — wide enough to swallow the crate the player climbs to reach the roof. It forced the deletion of a rope bridge's railings early on, and later cost a whole cycle of workarounds before the real fix: colliders now carry their true rectangle alongside their bounds, and the world tests the shape.

**Fixing the symptom instead of the fault.

** The chest lid took two attempts. The first fixed its *pose* — a real problem, but not the reported one. The actual fault was that a cylinder cut to half a turn is an open trough, because three.js caps the ends and never the cut. I changed how it was posed without once looking at what it was made of.

**Sending the user in circles.

** The costliest roadblock was not technical. Asked where the boss fruit were, I answered with arena coordinates — three times — without ever checking whether their run had reached the boss. It had not: the gate was 0/1 and the fight cannot start until it opens. Worse, every *"hard refresh to see the fix"* I gave them reset the run to zero stars, which is why the fruit appeared once and never again. The answer was on their own HUD in the screenshot they sent, and I had not read it.

5. Key Takeaways

**When a test passes and the user says it is broken, the test is the suspect.

** Every single time in this build, the user was right and the harness was wrong. That should have become the first hypothesis far earlier than it did.

**Verification is only as good as what it drives.

** The tests that caught real bugs were the ones running World.moveActor and Props.build. The ones that lied were the ones re-implementing what the game does. Mock the environment, never the subject.

**Ask what the player can reach, not what the geometry says.

** Every one of the worst findings — the unreachable sky temple, the unclimbable stairs, the ladders to nowhere, the village key 8.4m from any surface — was invisible to collision tests and obvious the moment something *walked the route*.

**Correct and legible are different properties.

** The fruit existed, in the right place, working exactly as designed, and were undiscoverable. So were the ladders that led nowhere and the gate that never said it wanted 15 stars. A thing that works and cannot be found has not been built yet.

**Comments should record the bug, not the behaviour.

** The most valuable lines in this codebase say *why a number is what it is* — that jumpVelocity is 12.9 because the sky stairs climb 2.48 a step and 11.2 fell seven centimetres short. That is the comment that stops someone tuning it back.

---

My final thought:

The hard part of this build was never three.js, and it was never the absence of a bundler — a game with no assets and no build step turned out to be a pleasure to work in, and file:// is a genuinely good deployment target.

The hard part was that I could not see it. Twenty-eight automated checks caught things no human would find by playing — a collider seven centimetres short, a roof shell hollow at one corner, a step whose bottom face shared a plane with a wall — and every one of them was blind to a crate that visibly flickered. The user found that in about four seconds.