Files
simvx/examples/ports/clear_code_zelda

Clear Code Zelda: SimVX port

SimVX port of Clear Code's "Zelda" Pygame ARPG.

Top-down ARPG with sword combat, magic, enemy AI, particles, y-sort, and a stat-upgrade screen. Source assets are CC0 (Ninja Adventure pack) and reused unchanged.

Run

# from the repo root
uv run python examples/ports/clear_code_zelda/main.py            # interactive
uv run python examples/ports/clear_code_zelda/main.py --test     # headless smoke run (12 frames)
uv run python examples/ports/clear_code_zelda/capture.py         # headless screenshots (frame 30/60/120)
uv run python examples/ports/clear_code_zelda/harness.py         # scripted-input capture (15 stages)
uv run simvx export web examples/ports/clear_code_zelda/main.py \
    -o /tmp/clear_code_zelda.html

Controls

The game opens on a title screen; SPACE, ENTER or a click starts it. Everything is playable from the keyboard or entirely with a pointer (mouse or touch).

Key Action
WASD / arrows Walk (8-direction)
SPACE Sword attack (melee)
LEFT/RIGHT CTRL Cast magic (heal / flame)
Q Cycle weapon (sword/lance/axe/rapier/sai)
E Cycle spell (heal/flame)
M Open / close upgrade menu
LEFT / RIGHT Upgrade selection (in menu)
SPACE / ENTER Buy upgrade (in menu)
ESC Quit

With a pointer, drag the stick in the lower-left corner to walk and tap the buttons in the lower-right: ATK swings, MAG casts, Q and E swap weapon and spell, M opens the upgrade screen. In the upgrade screen, a click selects a panel and a second click on the selected panel buys it.

What works

  • 57x50 tile map loaded from upstream CSV layouts (boundary, grass, objects, entities)
  • Player movement with 12-direction animation set (walk/idle/attack × 4 facings)
  • Sword attack with directional weapon sprite + cooldown
  • 5 weapon types with different damage/cooldown values, swappable on-the-fly
  • 2 spells (heal restores HP, flame is a projectile)
  • 4 enemy types (squid, raccoon, spirit, bamboo) with idle/move/attack states
  • Enemy AI: notice radius → chase via NavGrid2D → attack radius
  • Y-sort drawing via YSortContainer so player and enemies depth-sort by y
  • Particle effects: leaves on grass slash, magic sparkles on heal/flame, monster-specific death puffs
  • Hit feedback: the sprite flashes on damage and enemies are knocked back
  • Soundtrack plus sword, hit, heal, flame, monster-attack and death SFX through AudioPlayer
  • HUD: HP bar, energy bar, kills counter, XP counter, weapon/magic indicators, help strip
  • Upgrade screen: 5 panels (health/energy/attack/magic/speed) with selection, cost scales 1.4x per buy
  • AABB collision against invisible boundary tiles + objects (grass is walkable but slashable)
  • Web export

How it is put together

  • Player and Enemy publish Signals (attack_started, magic_cast, damaged, attacked, died) and Level connects to them, so neither side has to know about the other's internals.
  • Current values live on Property fields (hp, max_hp, speed, ...); the parallel stats dict is the table the upgrade screen iterates over, and Player.upgrade_stat is the only place that writes both.
  • The upgrade screen pauses the tree (tree.paused), and the HUD CanvasLayer runs with UpdateMode.ALWAYS so the overlay can still close itself.

What's intentionally simplified

  • No procedural floor texture. Upstream uses a 3648×3200 ground.png (~180 KB). The port draws a flat green rect under the world to keep the web bundle small and avoid the texture-cache for one giant repeating image. Visually matches at a glance.
  • Flame projectile is a single trail of looping puffs, not the upstream's chained particle anim with sparkle.
  • Sound is non-positional. The camera is faked by translating the y-sort container rather than with a Camera2D, so there is no 2D listener to pan against; every effect plays through a plain AudioPlayer on the SFX bus, with the soundtrack on the Music bus.
  • Camera is unsmoothed. Player stays exactly centred; the upstream has no smoothing either.
  • "Death" wraps to full-heal, not a Game Over screen; the upstream has the same loop.