Overview

Two unrelated Git histories, 426 tracked files each. Same art for slide/run/land. Different game underneath: keys, slide timer, hitboxes, spawn, lives, kick, towers.

Canonical · the game

Rizky RAR drop

po-runner-2026-rizky-20260907
branch master

88b4490f9b633e394805c9639915fe84a02f776c

Checkpoint: watchtowers with decks, wall hurtboxes, and spawn spacing.

Inbox: Drive folder

Icarus fork · prior

Do not treat as the game

po-runner-2026
branch fix/playable-loop

ccc194f5005cda3c92aeb434d4495ee27e8b2aab

Hut eaves slidable and board-arrow towers solid. Playtest, hanging, kick-breaks-rocks live here only.

426tracked files each
nonemerge-base (unrelated)
identicalslide SpriteFrames
0.5 vs 0.65SLIDE_DURATION seconds

Source inventory also on Drive: compare/2026-09-07-canonical-vs-icarus. Helios on Icarus. Intent: study only.

Methodology errors

Why we diverged. These are process mistakes, not Rizky bugs.

1
We treated the Icarus fork as the game.

Packed our zip for Rizky. Wrote CONTRACT.md, playtest_driver.gd, and walk skills against our architecture: pattern spawner, hanging, GameState.lives, kick-breaks-rocks, hut-eave-safe.

2
Discord notes mapped onto our scenario table.

slide_into_rock, hut as platform, tower_00/02. Canonical has no hanging, no playtest, no watchtower.gd.

3
Input maps are not the same.

Writing slide to .godot/walk/command talks to the fork’s action names, not Rizky’s keys. Canonical: D jump, S kick, Down slide.

4
Slide art is identical. The stutter was our clock.

8 frames @ 15 fps ≈ 0.53s loop, still loop: true. Icarus SLIDE_DURATION = 0.65. Canonical already 0.5.

5
Identical SpriteFrames hid a different game.

Hitboxes, spawn, lives, restart, and tower scenes do not match. Regression tooling would have measured the fork.

Input map

Same action names in Godot. Different physical keys. This is the first thing that makes walk notes lie.

Jump Slide Kick Restart Pause / unused
Canonical

D jump · S kick · Down slide

Esc
↑jump
W
Skick
Djump
↓slide
Spacejump
Enterrestart
Pad

Pause is absent. No gamepad map in project.godot.

Icarus fork

W jump · S slide · D kick

Escpause
↑jump
Wjump
Sslide
Dkick
↓slide
Spacejump
Enter
Padmapped

Restart action absent. Escape + gamepad pause. Walk commands hit this map.

ActionCanonicalIcarus fork
JumpD, Space, UpW, Space, Up + pad
SlideDown onlyS, Down + pad
KickSD + pad
PauseabsentEscape + pad
RestartEnterabsent

Slide clock

Art is the same clip. The timer is not. Scale is 0–0.65s (Icarus duration = 100% of this bar).

0.50s canonical SLIDE_DURATION
~0.53s art loop (8× / 15 fps)
0.65s Icarus timer
Canonical

SLIDE_DURATION = 0.5. Ends when the timer hits 0. Already shorter than the looping clip, so the clock does not overrun the loop.

Icarus fork

SLIDE_DURATION = 0.65. Ends via _try_end_slide() then _hanging_still_in_hit_area(). Timer longer than the 0.53s loop → stutter we diagnosed. Hanging linger has no canonical equivalent.

Same art SpriteFrames slide / run / land in main.tscn are byte-identical. slide still loop: true, 8 frames, speed 15. Default anim: canonical run kick, Icarus run.

player.gd

CanonicalIcarus fork
End slidetimer hits 0_try_end_slide() + hanging linger
Hitboxesswap SlideHitbox / DoubleJumpHitbox / FootCollisionresize one body+hit rect (SLIDE_HIT_H = 40)
Livesplayer.lives + lives_changedGameState.lose_life()
Kickanim only; clears on landstays until run kick finished; breaks Rock*; score 25 + Sfx + kick_pop
Hutevery hut area can drain_hut_corner_is_safe if y < 188
Idle gatenone in physicsGameState.is_running() early return
Hit drift-DRIFT_SPEED-GameState.game_speed
Deathfreeze_world() + pauseGameState.end_game(); delayed pause

Spawn

Canonical rolls. Icarus plays a fixed 12-slot tape that includes hanging and a bridge — neither exists in the RAR game.

Icarus PATTERN (loop)

01rock
02gap
03bamboo
04hanging
05hut
06gap
07rock
08bamboo2
09hanging
10tower
11gap
12bridge

Interval 0.9 scaled by speed. GROUND_Y_TOWER = 121. Scripted watchtower.gd swaps textures 00/02 and 01/03. Rose chips (hanging) and gold (bridge) are fork-only.

Canonical random

Hut 25%
Rock / bamboo / bamboo2

After a hut, tower chance 1/3. After bamboo, tower chance 0.1. Four tower scenes. GROUND_Y_WATCHTOWER = 63. Interval randf_range(1.8, 3.5). No hanging. No GameState gate. No reset / seek_kind.

Scenes / hitboxes

Same path names, different nodes. Boxes are scaled ~0.7 px per Godot unit, feet on the ground line.

Canonical player shapes
body 24×150
foot
slide 90×40
dj 48×58

Body (24, 150) + FootCollision (24, 24) + SlideHitbox (90, 40) + DoubleJumpHitbox (48, 58). _update_hitbox() toggles disabled flags.

Icarus player shapes
body 64×150
hit 44×140

No extra slide/foot/dj nodes. _set_crouch rewrites rectangle sizes pinned to feet. floor_snap_length = 12.

Obstacles

CanonicalIcarus fork
Hurtboxeschild HitArea / BodyHitArea / RoofHitArea / WallHitArea; root often collision_layer = 0root Area2D collision_layer = 2
HutBodyHitArea + RoofHitArea; roof not one-wayone-way roof StaticBody2D + _hut_corner_is_safe
TowersDeck + optional WallHitArea, scale ~1.2, plant y 63scripted posts/panels, scale 0.85, plant y 121
obstacle_hit_area.gdbyte-identical @export var obstacle_type

Files

Filter by substring. Rose = fork-only gameplay that Discord/walk assumed existed.

Only Icarus

Playtest gate + hanging + sfx + world speed.

  • .cursor/skills/po-runner-playtest/SKILL.md
  • assets/music/take5 po runner test.mp3
  • assets/sfx/game_over.wav, hit.wav, jump.wav, kick.wav
  • docs/playtest/CONTRACT.md
  • docs/playtest/mitchell_hut_eave_lip.jpg
  • docs/playtest/rizky_hut_tower_reference.jpg
  • fx/kick_pop.gd
  • obstacle/hanging_bamboo.tscn
  • obstacle/watchtower.gd
  • obstacle/watchtower_bridge.tscn
  • playtest_driver.gd
  • sfx.gd
  • tests/obstacle_contracts.json
  • tests/playtest/scenario_runner.gd
  • tools/gen_sfx.py
  • tools/run_playtest.sh / .bat
  • world_speed.gd
Only canonical

Wall towers + root HUD + graphic-development refs.

  • obstacle/watchtower2.tscn
  • obstacle/watchtower_wall.tscn
  • obstacle/watchtower_wall2.tscn
  • hud.gd — lives from player
  • .cursor/rules/workspace-boundary.mdc
  • graphic development/ hut + watchtower stills
  • graphic development/watchtower_00–03 platform jpgs
  • graphic development/watchtower1/2/3 pngs
  • graphic development/foreground.clip

HUD / loop

Canonical
  • obstacle.gd: SCROLL_SPEED = 315, PROCESS_MODE_PAUSABLE, no "obstacles" group
  • HUD hud.gd: score from GameState, lives from player
  • Restart = reload scene
  • GameState.start_game() in main._ready
  • GameState has no lives
Icarus fork
  • WorldSpeed.px(), GameState.is_running(), add_to_group("obstacles")
  • HUD generated scripts/HUD.gd: GameState.lives; idle “PRESS JUMP”
  • restart_run() without reload
  • --playtest* attaches playtest_driver.gd
  • GameState owns lives, go_idle(), lose_life(), mark_kick_break(), last_hit_*

Dirty in the RAR

Working tree on the extracted drop, not in tip 88b4490:

  • assets/obstacles/stone_00.png (620 → 658 bytes)
  • obstacle/bamboo_bundle.tscn (script uid + unique_id on HitArea)

Do not treat these as the checkpoint. Do not merge, replace the Icarus checkout, email, pack, or git push origin on po-runner.