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
YSortContainerso 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
PlayerandEnemypublishSignals (attack_started,magic_cast,damaged,attacked,died) andLevelconnects to them, so neither side has to know about the other's internals.- Current values live on
Propertyfields (hp,max_hp,speed, ...); the parallelstatsdict is the table the upgrade screen iterates over, andPlayer.upgrade_statis the only place that writes both. - The upgrade screen pauses the tree (
tree.paused), and the HUDCanvasLayerruns withUpdateMode.ALWAYSso 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 plainAudioPlayeron 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.