1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-07 00:22:50 +00:00
nescript/examples/palette_and_background.ne
Claude ad951a835f
examples: adopt the friendly asset syntax
Rewrites every example with non-trivial asset declarations to use
the pleasant QoL syntax introduced in the previous commit. Every
example still compiles to a byte-identical ROM (verified by a
temp-path diff before committing), so the committed `.nes` files
and the 23 emulator goldens are unchanged.

  * platformer.ne — the centerpiece end-to-end demo:
      - `palette Main` goes to grouped form with a shared
        `universal: 0x22` (sky blue), one shared colour per
        sub-palette, and named NES colours throughout; the
        long-standing `$3F10` mirror-trap warning is now handled
        by the parser and the manual pitfall comment is gone.
      - `sprite Tileset` is 15 tiles of ASCII pixel art instead
        of 240 bytes of inline hex.
      - `background Level` uses a `legend { '.': 15, '#': 9, ... }`
        block plus `map:` strings for the 32×30 nametable, and
        `palette_map:` rows for the attribute table. The map
        reads top-down like the rendered screen.
      - SFX latch-once `pitch: 0x30` scalars + `envelope:` alias.
      - `music Theme` uses note names + `tempo: 10` default.
  * audio_demo.ne — scalar sfx pitches, `envelope:` alias, and a
    note-name `C4, E4, G4, ...` music track.
  * palette_and_background.ne — grouped CoolBlues / WarmReds
    palettes with `universal: black` + named colours, plus
    `legend` + `map:` tilemaps for the two backgrounds.
  * sprites_and_palettes.ne — Arrow and Heart sprites rewritten
    as `pixels:` ASCII art.

Along the way, two small parser extensions support the rewrites:

  - `parse_pixel_art` now accepts `a/b/c` as aliases for `#/%/@`,
    matching the vocabulary every NES editor (and our own
    gen_platformer_tiles.rs generator) uses.
  - `palette_map_to_attrs` allows up to 16 metatile rows (the
    full attribute-table coverage, including the off-screen
    bottom half) and auto-replicates row 14 → row 15 when only
    15 rows are supplied so the visible bottom of the screen
    gets consistent sub-palette assignments by default. The old
    15-row cap couldn't match a hand-packed `0xAA` attribute
    table for the last row; the platformer required this to
    stay byte-identical.

`scripts/gen_platformer_tiles.rs` is updated to emit the new
syntax directly (pixel-art `pixels:` block + `legend`/`map:`/
`palette_map:` for the background), so regenerating the
platformer tiles stays a one-liner.

474 lib tests + 64 integration tests pass (3 new parser tests
for `palette_map:` 15/16/17 rows and the `abc` alias). All 23
emulator goldens still match pixel- and sample-for-sample.

https://claude.ai/code/session_01PzaSFj3VahDzxEYTKCESkz
2026-04-13 18:04:21 +00:00

169 lines
6.6 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Palette and Background Demo — shows the `palette` and
// `background` declarations plus runtime `set_palette` /
// `load_background` swaps.
//
// The program declares two palettes and two backgrounds. The
// *first* of each is loaded at reset time (before rendering is
// enabled) so the title screen comes up with the right colours
// and nametable on frame 0. A frame counter then toggles between
// the two palettes every 90 frames and between the two backgrounds
// every 180 frames, exercising the vblank-safe update path.
//
// Background tile 0 is the compiler's built-in smiley face (see
// the default CHR in `src/linker/mod.rs`). Tile indices 1+ would
// need matching `sprite` declarations with `chr:` data — we stick
// to tile 0 so the example is self-contained.
//
// Build: cargo run -- build examples/palette_and_background.ne
// Output: examples/palette_and_background.nes
game "Palette + BG Demo" {
mapper: NROM
mirroring: horizontal
}
// ── Palettes ────────────────────────────────────────────────
//
// Both palettes are authored in grouped form: one shared
// `universal:` colour feeds every sub-palette's index-0 slot,
// which auto-fixes the `$3F10/$3F14/$3F18/$3F1C` PPU mirror
// trap (the parser handles the packing). Each `bgN` / `spN`
// field only needs three colours — the universal is prepended.
//
// Named NES colours resolve to master-palette indices via the
// curated name table; see `docs/language-guide.md` for the full
// list. Hex byte literals (`0x01`, `0x14`, …) still work if you
// need a colour that doesn't have a name.
palette CoolBlues {
universal: black
bg0: [dk_blue, blue, sky_blue] // deep blue highlights
bg1: [indigo, royal_blue, periwinkle] // cool purples
bg2: [dk_cyan, cyan, lt_cyan] // aquas
bg3: [dk_teal, teal, lt_teal] // teal accents
sp0: [dk_blue, blue, sky_blue] // matches bg0
sp1: [red, orange, white] // warm accent sprites
sp2: [magenta, lt_magenta, pale_pink] // magenta sprites
sp3: [dk_teal, teal, lt_teal] // matches bg3
}
palette WarmReds {
universal: black
bg0: [dk_red, red, lt_red] // fiery warm bg
bg1: [brown, dk_orange, orange] // autumnal
bg2: [dk_olive, olive, yellow] // muted yellows
bg3: [dk_green, green, lt_green] // greens for contrast
sp0: [dk_red, red, lt_red] // matches bg0
sp1: [red, orange, white] // highlight sprites
sp2: [magenta, lt_magenta, pale_pink] // pinks
sp3: [dk_teal, teal, lt_teal] // cool accent
}
// ── Backgrounds ─────────────────────────────────────────────
//
// A 32×30 nametable is 960 tile bytes + 64 attribute bytes. We
// only specify the first few tiles per background — the rest of
// the nametable is zero-padded by the asset pipeline, so
// undeclared cells render as tile 0 (the built-in smiley). The
// attribute table is left empty so every 16×16 metatile uses
// background sub-palette 0 (which is where CoolBlues / WarmReds
// put their headline colour).
//
// TitleScreen paints a short ribbon of tile 0 across row 14 (the
// middle of the screen). StageOne paints a diagonal stripe from
// top-left to bottom-right so the swap is visually obvious.
background TitleScreen {
// Both backgrounds resolve to an all-zero nametable for this
// demo — the visual difference between them comes from the
// palette swap triggered alongside `load_background`. We still
// author them with a `legend` + `map:` block here so the demo
// exercises the pleasant syntax the same way a real game
// would. The '.' legend entry alone is enough to fill an
// all-tile-0 background; every row is padded to 32 columns
// automatically by the parser.
legend { ".": 0 }
map: [
"................................", // row 0
"................................", // row 1
"................................", // row 2
"................................", // row 3
"................................", // row 4
"................................", // row 5
"................................", // row 6
"................................", // row 7
"................................", // row 8
"................................", // row 9
"................................", // row 10
"................................", // row 11
"................................", // row 12
"................................", // row 13
"................................", // row 14 (ribbon)
"................................" // row 15
]
}
background StageOne {
// StageOne's visual difference comes from the palette swap —
// the tile grid is still the built-in smiley everywhere. We
// only declare 4 rows to prove the parser's zero-padding
// still works with the new tilemap form.
legend { ".": 0 }
map: [
"................................",
"................................",
"................................",
"................................"
]
}
var tick: u8 = 0
var phase: u8 = 0
on frame {
tick += 1
// Every 90 frames toggle the palette between the two.
if tick >= 90 {
tick = 0
phase += 1
if phase == 4 {
phase = 0
}
}
// Phase 0: CoolBlues + TitleScreen
// Phase 1: WarmReds + TitleScreen
// Phase 2: CoolBlues + StageOne
// Phase 3: WarmReds + StageOne
//
// We reissue the set_palette / load_background call on every
// phase transition (the single-frame condition also keeps the
// golden stable — the jsnes test only renders a handful of
// frames and the comparison runs without any controller input).
if tick == 1 {
if phase == 0 {
set_palette CoolBlues
load_background TitleScreen
}
if phase == 1 {
set_palette WarmReds
load_background TitleScreen
}
if phase == 2 {
set_palette CoolBlues
load_background StageOne
}
if phase == 3 {
set_palette WarmReds
load_background StageOne
}
}
// Draw a single sprite tracking the tick so the sprite still
// moves on screen and the golden diff tests spot regressions
// in OAM DMA alongside the palette/background path.
draw Smiley at: (tick + 60, 120)
}
start Main