1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 08:55:38 +00:00
nescript/examples/sha256/entering_state.ne
Claude ba23f8578a
examples/sha256: interactive SHA-256 hasher with on-screen keyboard
An end-to-end FIPS 180-4 SHA-256 hasher running entirely on the NES.
The player types up to 16 ASCII characters on a 5x8 on-screen
keyboard, presses Enter, and the program computes and displays the
64-character hex digest.

Layout (`examples/sha256/*.ne`):
  constants.ne         layout + K[64] / H_INIT[8] tables
                       (declared as `var` with init_array because the
                       v0.1 compiler treats `const u8[N] = [...]` as
                       a no-op — noted in the file)
  assets.ne            44-tile Tileset (A..Z, 0..9, punctuation,
                       special keys, cursor) shared between BG and
                       sprite layers
  background.ne        static nametable (title, labels, keyboard
                       grid) painted at reset
  state.ne             globals
  sha_core.ne          32-bit byte primitives (copy, xor, and, add,
                       not, rotr, shr) in inline asm + sigma/Sigma
                       mixers + schedule/round steps + fold
  render.ne            OAM helpers for cursor, input buffer, and
                       64-nibble digest
  keyboard.ne          key dispatch table
  entering_state.ne    cursor navigation + typing + auto-demo
  computing_state.ne   phased driver (48 schedule steps + 64 rounds
                       + fold across ~30 frames at 4 iterations each)
  showing_state.ne     renders the 256-bit digest as 8 rows of 8
                       sprite glyphs

Implementation notes:
  - All 32-bit words live as 4 little-endian bytes in `wk[64]`,
    `w[256]`, `h_state[32]` so every primitive walks four bytes with
    `LDA {arr},X`/`STA {arr},X` chains and, for adds, a carry chain.
  - Every primitive reads its parameters straight out of the
    transport slots `$04`/`$05` rather than `{dst}`/`{src}`
    substitutions: the inline-asm resolver looks parameters up in
    the analyzer's allocation table but the codegen spills them to a
    different per-function RAM slot, so `{dst}` would resolve to a
    ZP slot nothing ever writes to. Bypassing the substitution
    entirely sidesteps the issue without a compiler change.
  - Rotate-right by any amount is a byte-rotate loop plus a bit-
    rotate loop so the 10 SHA amounts (2, 6, 7, 11, 13, 17, 18, 19,
    22, 25) all compile to a handful of chained `ROR`s.
  - The headless jsnes golden auto-types "NES" after 1 s of idle and
    captures its SHA-256 digest
    AE9145DB5CABC41FE34B54E34AF8881F462362EA20FD8F861B26532FFBB84E0D
    — byte-identical to `shasum` / `hashlib.sha256(b"NES")`.

Build: `cargo run --release -- build examples/sha256.ne`

https://claude.ai/code/session_01FRmSBruVWCufm3LsUVMs8v
2026-04-16 14:02:58 +00:00

126 lines
4.1 KiB
Text

// sha256/entering_state.ne — the interactive typing state.
//
// Reads controller 1 each frame, moves the keyboard cursor on
// D-pad presses, types a character on A, deletes a character on
// B, and starts compression on START (or when the cursor is
// over the ↵ key and A is pressed). SELECT clears the buffer at
// any time so the user can start over.
//
// If the user doesn't press anything in the first AUTO_DELAY
// frames, the handler auto-types DEMO_TEXT ("NES") and
// transitions to Computing so the jsnes golden always captures
// a fully-rendered hash.
state Entering {
on enter {
kb_row = 0
kb_col = 0
idle_timer = 0
debounce = 0
blink_timer = 0
}
on frame {
// ── Debounce + blink tick ───────────────────────
if debounce > 0 {
debounce -= 1
}
blink_timer += 1
if blink_timer >= 40 {
blink_timer = 0
}
// ── D-pad navigation ────────────────────────────
var pressed: u8 = 0
if debounce == 0 {
if button.left {
if kb_col > 0 {
kb_col -= 1
} else {
kb_col = KB_COLS - 1
}
debounce = 8
pressed = 1
} else if button.right {
kb_col += 1
if kb_col >= KB_COLS {
kb_col = 0
}
debounce = 8
pressed = 1
} else if button.up {
if kb_row > 0 {
kb_row -= 1
} else {
kb_row = KB_ROWS - 1
}
debounce = 8
pressed = 1
} else if button.down {
kb_row += 1
if kb_row >= KB_ROWS {
kb_row = 0
}
debounce = 8
pressed = 1
} else if button.a {
// Press the key under the cursor. Backspace
// and enter dispatch to their actions; any
// other character is appended to `msg`.
var ch: u8 = current_key()
if ch == KEY_BKSP {
input_backspace()
} else if ch == KEY_ENTER {
if msg_len > 0 {
transition Computing
}
} else {
input_append(ch)
}
debounce = 10
pressed = 1
} else if button.b {
input_backspace()
debounce = 10
pressed = 1
} else if button.start {
if msg_len > 0 {
transition Computing
}
debounce = 10
pressed = 1
} else if button.select {
input_clear()
debounce = 10
pressed = 1
}
}
// ── Idle / auto-demo ────────────────────────────
if pressed == 1 {
idle_timer = 0
} else {
if idle_timer < 255 {
idle_timer += 1
}
if idle_timer == AUTO_DELAY {
if msg_len == 0 {
// Auto-type "NES" → SHA-256 digest visible
// in the headless jsnes golden.
input_append(0x4E) // N
input_append(0x45) // E
input_append(0x53) // S
transition Computing
}
}
}
// ── Render ──────────────────────────────────────
// Input buffer takes 16 sprites, cursor 1 (when on).
// Well under the 64-sprite OAM budget.
draw_input()
if blink_timer < 25 {
draw_cursor(1)
}
}
}