1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 00:45:38 +00:00

examples: move state-scoped globals to state-local in coin_cavern + platformer

Both examples declared their gameplay variables at the top level
even though every read and write happened inside one specific state.
That pattern hid the overlay feature from new users and kept the
state-local code path from being exercised outside the dedicated
`state_machine.ne` demo (which is how the "state-locals silently
drop their writes" bug survived so long).

`coin_cavern.ne`: the five Playing-only physics/position/inventory
vars (`player_x`, `player_y`, `player_vy`, `on_ground`,
`coins_left`) move onto Playing's state block. `score` stays
global because GameOver-era code could reasonably grow to read it.
The `on_enter` body loses its redundant resets — the declared
initializers on the state-locals re-run on every entry, so
retrying after `transition Title` comes back to a fresh state.

`platformer.ne`: player physics, camera, liveness, animation
phase, and the autopilot budget (`player_y`, `on_ground`,
`rise_count`, `fall_vy`, `camera_x`, `anim_tick`, `alive`,
`auto_jumps`) all move onto Playing. `frame_tick` and
`stomp_count` stay global — Title reads the former to
auto-advance, GameOver reads the latter to tally coins on the
death screen. The analyzer now overlays Title's `blink`,
Playing's eight physics vars, and GameOver's `linger` starting
at the same ZP byte (`$1A`), so the three scenes share a
9-byte window instead of each claiming their own slots.

Byte-level ROM bytes for both examples shift because variable
addresses moved. Video goldens stay pixel-identical (the harness
doesn't see Playing in coin_cavern, and the pre-transition
Title→Playing timing in platformer is preserved); the platformer
audio hash needed one more refresh because the now-slightly-shorter
reset prologue shifts APU writes within each frame.

https://claude.ai/code/session_015kvJu3iEFLSRJoShPBfm3X
This commit is contained in:
Claude 2026-04-17 11:58:02 +00:00
parent 73dcf08c7a
commit 40dec7907a
No known key found for this signature in database
5 changed files with 36 additions and 29 deletions

View file

@ -23,12 +23,7 @@ const COIN_X: u8 = 180
const COIN_Y: u8 = 100
// Global variables
var player_x: u8 = 40
var player_y: u8 = 200
var player_vy: u8 = 0
var on_ground: u8 = 1
var score: u8 = 0
var coins_left: u8 = 3
// Helper function: clamp a value to screen bounds
fun clamp_x(val: u8) -> u8 {
@ -53,11 +48,20 @@ state Title {
// Main gameplay state
state Playing {
// Physics and position live with the state: they're only
// meaningful while Playing is active, and the analyzer
// overlays them with the locals of the Title and GameOver
// states so the idle scenes don't reserve bytes they never
// touch. Initializers re-run on every entry, so dying and
// retrying starts the player back on the ground.
var player_x: u8 = 40
var player_y: u8 = 200
var player_vy: u8 = 0
var on_ground: u8 = 1
var coins_left: u8 = 3
on enter {
player_x = 40
player_y = 200
score = 0
coins_left = 3
}
on frame {

Binary file not shown.

View file

@ -367,21 +367,17 @@ const AUTOPILOT_JUMPS: u8 = 2
// ── Game state ──────────────────────────────────────────────
// Player physics
var player_y: u8 = 160
var on_ground: u8 = 1
var rise_count: u8 = 0 // frames of upward motion remaining
var fall_vy: u8 = 0 // gravity accumulator
// `frame_tick` is shared: Title reads it to auto-advance, Playing
// reads it for animation phasing. `stomp_count` bridges
// Playing → GameOver so the death screen can tally coins. The
// rest — player physics, camera, liveness, autopilot budget —
// are only meaningful while Playing is running, so they live on
// Playing's state block and overlay with the Title / GameOver
// locals (`blink`, `linger`) at the same bytes.
// World/camera
var camera_x: u8 = 0
// Cross-state scratch
var frame_tick: u8 = 0 // free-running frame counter
var anim_tick: u8 = 0 // visual animation phase
// Gameplay
var alive: u8 = 1 // 0 = dying/dead, 1 = playable
var stomp_count: u8 = 0 // successful enemy stomps this life
var auto_jumps: u8 = 0 // proximity pre-jumps used this life
// ── Helper functions ────────────────────────────────────────
@ -520,17 +516,24 @@ state Title {
}
state Playing {
// Physics, camera, liveness, and autopilot budget — all of
// this is Playing-only. Declaring them inside the state block
// lets the analyzer overlay them with Title.blink and
// GameOver.linger; each variable's initializer re-runs on
// entry, so the retry loop starts each life on the ground
// with a fresh autopilot budget without any manual reset.
var player_y: u8 = GROUND_Y
var on_ground: u8 = 1
var rise_count: u8 = 0
var fall_vy: u8 = 0
var camera_x: u8 = 0
var anim_tick: u8 = 0
var alive: u8 = 1
var auto_jumps: u8 = 0
on enter {
player_y = GROUND_Y
on_ground = 1
rise_count = 0
fall_vy = 0
camera_x = 0
frame_tick = 0
anim_tick = 0
alive = 1
stomp_count = 0
auto_jumps = 0
start_music Theme
}

Binary file not shown.

View file

@ -1 +1 @@
8f18a5d1 132084
2b03b3ec 132084