Skip to content

Commit fbfe16c

Browse files
cursoragentomiq
andcommitted
Add gfx_canvas_demo example and Playwright coverage
- examples/gfx_canvas_demo.bas: PETSCII + LOADSPRITE/DRAWSPRITE sample - examples/gfx_canvas_demo.png (8x8 red) and web/gfx_canvas_demo.png for HTTP fetch in tests - wasm_browser_canvas_test: load demo source, fetch PNG into MEMFS, assert sprite pixel - README, gfx-canvas-parity.md, CHANGELOG: document usage Co-authored-by: Chris Garrett <chris@chrisg.com>
1 parent 771426b commit fbfe16c

7 files changed

Lines changed: 55 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- **Tutorial embedding**: `make basic-wasm-modular``web/basic-modular.js` + `basic-modular.wasm` (`MODULARIZE=1`, `createBasicModular`). `web/tutorial-embed.js` mounts multiple terminal-style interpreters per page (`CbmBasicTutorialEmbed.mount`). Guide: `docs/tutorial-embedding.md`; example: `web/tutorial-example.html`. Test: `make wasm-tutorial-test`.
1313
- **Virtual FS upload/export**: `web/vfs-helpers.js``CbmVfsHelpers.vfsUploadFiles`, `vfsExportFile`, `vfsMountUI` (browser → MEMFS and MEMFS → download). Wired into `web/index.html`, `web/canvas.html`, and tutorial embeds (`showVfsTools`). CI WASM artifacts include `vfs-helpers.js`.
1414
- **Canvas GFX parity with basic-gfx**: `SCREEN 1` bitmap rendering; software PNG sprites (`gfx/gfx_software_sprites.c`, vendored `gfx/stb_image.h`) replacing the old WASM sprite stubs; compositing order matches Raylib (base then z-sorted sprites). `web/canvas.html` draws `#OPTION border` padding from `Module.wasmGfxBorderPx` / `wasmGfxBorderColorIdx`. Guide: `docs/gfx-canvas-parity.md`. `tests/wasm_browser_canvas_test.py` covers bitmap + sprite smoke.
15+
- **Example**: `examples/gfx_canvas_demo.bas` + `gfx_canvas_demo.png` (and `web/gfx_canvas_demo.png` for Playwright fetch); canvas test runs the full demo source.
1516

1617
- **80-column option (terminal + basic-gfx + WASM canvas)**
1718
- **Terminal**: `#OPTION columns N` / `-columns N` (1–255); default 40. Comma/TAB zones scale: 10 at 40 cols, 20 at 80 cols. `#OPTION nowrap` / `-nowrap`: disable wrapping.

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ The `examples` folder (included in release archives) contains:
407407
- `examples/dungeon.bas`: larger PETSCII dungeon-style map with `POKE`/screen RAM and custom charset data (terminal or `basic-gfx` with `-petscii`).
408408
- `examples/gfx_game_shell.bas`: **basic-gfx** tutorial game — tile `DATA`, `POKE` map, `LOADSPRITE`/`DRAWSPRITE` for 8×8 PNG actors (`player.png`, `enemy.png`) and HUD (`hud_panel.png`), `INKEY$()` + enemy chase. Run: `./basic-gfx examples/gfx_game_shell.bas`.
409409
- `examples/gfx_sprite_hud_demo.bas`: minimal **DRAWSPRITE** over PETSCII (semi-transparent `hud_panel.png`); comments explain pixel vs text-row coordinates.
410+
- `examples/gfx_canvas_demo.bas` + `examples/gfx_canvas_demo.png`: small **PETSCII + PNG sprite** sample for **basic-gfx** or **`web/canvas.html`** (upload the PNG to the virtual filesystem, or use `web/gfx_canvas_demo.png` when serving `web/`).
410411
- `examples/colaburger_viewer.bas`, `examples/gfx_colaburger_viewer.bas`, and `examples/colaburger.seq`: PETSCII .seq file viewer.
411412
- **.seq files** are sequential dumps of PETSCII screen codes (e.g. from BBS logs or PETSCII art).
412413
- The terminal viewer reads the file byte-by-byte with `GET#`, prints each byte via `CHR$`, and wraps after
@@ -508,13 +509,13 @@ make basic-gfx
508509
make basic-wasm
509510
# Optional: MODULARIZE terminal build for embedding multiple interpreters (see docs/tutorial-embedding.md)
510511
make basic-wasm-modular
511-
# Optional: 40×25 PETSCII canvas (GfxVideoState; no Raylib/sprites in browser)
512+
# Optional: 40×25 PETSCII canvas (GfxVideoState; bitmap + PNG sprites in browser)
512513
make basic-wasm-canvas
513514
```
514515

515516
Produces `web/basic.js` and `web/basic.wasm` (Asyncify-enabled), **`web/basic-modular.js`** / **`web/basic-modular.wasm`** for **`tutorial-embed.js`**, and optionally `web/basic-canvas.js` / `web/basic-canvas.wasm` plus **`web/canvas.html`** for a Canvas 2D PETSCII screen. Serve `web/` over HTTP (e.g. `cd web && python3 -m http.server 8080`) and open in a browser. The terminal demo uses an inline **INPUT** field and keyboard routing for **GET** / **INKEY$**; see `web/README.md` for details.
516517

517-
**PETSCII canvas** (no Raylib): `make basic-wasm-canvas` produces `web/basic-canvas.js`, `web/basic-canvas.wasm`, and `web/canvas.html`. The page refreshes the canvas during `SLEEP` and loops via a shared RGBA framebuffer.
518+
**PETSCII canvas** (no Raylib): `make basic-wasm-canvas` produces `web/basic-canvas.js`, `web/basic-canvas.wasm`, and `web/canvas.html`. The page refreshes the canvas during `SLEEP` and loops via a shared RGBA framebuffer (PETSCII, `SCREEN 1` bitmap, and **`LOADSPRITE`/`DRAWSPRITE`** like **basic-gfx**). Try **`examples/gfx_canvas_demo.bas`**: paste into the canvas page, use **Upload to VFS** to add **`gfx_canvas_demo.png`** (same file is copied to **`web/gfx_canvas_demo.png`** for local fetch tests), then Run.
518519

519520
**Automated WASM smoke tests** (headless Chromium via Playwright): install `pip install -r tests/requirements-wasm.txt`, run `python3 -m playwright install chromium`, then `make wasm-test`, `make wasm-canvas-test`, and **`make wasm-tutorial-test`**. These run in GitHub Actions for **tagged releases** and the **nightly** workflow; artifacts include `cbm-basic-wasm.tar.gz` (terminal, modular tutorial files, and canvas).
520521

docs/gfx-canvas-parity.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,8 @@ The browser build `make basic-wasm-canvas` targets the same **GfxVideoState** mo
3131

3232
## Tests
3333

34-
`make wasm-canvas-test` checks **bitmap mode** (`SCREEN 1` + `PSET`) and **sprite overlay** (fetch `testfixtures/red8x8.png` into MEMFS, `LOADSPRITE` + `DRAWSPRITE`, sample a red pixel).
34+
`make wasm-canvas-test` checks **bitmap mode** (`SCREEN 1` + `PSET`), **sprite overlay** (fetch `testfixtures/red8x8.png` into MEMFS), and the **`examples/gfx_canvas_demo.bas`** program (fetch `gfx_canvas_demo.png` from the static `web/` copy into MEMFS, assert a red pixel inside the drawn sprite).
35+
36+
## Example program
37+
38+
`examples/gfx_canvas_demo.bas` — two `PRINT` lines plus `LOADSPRITE` / `DRAWSPRITE` for `gfx_canvas_demo.png` (8×8 solid red PNG, same asset as `tests/fixtures/red8x8.png`).

examples/gfx_canvas_demo.bas

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
1 REM Browser canvas demo: PETSCII + PNG overlay (same LOADSPRITE/DRAWSPRITE as basic-gfx).
2+
2 REM Native: ./basic-gfx examples/gfx_canvas_demo.bas (needs gfx_canvas_demo.png beside this file)
3+
3 REM WASM: put gfx_canvas_demo.png on MEMFS root (e.g. canvas Upload, or fetch in tests).
4+
10 PRINT CHR$(14)
5+
20 PRINT "{CLR}{WHT}CANVAS DEMO{CYN} - red 8x8 PNG at pixel (100,100)"
6+
30 PRINT "{GREY2}SPRITE z=50 draws above text."
7+
40 LOADSPRITE 0,"gfx_canvas_demo.png"
8+
50 DRAWSPRITE 0,100,100,50
9+
60 SLEEP 120
10+
70 END

examples/gfx_canvas_demo.png

75 Bytes
Loading

tests/wasm_browser_canvas_test.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
ROOT = Path(__file__).resolve().parents[1]
1414
WEB = ROOT / "web"
15+
GFX_CANVAS_DEMO_BAS = ROOT / "examples" / "gfx_canvas_demo.bas"
1516

1617

1718
def _serve_web() -> tuple[socketserver.TCPServer, int]:
@@ -249,6 +250,41 @@ def main() -> int:
249250
browser.close()
250251
raise RuntimeError(f"sprite test error log: {log_sp!r}")
251252

253+
# Full example: examples/gfx_canvas_demo.bas + gfx_canvas_demo.png (served from web/)
254+
if not GFX_CANVAS_DEMO_BAS.is_file():
255+
browser.close()
256+
raise RuntimeError(f"missing {GFX_CANVAS_DEMO_BAS}")
257+
page.wait_for_function(
258+
"() => !document.getElementById('run').disabled",
259+
timeout=60000,
260+
)
261+
demo_src = GFX_CANVAS_DEMO_BAS.read_text(encoding="utf-8")
262+
page.evaluate(
263+
"""async () => {
264+
const r = await fetch('gfx_canvas_demo.png');
265+
const buf = await r.arrayBuffer();
266+
Module.FS.writeFile('/gfx_canvas_demo.png', new Uint8Array(buf));
267+
}"""
268+
)
269+
page.fill("#program", demo_src)
270+
_click_run(page)
271+
time.sleep(0.8)
272+
demo_px = _canvas_pixel_rgba(page, 104, 104)
273+
dr, dg, db = int(demo_px[0]), int(demo_px[1]), int(demo_px[2])
274+
if dr < 200 or dg > 80 or db > 80:
275+
browser.close()
276+
raise RuntimeError(
277+
f"gfx_canvas_demo: expected red inside sprite at (104,104), got rgba={demo_px!r}"
278+
)
279+
page.wait_for_function(
280+
"() => (window.Module && Module.wasmGfxRunDone === 1)",
281+
timeout=120000,
282+
)
283+
log_demo = page.text_content("#log") or ""
284+
if log_demo.strip():
285+
browser.close()
286+
raise RuntimeError(f"gfx_canvas_demo error log: {log_demo!r}")
287+
252288
browser.close()
253289
finally:
254290
httpd.shutdown()

web/gfx_canvas_demo.png

75 Bytes
Loading

0 commit comments

Comments
 (0)