makepad/aigame_port_findings.md
Admin 449f51be7a gamemaker prerequisites: makepad-tts library, TTS/whisper model downloader, aigame design docs
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>
2026-07-10 13:29:22 +02:00

12 KiB
Raw Permalink Blame History

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 612 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 lets, 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 414s 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 45.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.