libs/tts was never tracked despite being a build dependency of the gamemaker example. tools/download_tts.sh fetches the public upstream weights (HuggingFace Kokoro-82M + whisper.cpp) and converts them locally with the in-repo stdlib-only converter; model artifacts are gitignored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
179 lines
12 KiB
Markdown
179 lines
12 KiB
Markdown
# aigame port findings — my-game 3D sandbox → game.splash
|
||
|
||
Result of test-porting the AI-generated Godot game (`~/games/my-game`, the active
|
||
3D sandbox: ~2100 lines across main3d + 9 actor scripts) onto the gamemaker
|
||
engine as one `game.splash`. Fixture:
|
||
`examples/gamemaker/resources/fixtures/sandbox3d.splash` (~370 lines — 5.7×
|
||
denser than the GDScript, mostly because the engine owns spawning/physics/SFX).
|
||
Written 2026-07-09. Companion checklist: `aigame_port_inventory.md`.
|
||
|
||
## Verification
|
||
|
||
- Whole world evaluates clean first-load: **1036 entities** (961 terrain columns,
|
||
water sheets, 14 trees, goal + beacon, 19-creature cast, 2 trucks), zero eval
|
||
or tick errors at the 500k/tick instruction limit.
|
||
- Tape run (`ag test`, 200 frames): spawn→fall→land at exactly ground+half;
|
||
camera-relative walk at exactly SPEED 6.0; jump arc peaks +2.25 = JUMP²/2G to
|
||
the decimal; wander AI steers the cast; respawn works. Probe numbers, not hopes.
|
||
- Mount/drive/dismount verified end-to-end *by accident*: the first fixture spawn
|
||
was 2.3 units from a truck and the player silently auto-mounted at tick 1 —
|
||
seat height, top speed, and jump-to-dismount all matched the Godot behavior
|
||
before I even tried to test them. (Fixed by matching the original's player
|
||
transform (-8, 11, 8).)
|
||
- NOT verified visually (headless pane compositing still pending, task #9):
|
||
colors, camera feel, HUD text rendering. Not exercised behaviorally: nightmare
|
||
wake/pounce and the win touch (code-verified only; both use the same verbs the
|
||
probes exercised).
|
||
|
||
## Engine bugs the port flushed out (both fixed)
|
||
|
||
1. **Sensors were solid.** `step_world` filtered collision candidates by body
|
||
kind only — `sensor: true` boxes (goals, my water sheets, the beacon) blocked
|
||
movement, contradicting the documented contract. One-line filter fix. The
|
||
starter game never noticed because its goal floats at jump apex.
|
||
2. **(Earlier, same session) optional positional args trapped** — probing
|
||
`args[1]` on a 1-arg call failed the whole eval. Every optional arg would
|
||
have hit this.
|
||
|
||
A port whose only failures were engine bugs, not script bugs, is a good sign
|
||
for the DSL's authorability.
|
||
|
||
## The six predicted cleanups — verdict from actually porting
|
||
|
||
| # | Prediction (aigame_port_inventory.md) | Verdict |
|
||
|---|---|---|
|
||
| 1 | First-class SFX bank + synth | **Confirmed hard.** The corpus had invented `squeak` and `roar` beyond the bank we shipped — the bank grew twice in one day. Named-bank + beep/jingle is the right shape; expect the bank to keep growing from corpus usage. |
|
||
| 2 | Tags + broadcast/duck-dispatch | **Half-confirmed.** `find`/`tag`/`distance` + script-side state objects covered everything the port needed — cross-actor "messages" (enrage/zap/heal) became plain field writes on shared script objects, which is *better* than Godot's group+has_method bus. A native broadcast API is NOT needed while one script owns all actors. It becomes needed only if games ever split across isolates. **Deferred, deliberately.** |
|
||
| 3 | Script raycast | **Dodged, honestly.** Ledge-probe AI was replaced by heightmap lookup (`h_at`) because terrain heights are script data anyway. Fine for this game; a shooter that needs line-of-sight will force `game.raycast`. Keep on the roadmap, don't build speculatively. |
|
||
| 4 | Camera-relative helper | **Confirmed, split in two.** The missing primitive wasn't a movement helper — it was `game.cam_yaw()` (the orbit camera's yaw was engine-private). With yaw readable, two lines of cos/sin in script do the rest; documented as a pattern instead of an API. |
|
||
| 5 | `attach/detach` (seats/carrying) | **Confirmed emphatically.** Replaced ~100 lines of per-actor teleport-following + collision toggling in GDScript with 2 calls. Vehicles, passengers, and the dismount-pop all fell out. |
|
||
| 6 | Mover platform carry | Already in the engine (kinematic floor_id carry); the port didn't stress it (no moving platforms in the 3D sandbox — they're in the 2D game). |
|
||
|
||
## New findings (not predicted)
|
||
|
||
1. **No RNG in the script language** — the single most-used Godot facility
|
||
(randf/randi everywhere in wander AI) simply didn't exist. Added
|
||
`game.rand()`/`game.rand_range(a,b)`, xorshift **seeded per eval**: wander AI
|
||
now replays identically under input tapes, which Godot's `randomize()`
|
||
corpus could never do. Determinism became a feature of the port.
|
||
2. **No noise either** — the terrain wants value noise. A 12-line script
|
||
`hash/smooth/noise2` worked fine at 31×31. At the original's 256×256 it
|
||
wouldn't (65k columns ≈ 3M+ instructions, and 65k entities would swamp both
|
||
the O(statics×movers) physics and the instanced renderer). The Godot game
|
||
itself had to weld chunks — scale is an *engine* concern in any engine.
|
||
→ Roadmap: `game.terrain({cells, cell, heights})` native heightfield
|
||
(box3d has heightfield shapes waiting) + greedy column merging.
|
||
3. **`shoot` was a hardcoded gap** — the ActionMap knew jump but nothing else;
|
||
the corpus registers custom actions at runtime (mouse+F+gamepad X). Added F →
|
||
`shoot`/`shoot_pressed`. Real fix on the roadmap: `game.action("dash", "KeyQ")`
|
||
runtime action registration, like the corpus does in Godot.
|
||
4. **Projectiles work but are clunky**: a bolt = gravity-0 mover + set_vel +
|
||
script-side life/hit bookkeeping in a `retain` closure. Fine at 5 bolts;
|
||
a bullet-hell would want `game.spawn_projectile({vel, life, on_hit})` with
|
||
engine-side lifetime. Mover-vs-mover hits also aren't reported by `on_touch`
|
||
(sensor×mover only) — the port used distance checks; real overlap events for
|
||
movers is a small, worthwhile addition.
|
||
5. **One HUD line is enough** — the original's 5 labels + colors + cancel-token
|
||
flashes collapsed into `game.text` + a 6-line script `flash()` with a
|
||
generation counter. Colored/positioned HUD text can wait.
|
||
6. **Multi-part models are the visible fidelity loss.** Every creature is one
|
||
colored box; the originals have legs/shirt/head/eyes/smile built from 6–12
|
||
cubes, billboard nametags, emissive eyes. The single biggest visual upgrade
|
||
per line of API: `game.part(id, {offset, size, color})` decorative child
|
||
boxes (no physics), plus `game.label(id, text)` nametags. This is also what
|
||
made the Godot corpus *charming* — worth prioritizing over new mechanics.
|
||
7. **Script ergonomics held up.** Struct-arrays of actor state + one shared
|
||
steering function expressed 9 GDScript classes in ~120 lines. Field mutation
|
||
through array elements, closures capturing top-level `let`s, and `retain`
|
||
with side effects all just worked. The `0.0 - x` workaround for (possibly
|
||
fine) unary minus should be tested and, if broken, fixed — it's the ugliest
|
||
thing in the fixture.
|
||
|
||
## Fidelity ledger (what the port drops vs the original)
|
||
|
||
- **Terrain at 1/8 resolution** (31×31×4u columns vs 256×256×0.625u welded
|
||
chunks): terraces are chunkier, no caves (cave-carving needs the fine grid to
|
||
read as tunnels), no smooth triangulated slopes, no vertex-color blending.
|
||
- **Retired cast stays retired** (Huggy, Robot, soldiers — the original has them
|
||
commented out too), so Kissy's enrage/defend/flee arcs are dormant here as
|
||
there; her follow behavior is live.
|
||
- Single-box creatures, no nametags, no arm-reach animation, no emissive glow,
|
||
no procedural sky (fixed background), water is an opaque thin sheet (alpha
|
||
untested in the cube renderer), no camera-blocking ray (camera can clip
|
||
through hills), no mouse-capture (orbit-drag instead), mini-variants are
|
||
recolored critters.
|
||
- Behavior approximations: injured-Kissy heals into a follower (original swaps
|
||
to a full Kissy with her own arc); nightmare glow-eyes become a body-color
|
||
swap; critter squeak probabilities eyeballed.
|
||
|
||
## Bottom line
|
||
|
||
The port took one authoring pass, found two engine bugs and five API gaps, and
|
||
every gap closed with a small primitive rather than a framework. The engine's
|
||
current vocabulary + script-side state objects genuinely cover the corpus's
|
||
*behavioral* range; the visible gap is decoration (parts/labels), and the
|
||
structural ceiling is terrain scale — both have clear, small next steps.
|
||
|
||
## v2 re-port (2026-07-09 late) — the full current game
|
||
|
||
`fixtures/sandbox3d.splash` v2 ports the game's CURRENT state (~3600 GDScript lines,
|
||
44 creatures + 2 trucks) onto the upgraded engine: verified headless — eval clean at
|
||
90 entities, tape run writes test_done/captures/probes, plaza floor exactly 7.9 and
|
||
walk exactly 6.0 (Godot ground truth), and two identical runs produce byte-identical
|
||
probes. The tape also exercises the full truck mount/drive/dismount chain.
|
||
|
||
### Fidelity ledger
|
||
|
||
**Exact:** every spawn-table name/position/size/color/speed; all behavior constants —
|
||
Giant DogDay guardian (follow 34/stand 9.5, charge 8.4 to an intercept point, bonk 5 →
|
||
stagger+knockback+bark, golden beam flash), Kissy bodyguard (gates 18/34, intercept,
|
||
shove 2.2), headcrabs (chase 26, leap at 6 [up 8 fwd 7, cd 1.8], latch 1.5 → ride-attach
|
||
(0,1.95,0) spin 2 + speed_mult 0.5, shake-off on vel.y>5 or 4s, self-stun 2.5),
|
||
Prototype weeping-angel (LOS dot 0.55 in 34, creep 70, eye glow 1.5/4.5), Baba Chops
|
||
(charge 14 @7.2, ram 2.4, fire-eye glow 2→5 ramp), Nightmare Huggy (hunt 90, arm-reach
|
||
via move_part inside 30), nightmare critters (creep 30, nip 1.3 bounce), CatNap
|
||
(sleep-curl scale 1/0.6/1, wake 9, catch 1.9), farm animals (shy 4.5/flee 4.4, calls
|
||
4–14s at Godot's per-kind pitches), trucks/passengers, terraced terrain recipe
|
||
(12.5+noise, snap 0.5, plaza 26/14/7, water 3.5, height-color bands), goal on the true
|
||
peak + glowing beacon, third-person camera (1.6/10/−0.35) + crosshair + hint, zap
|
||
dispatch on all threat kinds, HUD flash tokens, win latch, full synth bank.
|
||
|
||
**Approximated:** terrain colors are single-color auto-shade (Godot has height bands
|
||
sand/grass/dirt/stone/snow — a 257² script colors array would blow the eval budget;
|
||
engine follow-up: band colors as terrain options);
|
||
models simplified but silhouette-faithful (DogDay 21 parts incl. the 8-box sunbeam
|
||
collar vs 23; Baba 15 vs 27 — horns straight not curled); animal voices = moo/squeak
|
||
at Godot's pitch tables (no dedicated oink/baa/cluck recipes); only Nightmare Huggy's
|
||
arms animate (pack arms static); zap stun uniform 4.5s (Godot 4–5.5 per kind).
|
||
|
||
**Dropped:** cave tunnels only. (x-major→z-major heightfield transpose handled;
|
||
goal verified on the true peak via `game.ground_peak()`.)
|
||
|
||
### Engine gaps: found by the port, then closed engine-side (same day)
|
||
|
||
1. **CLOSED — eval budget vs terrain scale.** `game.terrain` now runs its noise
|
||
engine-side (`freq`/`offset`/`step`/`min`/`max`/`plaza`, cells up to 384), plus
|
||
`game.ground_y(x, z)` and `game.ground_peak()`. The fixture builds the full
|
||
Godot-scale 257×257 world (256 cells @0.625u) with zero script instruction cost,
|
||
and all spawn/tree/goal heights come from ground queries.
|
||
2. **CLOSED — second input action.** `grab` exists (keyboard G, gamepad B, tape
|
||
action) with `grab`/`grab_pressed` snapshot fields. The grapple hand is ported:
|
||
32 u/s flight to range 16 on a `game.beam` cable, terrain hit → player yank
|
||
(22 u/s + 6.5 up), creature hit → ride-attach haul, held 1.2s, set down at the
|
||
player's feet with a pop. Approximations (memo): solid-hit detection is
|
||
terrain-height only (tree/box bodies don't catch the cable — needs a script
|
||
raycast/box-probe verb); the hand vanishes at max range instead of retracting.
|
||
3. **CLOSED — label outlines.** Engine draws a 4-copy dark outline behind every
|
||
billboard label; nothing needed in script.
|
||
4. **OPEN — height-carving.** Caves need holes/overhangs in the heightfield (or
|
||
rock-slab CSG); the smooth mesh is single-valued.
|
||
5. **OPEN (new, minor) — terrain band colors.** Height-band coloring
|
||
(sand/grass/dirt/stone/snow) as engine-side terrain options, since script-built
|
||
color arrays don't scale past ~100² vertices.
|
||
6. **OPEN (new, minor) — solid-probe verb.** A `game.raycast`/box-probe would let
|
||
the grapple (and ledge-AI patterns from the corpus) hit boxes, not just terrain.
|
||
|
||
Verification after the engine-noise/grapple update: eval clean at 90 entities,
|
||
zero JIT failures, last_error empty, tape (walk/jump/shoot/grab) → test_done +
|
||
captures + probes with plaza floor exactly 7.9 and walk exactly 6.0, two runs
|
||
byte-identical.
|