# platformer_demo.ludic — the ludic.platformer controller (#58) exercised # headlessly and deterministically over the shared engine collision (entity # solids): gravity + landing, jump apex height, coyote-time tracking, double jump, # the JumpRequested veto hook, and a horizontal run into a wall. # # Movement intent is written directly (the AI / custom-input path: the input system # is switched off with `disable system esys_platformer_input`), so the run needs no # key feed and is fully reproducible. A full run prints: # 1 1 1 1 1 1 1 1 # # Build (from the repo root): # LUDIC_MODULES=packages ludicc --headless examples/games/platformer_demo.ludic program PlatformerDemo { import "ludic.platformer/platformer.ludic" property Position { x: int = 0, y: int = 0 } property Body { vx: fixed = 0.0, vy: fixed = 0.0, gravity: fixed = 0.0, max_fall: fixed = 0.0, rx: fixed = 0.0, ry: fixed = 0.0, policy: int = 0, on_ground: int = 0, hit_wall: int = 0, hit_ceiling: int = 0 } property Collider { w: int = 0, h: int = 0, offx: int = 0, offy: int = 0, is_trigger: int = 0, one_way: int = 0, layer: int = 0, mask: int = 0, hit: int = 0, entered: int = 0, exited: int = 0 } model Hero { Position, Body, Collider, Platformer } model Floor { Position, Collider } model Wall { Position, Collider } @OnSpawn(Hero) handler HeroInit { } @OnSpawn(Floor) handler FloorInit { } @OnSpawn(Wall) handler WallInit { } var jumps: int = 0 @On(JumpPerformed) handler CountJumps { jumps += 1 } # a game rule: forbid the air (double) jump unless a "power" flag is set — pure # lever-4 veto, no controller edit. var have_power: int = 0 var vetoed_air: int = 0 @On(JumpRequested) handler GateAir { if (kind == 2) and (have_power == 0) { vetoed_air = 1; cancel } } function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } } function bi(b: bool) -> int { if b { return 1 }; return 0 } function pf_coyote(e: int) -> int { let P = World.prop_id("Platformer") return World.get(e, P, World.field_id(P, "coyote_t")) } entry { let PB = World.prop_id("Body") let PP = World.prop_id("Position") let PL = World.prop_id("Platformer") let fy = World.field_id(PP, "y") let fx = World.field_id(PP, "x") let fgrd = World.field_id(PB, "on_ground") # a solid floor (top at y=192) and a wall at x=480; both plain solid colliders, # so the platformer rides the same esys_move sweep the physics tests use. spawn Floor { Position { x: 0, y: 192 }, Collider { w: 640, h: 32 } } spawn Wall { Position { x: 480, y: 0 }, Collider { w: 16, h: 192 } } # the input system is off — drive intent directly for a reproducible test. disable system esys_platformer_input spawn Hero { Position { x: 80, y: 40 }, Collider { w: 12, h: 16 }, Platformer { move_speed: 3, jump_height: 4, apex_frames: 14, air_jumps: 1, coyote_frames: 6 } } let h = World.query_next(PL, 0) # ---- A. gravity + landing ---------------------------------------------- tick_n(90) let floor_y = 192 - 16 # feet on the floor top print(bi(World.get(h, PP, fy) == floor_y)) # 1 print(bi(World.get(h, PB, fgrd) == 1)) # 1 # ---- B. jump apex height ~= jump_height tiles (held for full height) ---- Platformer.hold(h, 1) # hold jump: full-height arc Platformer.jump(h) var top = World.get(h, PP, fy) var i = 0 while i < 20 { tick_fixed(); let y = World.get(h, PP, fy); if y < top { top = y }; i += 1 } let rise = floor_y - top # px risen at apex (target 64 = 4 tiles) print(bi((rise >= 56) and (rise <= 72))) # 1 Platformer.hold(h, 0) tick_n(40) print(bi(World.get(h, PB, fgrd) == 1)) # 1 — landed again # ---- C. coyote time is tracked (refilled while grounded) --------------- print(bi(pf_coyote(h) == 6)) # 1 # ---- D. double jump gated by the veto hook ----------------------------- have_power = 0 Platformer.jump(h); tick_n(3) # ground jump -> airborne let after_ground = jumps Platformer.jump(h); tick_n(2) # air jump attempt -> vetoed print(bi((jumps == after_ground) and (vetoed_air == 1))) # 1 have_power = 1 let before_power = jumps Platformer.jump(h); tick_n(2) # air jump now allowed print(bi(jumps == before_power + 1)) # 1 tick_n(60) # settle on the floor # ---- E. horizontal run into the wall (collision reuse) ----------------- Platformer.move(h, 1) # hold right tick_n(200) let hx = World.get(h, PP, fx) print(bi((hx >= 460) and (hx < 480))) # 1 — stopped at the wall (right edge ~468) quit() } }