mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 17:06:04 +00:00
assets: auto-generate CHR data from @nametable() PNG sources
`background Foo @nametable("file.png")` previously decoded the PNG
into a tile-index table and an attribute table but left CHR
generation to the user — they had to supply matching tiles via a
separate `sprite Tileset @chr(...)` declaration in the same
deduplication order, which was both error-prone and the main thing
keeping the shortcut form from being a one-liner.
The CHR pipeline now closes the gap. `png_to_nametable_with_chr`
returns a `PngNametable` carrying the tile-index table, the
attribute table, *and* a per-tile CHR blob encoded with the same
brightness-bucketing `png_to_chr` already uses for sprites. The
resolver passes `next_sprite_tile` (computed from the resolved
sprite list) so each background's CHR allocation slots in
immediately after the sprite range, and rewrites the nametable
indices to point at the actual physical tile numbers. The linker
copies each background's `chr_bytes` into CHR ROM at
`chr_base_tile * 16`, so the final image renders without any
user-supplied CHR.
`BackgroundData` carries `chr_bytes` and `chr_base_tile` so the
linker has everything it needs at a glance. Inline `tiles:` /
`attributes:` declarations leave them empty and behave exactly
like before — that path doesn't auto-generate CHR because the
user is implicitly opting into "I'll provide tiles myself" by
typing the indices out by hand.
The new `examples/auto_chr_background.ne` is a 256×240 grayscale
gradient committed alongside its `auto_chr_bg.png` source; the
emulator harness verifies the rendered output against a
committed golden so a regression in the dedupe/encode/linker
plumbing fails CI loudly. Existing example ROMs are byte-
identical because their backgrounds either have no PNG source or
already provided their own CHR.
https://claude.ai/code/session_01KEczoNUX3WmcFLfq6iAQxB
This commit is contained in:
parent
6b080316a4
commit
cc3f7eec7e
15 changed files with 337 additions and 54 deletions
|
|
@ -29,6 +29,7 @@ Open any `.nes` file in an NES emulator ([Mesen](https://www.mesen.ca/), [FCEUX]
|
|||
| `uxrom_user_banked.ne` | UxROM, `bank Foo { fun ... }`, cross-bank trampoline | First example to put real user code inside a switchable bank. The animation step lives in `bank Extras` and is invoked from the fixed-bank state handler via a generated `__tramp_step_animation` stub that selects bank 0, JSRs the body, then restores the fixed bank before returning. |
|
||||
| `uxrom_banked_to_banked.ne` | UxROM, banked → banked cross-bank call | Two `bank Foo { fun ... }` blocks: `step` lives in bank Logic and calls `clamp` in bank Helpers. The trampoline uses `ZP_BANK_CURRENT + PHA/PLA` to save and restore the caller's bank, so the same per-callee stub works whether the caller is in the fixed bank or another switchable bank. |
|
||||
| `palette_and_background.ne` | palette, background, set_palette, load_background | Reset-time initial load plus vblank-safe runtime swaps |
|
||||
| `auto_chr_background.ne` | `background @nametable(...)` with auto-CHR | First example to use the `@nametable("file.png")` shortcut without supplying any matching CHR data. The resolver dedupes the PNG's 8×8 cells, encodes them via the same brightness-bucketing the sprite CHR encoder uses, and slots them into CHR ROM at the next free tile slot. The committed `auto_chr_bg.png` is a 256×240 grayscale gradient that exercises ~50 unique tiles. |
|
||||
| `friendly_assets.ne` | named colours, grouped palette, pixel art, tilemap+legend, palette_map, scalar sfx pitch, note-name music | Exercises every "friendlier" asset syntax at once — the `palette` uses `bg0..sp3` + a shared `universal:`, the sprite is authored as ASCII pixel art, the background uses a `legend { ... } + map:` tilemap with a `palette_map:` for attributes, the sfx uses a scalar `pitch:` + `envelope:` alias, and the music uses note names (`C4, E4 40, rest 10`) with a `tempo:` default. |
|
||||
| `noise_triangle_sfx.ne` | `channel: noise`, `channel: triangle` on `sfx` blocks | Demonstrates the noise and triangle sfx channels. Declares one noise burst and one triangle bass note, plays each on a timer so the emulator harness captures both the pixel output and the APU state. |
|
||||
| `sfx_pitch_envelope.ne` | varying-pitch pulse SFX | A 16-frame frequency sweep written as a per-frame `pitch:` array on a Pulse-1 sfx. The compiler emits a separate `__sfx_pitch_<name>` blob and gates the audio tick's pitch update path on the `__sfx_pitch_used` marker, so programs that stick to the scalar `pitch:` form still get byte-identical ROM output. |
|
||||
|
|
|
|||
48
examples/auto_chr_background.ne
Normal file
48
examples/auto_chr_background.ne
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// Auto-CHR background — first NEScript example to use the
|
||||
// `@nametable("file.png")` shortcut without supplying any matching
|
||||
// CHR data. Previously the resolver decoded the PNG into a tile
|
||||
// index table but left CHR generation to the user; the new
|
||||
// `png_to_nametable_with_chr` helper also generates the per-tile
|
||||
// CHR bytes from the same brightness-bucketing code that
|
||||
// `png_to_chr` uses for sprite imports, then the asset resolver
|
||||
// allocates contiguous CHR-ROM tile indices starting after the
|
||||
// last sprite tile and rewrites the nametable to point at them.
|
||||
//
|
||||
// `auto_chr_bg.png` is a 256×240 grayscale gradient rendered to
|
||||
// roughly fifty distinct tiles — enough variety to exercise the
|
||||
// dedupe + CHR generation path but well within the 256-tile cap.
|
||||
//
|
||||
// Build: cargo run -- build examples/auto_chr_background.ne
|
||||
|
||||
game "Auto CHR Background" {
|
||||
mapper: NROM
|
||||
}
|
||||
|
||||
// Grayscale palette with every background sub-palette set to the
|
||||
// same three shades. The PNG-to-attribute helper buckets each
|
||||
// 16×16 quadrant by average brightness and assigns sub-palette
|
||||
// 0..3, so we need *every* bg sub-palette to carry the same
|
||||
// dk_gray / lt_gray / white triple — otherwise quadrants that
|
||||
// pick a non-zero palette would render as the universal colour.
|
||||
palette Main {
|
||||
universal: black
|
||||
bg0: [dk_gray, lt_gray, white]
|
||||
bg1: [dk_gray, lt_gray, white]
|
||||
bg2: [dk_gray, lt_gray, white]
|
||||
bg3: [dk_gray, lt_gray, white]
|
||||
sp0: [black, black, black]
|
||||
sp1: [black, black, black]
|
||||
sp2: [black, black, black]
|
||||
sp3: [black, black, black]
|
||||
}
|
||||
|
||||
// `@nametable` is the shortcut form: no `tiles:` / `attributes:`
|
||||
// body, and the resolver fills both — and now CHR data too — from
|
||||
// the PNG itself.
|
||||
background Stage @nametable("auto_chr_bg.png")
|
||||
|
||||
on frame {
|
||||
wait_frame
|
||||
}
|
||||
|
||||
start Main
|
||||
BIN
examples/auto_chr_background.nes
Normal file
BIN
examples/auto_chr_background.nes
Normal file
Binary file not shown.
BIN
examples/auto_chr_bg.png
Normal file
BIN
examples/auto_chr_bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
Loading…
Add table
Add a link
Reference in a new issue