190 lines
10 KiB
Markdown
190 lines
10 KiB
Markdown
# How we built a PS1 dungeon to sell a code-review workflow
|
||
|
||
[Eureka](https://eureka.gamecult.org/forge/GameCult/Eureka) is a Claude Code skill built on
|
||
one rule: the agent that writes the code does not get to certify that the code
|
||
works. It needed a landing page. This repo is that page, and it turned into a
|
||
PlayStation 1 game you scroll through.
|
||
|
||
Served at **https://eureka.gamecult.org/**.
|
||
|
||
## Front Door
|
||
|
||
- **Value:** the landing page for [Eureka](https://eureka.gamecult.org/forge/GameCult/Eureka), a scroll-driven WebGL dungeon rendered as a PlayStation 1 game, plus MIT-licensed parts you can lift: a PS1 material and dither pass for three.js, a scroll-on-rails camera pattern, and self-contained organ builders.
|
||
- **Problem:** explain a code-review workflow (the agent that writes the code does not certify it) as something you travel through rather than read about.
|
||
- **Entry points:**
|
||
- `prototype/index.html`: the copy; each `<section data-stop>` pins one camera stop.
|
||
- `prototype/ps1.js`: the reusable PS1 kit (`ps1()` material, `framebuffer()`); `prototype/organs.js`: the organ builders; `prototype/world.js`: the scene and `ROUTE`; `prototype/main.js`: the scroll timeline.
|
||
- Run locally: `python -m http.server 8765 --directory prototype`.
|
||
- `BRIEF.md` and `gpt6-copy.md`: the two source briefs; `docs/research/landing-claims-audit-2026-10-09.md`: copy claims labelled CURRENT, STALE or UNVERIFIED against the doctrine.
|
||
- **Live:** the page served at https://eureka.gamecult.org/ from `prototype/`; deployment is in `gamecult-ops` at `runbooks/eureka-prototype-yggdrasil.md`.
|
||
- **Planned:** nothing recorded in this repo beyond refreshing copy flagged STALE or UNVERIFIED in the audit.
|
||
|
||
## Two briefs, neither in charge
|
||
|
||
We started with two documents, and both are in this repo.
|
||
|
||
The first was [`BRIEF.md`](BRIEF.md). The agent that had been running Eureka on
|
||
a live multi-repo campaign wrote it with no guidance from us, because we wanted
|
||
the view from the ground. It is good on evidence. It lists real defects, each
|
||
caught by a pass that did not write the code, sitting behind a green suite. It
|
||
is also confident about things nobody had asked it to decide, such as which
|
||
static-site generator to use. We kept the evidence and overruled the rest. The
|
||
dated rulings at the top of the file record what was overruled.
|
||
|
||
The second was [`gpt6-copy.md`](gpt6-copy.md), a long copy draft that came out
|
||
of a longer conversation about where Eureka and GameCult are going. The page's
|
||
text is cut from it, along with its taste for short lines that land one at a
|
||
time.
|
||
|
||
## A camera on rails
|
||
|
||
The reference was [acko.net](https://acko.net/). What we took from it was
|
||
specific: the page background is a WebGL scene, and scrolling moves the camera
|
||
through it along a fixed path. Reading the page is the same as travelling
|
||
through it.
|
||
|
||
So the page is a route. Every section of text pins one camera stop, and unnamed
|
||
points in between shape the flight. It begins in a hall where a cracked monument
|
||
reads ALL TESTS PASSING, in front of a wall of green checkmarks. The next stop
|
||
looks at a floor hatch stencilled GREEN, which has been pried up. Below the
|
||
hatch, the route runs:
|
||
|
||
1. Down a shaft into Eureka's organ chamber.
|
||
2. Through a lattice where Soul's probes flip green nodes to orange.
|
||
3. Along a gallery of specimen cases: the mutation runner's four false greens, and one empty case.
|
||
4. Past an archive where the rules keep their evidence.
|
||
5. Into the vanishing twin's chamber, where Epiphany's scaffolding stands mostly
|
||
empty.
|
||
6. Up a second shaft into the hall, where the monument has now split along its
|
||
crack.
|
||
|
||
One number owns all of it. Scroll position becomes a path parameter `u`, and `u`
|
||
drives the camera, the organs, the probe animation, the staccato lines, and the
|
||
monument splitting at the end. There is no second clock to drift out of sync.
|
||
|
||
## Going full PlayStation
|
||
|
||
The first pass rendered at low resolution with a dither, which was tasteful and
|
||
wrong. Then we found keijiro's
|
||
[Retro3D](https://github.com/keijiro/Retro3D), a Unity shader of about 20 lines,
|
||
and went all the way to PlayStation 1. The whole effect comes down to a few
|
||
tricks, and each one ports to WebGL in a line or two:
|
||
|
||
- **Vertex snapping.** The PS1's geometry chip had no sub-pixel precision, so
|
||
every projected vertex snaps to a 240-line pixel grid. That is where the
|
||
wobble comes from.
|
||
- **Affine textures.** Texture coordinates are multiplied by depth in the vertex
|
||
shader and divided back out per pixel. That cancels the GPU's perspective
|
||
correction and gives the PS1's texture swim. Large floors are split into
|
||
tiles, as PS1 games did, so the warp looks charming rather than broken.
|
||
- **15-bit colour** with the console's own 4×4 ordered dither, applied in a final
|
||
full-screen pass.
|
||
- **Everything else:** lighting computed per vertex, fog to black, no mipmaps,
|
||
nearest-neighbour filtering, and half-transparent surfaces for glass and
|
||
Soul's crystal.
|
||
|
||
Nothing here is a model file. Every texture is drawn to a `<canvas>` at load,
|
||
and every mesh is built in code.
|
||
|
||
## The organs
|
||
|
||
Eureka's faculties come from Epiphany, the larger system it grew beside and
|
||
then absorbed.
|
||
[Aquarium](https://github.com/GameCult/Aquarium/blob/main/docs/epiphany-agent-sdf-visual-language.md)
|
||
already defines how each organ looks, as raymarched distance fields with PBR
|
||
materials. We demade them:
|
||
|
||
- **Self** is a black core inside gold orrery rails.
|
||
- **Soul** is a see-through verdict crystal with a white oath light, blue edges
|
||
and a red risk seam.
|
||
- **Hands** is a dark tool with three enamel jaws and a hot cutting edge.
|
||
- **Imagination** is an opal seed wrapped in curling ribbon petals.
|
||
- **Life** is a teal nautilus shell carrying an ember and memory beads.
|
||
- **Eyes** is a black-glass lens in blue-metal gimbals, behind spiral aperture
|
||
petals that narrow as evidence arrives, with cyan evidence sparks.
|
||
|
||
They sit on Aquarium's own orbit slots, so the camera's circuit around the
|
||
chamber is the Aquarium layout. The seat where Epiphany kept her Face belongs
|
||
to the viewer: in Eureka, the operator is a human.
|
||
|
||
Epiphany's chamber is the vanishing twin: the twin absorbed before birth, whose
|
||
cells the survivor carries. All eight cages still stand, mostly empty. Eyes has
|
||
already gone to Eureka; Body and Face rise out of their cages and drift toward
|
||
her as you pass.
|
||
|
||
## The thing at the far plane
|
||
|
||
GameCult's mark is a camera aperture, and we wanted it to hang behind the fog as
|
||
a megastructure. An earlier AI rendering showed the whole thing, evenly lit and
|
||
front-on, and it read as a medallion. Scale comes from what you can't see. This
|
||
aperture is a separate sky layer: it follows the camera's rotation and never
|
||
moves closer. It is tilted so you see it at an angle and held at a fixed depth
|
||
of fog. Its six blades are habitats (forest, desert, ice, falls, highlands, a
|
||
night city) around a gold hub, with stations in orbit. Underground it fades to a
|
||
faint presence. The iris opens as you read.
|
||
|
||
## Small machines that earned their place
|
||
|
||
- **Framing follows the text.** Text panels covered the subject in most shots.
|
||
We didn't retune 26 camera angles. Each section already says which side its
|
||
panel is on, so the camera shifts its framing toward the other side, eased
|
||
between stops.
|
||
- **Stops are checked.** The page's sections and the route's stops must match
|
||
one to one, in order. The page checks this on load and says so in the console
|
||
when they don't.
|
||
- **Motion is optional.** Without JavaScript, or with reduced motion turned on,
|
||
it is a readable page of prose. Reduced motion replaces the flight with a
|
||
still frame per section.
|
||
- **Verification ran headless.** The in-app browser we were using could not
|
||
capture the page once it had scrolled, even with rendering paused. Every stop
|
||
was checked with headless Chrome driven by puppeteer instead, using the
|
||
`?debug` readout and the `?wp=N` jump.
|
||
|
||
## Take the toys
|
||
|
||
The retro 3D adventure is what makes this page special. Sharing toys is what
|
||
GameCult is for, so it's all MIT: take any of it.
|
||
|
||
- **The PS1 kit** is [`prototype/ps1.js`](prototype/ps1.js), about 240 lines.
|
||
Only a little of it is specific to this page: the brand palette, and two
|
||
opt-in effects (`nodes` for the lattice probes and `cells` for the wall's
|
||
re-graded checks) that you can ignore or delete.
|
||
- `ps1({ map, color, emissive, rim, alpha, ... })` returns one three.js
|
||
material with vertex snapping, affine textures, per-vertex lighting, rim
|
||
light, fog and PS1 half-transparency, each switched on by options.
|
||
- `framebuffer(renderer)` gives you the low-resolution target and the 15-bit
|
||
dither pass.
|
||
- There are helpers for canvas textures, tiled floors that keep the texture
|
||
warp in check, and merging many parts into one draw call.
|
||
- Copy it next to three.js, point your meshes at `ps1()`, and your scene is
|
||
now 1997.
|
||
- **The scroll-on-rails pattern** is about 40 lines of `main.js`: turn scroll
|
||
position into a path parameter by interpolating between section anchors, then
|
||
let everything else read that one number. The framing shift away from text
|
||
panels is another 10 lines on top.
|
||
- **The organs** in [`prototype/organs.js`](prototype/organs.js) are
|
||
self-contained builders. Each one returns a `{ group, update(t, k) }` object
|
||
you can drop into any three.js scene, as long as `ps1.js` comes with it.
|
||
|
||
If you make something with them, we'd love to see it.
|
||
|
||
## Where things live
|
||
|
||
| File | Owns |
|
||
| --- | --- |
|
||
| `prototype/index.html` | The copy. Each `<section data-stop>` pins a camera stop. |
|
||
| `prototype/world.js` | The scene, the aperture, and `ROUTE`. |
|
||
| `prototype/organs.js` | The organ demakes and the twin's scaffolding. |
|
||
| `prototype/ps1.js` | The PS1 material, the framebuffer and dither, and geometry helpers. |
|
||
| `prototype/main.js` | The scroll timeline and the frame loop. |
|
||
| `prototype/vendor/` | three.js 0.186.1 (MIT). |
|
||
|
||
Run it locally by serving `prototype/` over HTTP, since ES modules won't load
|
||
from `file://`:
|
||
|
||
```bash
|
||
python -m http.server 8765 --directory prototype
|
||
```
|
||
|
||
The Yggdrasil deploy is documented in `gamecult-ops`, in
|
||
`runbooks/eureka-prototype-yggdrasil.md`.
|