From 0708596fc0cc996f9cc607f9ac8065bacb3cab2a Mon Sep 17 00:00:00 2001 From: Manuel Magnabosco Date: Tue, 25 Aug 2026 00:09:50 +0200 Subject: [PATCH 1/2] Import the PAP Render upstream snapshot --- renderer/.github/workflows/ci.yml | 20 + renderer/.gitignore | 13 + renderer/.python-version | 1 + renderer/LICENSE | 21 + renderer/README.md | 249 ++++ renderer/UPSTREAM.md | 68 ++ renderer/appunti.txt | 227 ++++ renderer/classes.py | 422 +++++++ renderer/input/palette.json | 18 + renderer/input/reference.png | Bin 0 -> 6389 bytes renderer/input/scene.json | 62 + renderer/input/sprites.bin | Bin 0 -> 32768 bytes renderer/input/sprites.png | Bin 0 -> 2222 bytes renderer/input/tiles.bin | 1 + renderer/input/tiles.png | Bin 0 -> 1967 bytes renderer/main.py | 19 + renderer/output/.gitkeep | 0 renderer/output/example.png | Bin 0 -> 6389 bytes renderer/project_python.pdf | Bin 0 -> 178065 bytes renderer/pyproject.toml | 14 + renderer/test_data/palette_ok.json | 18 + renderer/test_data/palette_wrong_count.json | 5 + renderer/test_data/palette_wrong_value.json | 18 + renderer/tests.py | 1151 +++++++++++++++++++ renderer/uv.lock | 143 +++ 25 files changed, 2470 insertions(+) create mode 100644 renderer/.github/workflows/ci.yml create mode 100644 renderer/.gitignore create mode 100644 renderer/.python-version create mode 100644 renderer/LICENSE create mode 100644 renderer/README.md create mode 100644 renderer/UPSTREAM.md create mode 100644 renderer/appunti.txt create mode 100644 renderer/classes.py create mode 100644 renderer/input/palette.json create mode 100644 renderer/input/reference.png create mode 100644 renderer/input/scene.json create mode 100644 renderer/input/sprites.bin create mode 100644 renderer/input/sprites.png create mode 100644 renderer/input/tiles.bin create mode 100644 renderer/input/tiles.png create mode 100644 renderer/main.py create mode 100644 renderer/output/.gitkeep create mode 100644 renderer/output/example.png create mode 100644 renderer/project_python.pdf create mode 100644 renderer/pyproject.toml create mode 100644 renderer/test_data/palette_ok.json create mode 100644 renderer/test_data/palette_wrong_count.json create mode 100644 renderer/test_data/palette_wrong_value.json create mode 100644 renderer/tests.py create mode 100644 renderer/uv.lock diff --git a/renderer/.github/workflows/ci.yml b/renderer/.github/workflows/ci.yml new file mode 100644 index 0000000..38b0ff5 --- /dev/null +++ b/renderer/.github/workflows/ci.yml @@ -0,0 +1,20 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + name: Test (pytest) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up uv + uses: astral-sh/setup-uv@v5 + + - name: Run test suite + run: uv run pytest -q diff --git a/renderer/.gitignore b/renderer/.gitignore new file mode 100644 index 0000000..e252dab --- /dev/null +++ b/renderer/.gitignore @@ -0,0 +1,13 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +# Virtual environments +.venv + +# Project notes / assignment (not part of the deliverable) +CLAUDE.md diff --git a/renderer/.python-version b/renderer/.python-version new file mode 100644 index 0000000..6324d40 --- /dev/null +++ b/renderer/.python-version @@ -0,0 +1 @@ +3.14 diff --git a/renderer/LICENSE b/renderer/LICENSE new file mode 100644 index 0000000..2ac5e28 --- /dev/null +++ b/renderer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Manuel Magnabosco + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/renderer/README.md b/renderer/README.md new file mode 100644 index 0000000..dbe89c8 --- /dev/null +++ b/renderer/README.md @@ -0,0 +1,249 @@ +# PAP Render + +[![CI](https://github.com/xtraid/PAP_render/actions/workflows/ci.yml/badge.svg)](https://github.com/xtraid/PAP_render/actions/workflows/ci.yml) + +A command-line pixel art renderer that composites a scene from a tile-based background and a list of sprites, exporting the result as a PNG image. + +**Status**: feature-complete. The code works and is tested; any future changes will be refactoring, cleanup, or optimization. + +## Output + +![example render](output/example.png) + +## Overview + +- 16-color indexed palette (4 bit per pixel) +- Tile sheet: 64 tiles of 32×32 pixels in a 256×256 image +- Sprite sheet: 16 sprites of 64×64 pixels in a 256×256 image +- Frame buffer: 640×480 pixels (tile map 15×20) +- Sprite transformations: flip on x/y axis, rotation by 90/180/270 degrees +- Transparency via a designated palette index + +## Usage + +```bash +python main.py +``` + +| Argument | Description | +|---|---| +| `palette.json` | 16 RGB colors | +| `scene.json` | transparent index, tile map, sprite list | +| `tiles.bin` | packed binary tile sheet (32768 bytes) | +| `sprites.bin` | packed binary sprite sheet (32768 bytes) | +| `output.png` | rendered output | + +## Input Format + +### palette.json + +Array of 16 RGB colors: + +```json +[ + [0, 0, 0], + [255, 0, 0], + ... +] +``` + +### scene.json + +```json +{ + "transparent_index": 0, + "tile_map": [[1, 0, 2, ...], ...], + "sprites": [ + { "id": 0, "x": 100, "y": 80, "flip_x": false, "flip_y": false, "rotation": 0 } + ] +} +``` + +- `transparent_index`: palette index (0–15) treated as transparent for all sprites +- `tile_map`: 15×20 matrix of tile IDs (0–63) +- `sprites`: ordered list — draw order determines z-order + +### .bin files + +Packed binary, 256×256 pixels stored as 32768 bytes. Each byte contains 2 pixels: +- high nibble (bits 7–4): first pixel → palette index 0–15 +- low nibble (bits 3–0): second pixel → palette index 0–15 + +## Architecture + +| Class | Responsibility | Status | +|---|---|---| +| `Palette` | Reads and validates `palette.json`, maps index → RGB | Done | +| `VirtualVRAM` | Loads `.bin` files, decodes nibble-packed pixels into index matrices; exposes `get_tile(id)` and `get_sprite(id)` | Done | +| `SceneParser` | Reads and validates `scene.json`, returns `transparent_index`, `tile_map`, and `sprites` | Done | +| `Blitter` | Composites tiles and sprites onto a 640×480 frame buffer; applies flip/rotation and transparency | Done | +| `RenderingPipeline` | Orchestrates the full render and exports PNG | Done | + +Custom exceptions (`PaletteError`, `VRAMError`, `SceneError`, `BlitterException`, `RenderingException`) are raised for all invalid input cases. `FileNotFoundError` propagates with a descriptive message from all file-loading classes. + +## API Reference + +### Palette + +```python +Palette(path: str) +``` + +| Member | Type | Description | +|---|---|---| +| `data` | `np.ndarray (16, 3) uint8` | Full palette array | +| `__getitem__(idx: int)` | `np.ndarray (3,) uint8` | RGB color at palette index `idx` ∈ [0, 15] | +| `print_palette()` | `None` | Prints palette to stdout | + +Raises `PaletteError` on invalid palette. Raises `FileNotFoundError` if file is missing. + +--- + +### VirtualVRAM + +```python +VirtualVRAM(path_t: str, path_s: str) +``` + +| Member | Type | Description | +|---|---|---| +| `get_tile(idx: int)` | `np.ndarray (32, 32) uint8` | Palette-index matrix for tile `idx` ∈ [0, 63] | +| `get_sprite(idx: int)` | `np.ndarray (64, 64) uint8` | Palette-index matrix for sprite `idx` ∈ [0, 15] | + +Raises `VRAMError` on invalid file size. Raises `FileNotFoundError` if a file is missing. + +--- + +### SceneParser + +```python +SceneParser(path: str) +``` + +| Member | Type | Description | +|---|---|---| +| `transparent_index` | `int` | Palette index treated as transparent, ∈ [0, 15] | +| `tile_map` | `np.ndarray (15, 20) uint8` | Grid of tile IDs | +| `sprites` | `list[dict]` | Ordered sprite list; each dict has `id`, `x`, `y`, `flip_x`, `flip_y`, `rotation` | + +Raises `SceneError` on invalid scene. Raises `FileNotFoundError` if file is missing. + +--- + +### Blitter + +```python +Blitter(vram: VirtualVRAM, asset_type: str, idx: int, transparent_index: int, buffer: np.ndarray) +``` + +| Member | Type | Description | +|---|---|---| +| `transparent_index` | `int` | Palette index treated as transparent | +| `_buffer` | `np.ndarray (480, 640) uint8` | Shared frame buffer written in place | +| `blit_tile(row: int, col: int)` | `None` | Copies tile onto buffer at tilemap cell (`row` ∈ [0,14], `col` ∈ [0,19]) | +| `blit_sprite(x, y, flip_x, flip_y, rotation)` | `None` | Transforms and blits sprite at top-left `(x, y)`; transparent pixels are skipped | +| `_transform(sprite_matrix, flip_x, flip_y, rotation)` | `np.ndarray (64, 64) uint8` | Applies flip then rotation to a sprite matrix | +| `_clip(x: int, y: int)` | `tuple[tuple[slice, slice], tuple[slice, slice]]` | Returns `(dst, src)` slice pairs for a 64×64 sprite at `(x, y)` | + +Raises `BlitterException` on invalid arguments. + +--- + +### RenderingPipeline + +```python +RenderingPipeline(palette_path, scene_path, tiles_path, sprites_path, output_path) +``` + +| Member | Type | Description | +|---|---|---| +| `get_buf()` | `np.ndarray (480, 640) uint8` | classmethod — creates a blank frame buffer | +| `render()` | `None` | Runs full pipeline: compose tiles + sprites, export PNG | +| `_compose(buf: np.ndarray)` | `None` | Fills `buf` with tiles then sprites in scene order | +| `_export(buf: np.ndarray)` | `None` | Maps palette indexes → RGB, saves PNG via Pillow | + +Raises `RenderingException` on pipeline errors. + +## Requirements + +- Python ≥ 3.14 +- `Pillow` used only for PNG export +- `numpy` arrays with `np.uint8` dtype + +## Project Structure + +``` +. +├── main.py # CLI entry point +├── classes.py # Palette, VirtualVRAM, SceneParser, Blitter, RenderingPipeline +├── tests.py # Test suite (144 tests, all passing) +├── input/ # Example input files (palette, scene, tiles, sprites) +├── output/ # Rendered PNG output goes here +├── test_data/ +│ ├── palette_ok.json # Valid 16-color palette +│ ├── palette_wrong_count.json # Only 3 colors (invalid) +│ ├── palette_wrong_value.json # Component > 255 (invalid) +│ ├── test_boundary_values/ # Per-test directories generated at runtime +│ ├── test_vram_load_ok/ # Each contains the files written by that test +│ └── ... # (one subdirectory per test that writes files) +└── pyproject.toml +``` + +## Tests + +```bash +uv run pytest tests.py -v +``` + +144 tests covering all classes: + +**Palette (18 tests)** +- Happy path: load, `__getitem__` first/last, boundary values (0 and 255) +- File errors: file not found, invalid JSON +- Wrong color count: too few, too many, empty +- Wrong color format: fewer than 3 components, more than 3 components +- Out-of-range values: above 255, negative, exact 255 +- `__getitem__` bounds: index 16 and index −1 + +**VirtualVRAM — decode (8 tests)** +- Happy path: load, shape and dtype, all-zeros decode, all-`0xFF` decode, nibble split (`0xAB` → 10, 11) +- File errors: tiles not found, sprites not found +- Wrong size: tiles file too short, sprites file too short + +**VirtualVRAM — get_tile (7 tests)** +- Happy path: shape and dtype, first tile (id 0) all 15, last tile (id 63) all 15 +- Isolation: tile 5 filled, tile 0 untouched +- Errors: non-int id, id 64, id −1 + +**VirtualVRAM — get_sprite (7 tests)** +- Happy path: shape and dtype, first sprite (id 0) all 15, last sprite (id 15) all 15 +- Isolation: sprite 7 filled, sprite 0 untouched +- Errors: non-int id, id 16, id −1 + +**SceneParser (33 tests)** +- Happy path: load, transparent_index, tile_map shape and dtype, sprites list, boundary transparent_index 15, empty sprites, sprite fields +- File errors: file not found, invalid JSON +- Missing keys: transparent_index, tile_map, sprites +- transparent_index errors: not int, too high (16), negative +- tile_map errors: wrong rows, wrong cols, jagged rows, value 64, negative value, value not int +- sprites errors: not a list, missing field, id not int, id out of range, x/y not int, flip_x/flip_y not bool, rotation not int, rotation invalid (45) + +**Blitter (49 tests)** +- init/_validate: valid tile/sprite, all error cases (asset_type, idx, transparent_index) +- blit_tile: correct write and position, all error cases (row/col type and bounds) +- _transform: identity, flip_x, flip_y, rotation 90/180/270, flip_x+y +- _clip: fully inside, centered, clipping on all 4 sides, sprite fully outside frame +- blit_sprite: all-opaque, all-transparent, mixed transparency, position, clipping on all 4 sides, fully outside frame on all 4 sides, transform+clip combined, z-order + +**RenderingPipeline (18 tests)** +- get_buf: shape, dtype, all zeros, independence between calls +- __repr__: all 5 paths present +- _export: file created, image size 640×480, pixel color maps correctly from palette, bad output path raises RenderingException +- _compose: tiles written, full tile_map filled, sprite over tile, transparent sprite not drawn, sprite z-order, sprite transformation applied, sprite clipping at frame edge +- render(): output file created, output size 640×480 + +**main.py (4 tests)** +- `--help` exits with code 0 +- missing arguments exits with non-zero code +- valid input runs and creates output file +- invalid file paths propagate `FileNotFoundError` diff --git a/renderer/UPSTREAM.md b/renderer/UPSTREAM.md new file mode 100644 index 0000000..31179aa --- /dev/null +++ b/renderer/UPSTREAM.md @@ -0,0 +1,68 @@ +# PAP Render upstream snapshot + +The renderer directory vendors one snapshot of the standalone PAP Render +project. It is intentionally isolated from the Tiling Foundry core and does +not contain a nested Git repository. + +## Provenance + +- Upstream: +- Default branch at import: `main` +- Commit: `3eba839fb0bb80a533415eb7c1c793a9edf1a1e1` +- Commit date: 2026-07-01 +- License: MIT; the upstream license is preserved in `LICENSE` +- Import date: 2026-08-25 + +All files tracked by that commit are present. Production code, tests, assets, +lockfile, project metadata, and license are byte-for-byte unchanged. The sole +documentary correction to the imported snapshot is in `README.md`: its stale +test totals said 135, while collection at the pinned commit yields 144 tests. +The overall and affected section totals now say 144, 18 Palette, 33 +SceneParser, and 49 Blitter tests. This `UPSTREAM.md` file is local provenance, +not an upstream file. + +## Verification + +The isolated upstream project requires Python 3.14 or newer. Its committed +`.python-version` selects Python 3.14, and the import verification used CPython +3.14.6. `pyproject.toml` declares NumPy, Pillow, and pytest; `uv.lock` resolves +them to NumPy 2.4.6, Pillow 12.2.0, and pytest 9.0.3 for this environment. + +From `renderer/`, reproduce the locked Python 3.14 environment and run: + +```sh +uv run --locked pytest --collect-only -q +uv run --locked pytest -q +``` + +The import baseline is 144 collected tests and 144 passing tests. Keep the +renderer environment under `renderer/.venv`; its local `.gitignore` excludes +that environment and generated Python/build files. + +The preserved `renderer/.github/workflows/ci.yml` is nested upstream evidence; +GitHub does not run it as a workflow of the containing repository. Adding a +separate top-level renderer CI job is deliberately outside this snapshot-only +import. + +## Updating the snapshot + +Treat an update as a separate reviewed change: + +1. Resolve and record the exact upstream commit before copying files. +2. Inspect its default branch, license, dependency metadata, repository + instructions, tracked files, and collected test count. +3. Run its locked suite in a temporary clone before changing this directory. +4. Replace the snapshot from `git archive` so no upstream `.git` directory is + copied. Preserve this provenance file and document every local difference. +5. Re-run the renderer suite and the Tiling Foundry repository gates, then + inspect the complete diff, secrets, and generated artifacts. + +Do not pull or merge an upstream branch directly into this repository. Do not +move Pillow, NumPy, pytest, or renderer code into the root Python project. + +## Rollback + +Before integration, the feature branch is the rollback boundary. After the +import is integrated, revert the complete import commit with `git revert`; +after a later snapshot update, revert that update commit. This keeps rollback +reviewable and preserves the upstream provenance history. diff --git a/renderer/appunti.txt b/renderer/appunti.txt new file mode 100644 index 0000000..17d4743 --- /dev/null +++ b/renderer/appunti.txt @@ -0,0 +1,227 @@ +# OVERVIEW: +- [x] palette 16 colors +- [x] tiles 32*32 +- [x] sprite 64*64 + ~ \forall pixel \implies 4bit \in palette + +- [x] save the config => palette.json + scene.json +- [x] final rendered device => png + + +# INTERFACE AT COMMAND LINE: + +- this program must be executable by command line with the following shape: + python main.py + + +# INPUT FORMAT: + - [x] the renderer must be able to read JSON to get assets, split in 2 separate files: + |- palette.json => palette only + |- scene.json => transparent_index, tile_map, sprites + + - [x] palette \represented as pure JSON + ~ [ + [0,0,0], + [255,0,0], + [0,255,0], + [0,0,255], + [255,255,0], + [0,255,255], + [255,0,255], + [255,255,255], + [128,0,0], + [0,128,0], + [0,0,128], + [128,128,0], + [0,128,128], + [128,0,128], + [128,128,128], + [255,128,0] + ] + + - [x] read tile and sprite binaries + \saved as packed binary as 256*256 matrix + |- packed binary are ( 256 * 256 ) / 2 bytes (file 32768 byte) (each byte contains 2 pixel) + \foreach byte + |- high nibble (bit 7-4) ~ first pixel => palette index 0-15 + |- low nibble (bit 3-0) ~ second pixel => palette index 0-15 + + - [x] the scene is saved in a JSON + \is organized such as + |- 1. transparent_index ( palette index 0-15 treated as transparent ) + |- 2. tile_map ( matrix 15 * 20 of tile IDs 0-63 ) + |- 3. sprites ( list of sprite ) + \where \foreach sprite \has (at least) columns: + |- id + |- x + |- y + |- flip_x + |- flip_y + |- rotation + + ~ { + "transparent_index": 0, + "tile_map": [ + [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [ 1, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 0, 0, 0, 1], + [ 1, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 1], + [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [ 1, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 1], + [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [ 1, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 0, 0, 0, 1], + [ 1, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 1], + [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + ], + "sprites": [ + { "id": 0, "x": 100, "y": 80, "flip_x": false, "flip_y": false, "rotation": 0 }, + { "id": 3, "x": 320, "y": 200, "flip_x": true, "flip_y": false, "rotation": 0 }, + { "id": 7, "x": 480, "y": 150, "flip_x": false, "flip_y": true, "rotation": 90 }, + { "id": 12, "x": 60, "y": 380, "flip_x": true, "flip_y": true, "rotation": 180 } + ] + } + + +# ARCHITECTURE: +- [x] the palette: + \foreach color + |- is defined as RGB between 0-255 ~ 3 byte + \foreach pixel + |- has just 1 reference to a color ~ 4 bit + +- [x] a tile sheet: + \is 256*256 + |-\has 64 tiles \in grid 8*8 + \foreach tile + |- \has 32*32 pixel + \is ordered + |- tile 0 \is top left + ~ |----------------------------| + | tile 0 tile 1 ... tile 7 | + | tile 8 ... tile 15 | + | . . | + | . . | + | . . | + | | + | tile 56 ... tile 63 | + |----------------------------| + +- [x] a sprite sheet: + \is 256*256 pixel + |-\has 16 sprites \in grid 4*4 + \foreach sprite + |-\has 64*64 pixel + \is ordered + |- sprite 0 top left + ~ |----------------------------------| + | sprite 0 sprite 1 ... sprite 3 | + | sprite 4 ... sprite 7 | + | . . | + | . . | + | . . | + | | + | sprite 12 ... sprite 15 | + |----------------------------------| + +- [x] a frame buffer: + \is 640*480 pixel + \is fondale => whole screen + |-\binds tiles \by tile_map 15*20 ( + - 480/32 = 15 + - 640/32 = 20 + ) + +- [x] transparency: + \exists transparent_index \in palette (0-15) + \foreach sprite, \foreach pixel + - \if pixel palette index == transparent_index => pixel is not copied over the tile on the buffer + - \if pixel palette index != transparent_index => pixel get overwritten on the buffer + +- [x] sprite transformation: + \happens before \binding frame buffer + |-1. reflected on x axis + |-2. reflected on y axis + |-3. rotated by 90,180,270 degree + + +# BUILDING FLOW: + 1. First whole fondale is drawn based on tile_map + 2. Second \foreach sprite is drawn in the order it is saved in json + + N.B. a sprite can be partially out of frame \in this case the pixel drawn are just the \in frame ones + + +# CLASSES: + |- [x] 1. class Palette that takes a JSON path, reads and validates the palette and makes an index to the respective RGB value + |- [x] 2. class VirtualVRAM which uploads the tile sheet and sprite sheet from the respective binaries, decodes the 4 bit sequences in a matrix of palette indexes + |- [x] 3. class SceneParser which reads a JSON and gives back the description of the frame buffer and of the sprites + |- [x] 4. class Blitter which gets tiles and sprites from the respective sheets, applies flip, rotations and transparency and draws the final result in the background + |- [x] 5. class RenderingPipeline which calls and executes the complete composition of the scene and exports it as png + + +# OPTIONS / EXTRA: +- [ ] nibble unpacker in C via ctypes + \instead of pure numpy in VirtualVRAM._decode() + |- write decode(uint8* input, int len, uint8* output) as C shared lib (.so) + |- load in VirtualVRAM via ctypes + |- good ctypes demo: simple interface, bit-level operation that belongs to low level + |- numpy alternative: data>>4 and data&0x0F with interleave (already fast, C not needed for perf) + |- value is in demonstrating ctypes cross-language integration, not raw speed + + +# TODO IF TIME (refactoring design/architettura, non bloccanti — progetto ok cosi): +- [ ] blit_sprite: togliere il chained indexing self._buffer[dst][mask[src]] = ... + |- funziona solo perche dst sono slice pure (basic indexing => view => write-through) + |- fix idiomatico: np.copyto(region, sprite_buf[src], where=sprite_buf[src] != transparent_index) + con region = self._buffer[dst] +- [ ] RenderingPipeline: caricare Palette/VirtualVRAM/SceneParser in cima a render() (fail-fast) + |- ora la palette si valida solo in _export, dopo aver gia composto tutto il buffer + |- passare gli oggetti gia pronti a _compose/_export invece di ricostruirli +- [ ] Blitter: spostare l'id da costruttore a parametro dei metodi (un solo Blitter per scena) + |- ora se ne crea 1 per cella (300+) e si ri-valida transparent_index ogni volta + |- blit_tile(tile_id, row, col) / blit_sprite(sprite_id, x, y, flip_x, flip_y, rotation) + |- N.B. cambia le firme => aggiornare i test (make_pipeline, test Blitter) + + +# REQUIREMENTS: +- in case of error the renderer must raise a related exception, is suggested to make custom exceptions +- each relevant function must be commented with what it does, its input and its output +- Pillow usage must stay in the export to PNG only +- utilize Numpy for arrays, specifically the type np.uint8 +- renderer must be executed by command line in the proposed version + + +# PRE-WRITING CHECKLIST (Python good habits): + +## Naming +- [ ] classi in PascalCase (es. VirtualVRAM, SceneParser) +- [ ] metodi e variabili in snake_case (es. load_json, tile_map) +- [ ] costanti in UPPER_SNAKE_CASE (es. TILE_SIZE = 32) + +## Attributi di istanza +- [ ] self.x => pubblico, parte dell'API esterna +- [ ] self._x => protetto, uso interno / sottoclassi +- [ ] self.__x => privato, name-mangled (_ClassName__x), nessuno lo tocca fuori dalla classe + \scegli __ solo se c'è ereditarietà o vuoi protezione esplicita, altrimenti _ basta + +## Dunder methods +- [ ] __repr__ => sempre, stringa developer-facing (ideale: eval(repr(obj)) ricrea l'oggetto) +- [ ] __str__ => opzionale, stringa human-readable per print() + \se implementi solo uno => implementa __repr__, Python lo usa come fallback +- [ ] __getitem__ => se l'oggetto è indicizzabile con [], usa isinstance() per validare il tipo dell'indice + +## isinstance nei dunder +- [ ] nei metodi che accettano argomenti esterni (operatori, __getitem__, ecc.) + usare isinstance(x, T) per dare errori chiari invece di TypeError oscuri + es: + if not isinstance(idx, int): + raise MyError(f"expected int, got {type(idx)}") + +## Eccezioni custom +- [ ] definire almeno una eccezione per classe/modulo (es. PaletteError, VRAMError) +- [ ] sollevarla sempre con un messaggio che dice COSA è andato storto e QUALE valore ha causato il problema diff --git a/renderer/classes.py b/renderer/classes.py new file mode 100644 index 0000000..1b17946 --- /dev/null +++ b/renderer/classes.py @@ -0,0 +1,422 @@ +import numpy as np +import json +from PIL import Image + + +class PaletteError(Exception): + pass + + +class Palette: + def __init__(self, path): + self.__path = path + self.data = self._load_json() + + # Reads and validates a palette JSON file. + # Input: self.__path — path to a JSON file containing a list of 16 [R, G, B] colors. + # Output: np.ndarray of shape (16, 3) dtype uint8 with the palette colors. + def _load_json(self): + try: + with open(self.__path) as f: + raw = json.load(f) + except FileNotFoundError as e: + raise FileNotFoundError(f"palette file not found: {self.__path!r}") from e + except json.JSONDecodeError as e: + raise PaletteError(f"file JSON not valid: {e}") from e + if len(raw) != 16: + raise PaletteError(f"palette must contain 16 colors, trovati {len(raw)}") + for i, color in enumerate(raw): + if len(color) != 3: + raise PaletteError( + f"color {i}: must contain 3 component for RGB, found {len(color)}" + ) + for component in color: + if not isinstance(component, int) or isinstance(component, bool): + raise PaletteError( + f"color {i}: component must be int, got {type(component).__name__}" + ) + if not 0 <= component <= 255: + raise PaletteError( + f"color {i}: value {component} out of bounds [0, 255]" + ) + return np.array(raw, np.uint8) + + # Returns an unambiguous string representation of the palette. + # Input: none. + # Output: str. + def __repr__(self): + return f"Palette(path={self.__path!r})" + + # Prints the full palette to stdout. + # Input: none. + # Output: none. + def print_palette(self): + print(self.data) + + # Returns the RGB color at the given palette index. + # Input: idx — integer in [0, 15]. + # Output: np.ndarray of shape (3,) dtype uint8 with [R, G, B] values. + def __getitem__(self, idx): + if not isinstance(idx, int): + raise PaletteError(f"index must be int, got {type(idx).__name__}") + if not 0 <= idx <= 15: + raise PaletteError(f"index {idx} out of range [0, 15]") + return self.data[idx] + + +class VRAMError(Exception): + pass + + +class VirtualVRAM: + def __init__(self, path_t, path_s): + self.__path_tiles = path_t + self.__path_sprites = path_s + self.__tiles, self.__sprites = self._load_bin() + self._tiles_mx = self._decode(self.__tiles) + self._sprites_mx = self._decode(self.__sprites) + + # Reads the tile sheet and sprite sheet binary files. + # Input: self.__path_tiles, self.__path_sprites — paths to nibble-packed binary files. + # Output: tuple of two bytes objects (tiles_raw, sprites_raw). + def _load_bin(self): + try: + with open(self.__path_tiles, "rb") as ftile: + tiles = ftile.read() + except FileNotFoundError as e: + raise FileNotFoundError( + f"tiles file not found: {self.__path_tiles!r}" + ) from e + except OSError as e: + raise VRAMError(f"cannot open {self.__path_tiles!r}: {e}") from e + try: + with open(self.__path_sprites, "rb") as fsprite: + sprites = fsprite.read() + except FileNotFoundError as e: + raise FileNotFoundError( + f"sprites file not found: {self.__path_sprites!r}" + ) from e + except OSError as e: + raise VRAMError(f"cannot open {self.__path_sprites!r}: {e}") from e + return tiles, sprites + + # Decodes a nibble-packed binary buffer into a 256x256 matrix of palette indexes. + # Input: raw — bytes of exactly 32768 bytes (2 pixels per byte, high nibble first). + # Output: np.ndarray of shape (256, 256) dtype uint8 with palette indexes 0-15. + def _decode(self, raw): + if len(raw) != 32768: + raise VRAMError(f"invalid file size: expected 32768 bytes, got {len(raw)}") + data = np.frombuffer(raw, dtype=np.uint8) + return np.stack([data >> 4, data & 0x0F], axis=1).ravel().reshape(256, 256) + + # Returns the tile at the given index as a palette-index matrix. + # Input: idx — integer in [0, 63]. + # Output: np.ndarray of shape (32, 32) dtype uint8 with palette indexes 0-15. + def get_tile(self, idx): + if not isinstance(idx, int): + raise VRAMError(f"index must be int, got {type(idx).__name__}") + if not 0 <= idx <= 63: + raise VRAMError(f"index must be in [0, 63], got {idx}") + row = idx // 8 + col = idx % 8 + return self._tiles_mx[row * 32 : row * 32 + 32, col * 32 : col * 32 + 32] + + # Returns the sprite at the given index as a palette-index matrix. + # Input: idx — integer in [0, 15]. + # Output: np.ndarray of shape (64, 64) dtype uint8 with palette indexes 0-15. + def get_sprite(self, idx): + if not isinstance(idx, int): + raise VRAMError(f"index must be int, got {type(idx).__name__}") + if not 0 <= idx <= 15: + raise VRAMError(f"index must be in [0, 15], got {idx}") + row = idx // 4 + col = idx % 4 + return self._sprites_mx[row * 64 : row * 64 + 64, col * 64 : col * 64 + 64] + + def __repr__(self): + return ( + f"VirtualVRAM(path_t={self.__path_tiles!r}, path_s={self.__path_sprites!r})" + ) + + +class SceneError(Exception): + pass + + +class SceneParser: + def __init__(self, path): + self.__path = path + raw = self._load_descriptor() + self.transparent_index, self.tile_map, self.sprites = self._validate(raw) + + # Reads the scene JSON file. + # Input: self.__path — path to a JSON file describing the scene. + # Output: dict with the raw parsed JSON content. + def _load_descriptor(self): + try: + with open(self.__path) as f: + raw = json.load(f) + except FileNotFoundError as e: + raise FileNotFoundError(f"scene file not found: {self.__path!r}") from e + except json.JSONDecodeError as e: + raise SceneError(f"file JSON not valid: {e}") from e + for sprite in raw.get("sprites", []): + if "flip_h" in sprite: + sprite["flip_x"] = sprite.pop("flip_h") + if "flip_v" in sprite: + sprite["flip_y"] = sprite.pop("flip_v") + return raw + + # Validates the raw scene dict and returns typed, checked fields. + # Input: raw — dict from _load_descriptor. + # Output: tuple (transparent_index: int, tile_map: np.ndarray (15,20) uint8, sprites: list of dict). + def _validate(self, raw): + for key in ("transparent_index", "tile_map", "sprites"): + if key not in raw: + raise SceneError(f"missing key: {key!r}") + transparent_idx = raw["transparent_index"] + tile_map = raw["tile_map"] + sprites = raw["sprites"] + if not isinstance(transparent_idx, int) or isinstance(transparent_idx, bool): + raise SceneError( + f"transparent_index must be int, got {type(transparent_idx).__name__}" + ) + if not 0 <= transparent_idx <= 15: + raise SceneError( + f"transparent_index must be in [0, 15], got {transparent_idx}" + ) + if len(tile_map) != 15: + raise SceneError(f"tile_map must have 15 rows, got {len(tile_map)}") + for i, row in enumerate(tile_map): + if len(row) != 20: + raise SceneError( + f"tile_map row {i} must have 20 columns, got {len(row)}" + ) + for j, v in enumerate(row): + if not isinstance(v, int) or isinstance(v, bool): + raise SceneError( + f"tile_map[{i}][{j}] must be int, got {type(v).__name__}" + ) + if not 0 <= v <= 63: + raise SceneError( + f"tile_map[{i}][{j}] value {v} out of range [0, 63]" + ) + if not isinstance(sprites, list): + raise SceneError(f"sprites must be a list, got {type(sprites).__name__}") + for i, sprite in enumerate(sprites): + for key in ("id", "x", "y", "flip_x", "flip_y", "rotation"): + if key not in sprite: + raise SceneError(f"sprite {i}: missing key {key!r}") + if not isinstance(sprite["id"], int) or isinstance(sprite["id"], bool): + raise SceneError( + f"sprite {i}: id must be int, got {type(sprite['id']).__name__}" + ) + if not 0 <= sprite["id"] <= 15: + raise SceneError( + f"sprite {i}: id must be in [0, 15], got {sprite['id']}" + ) + if not isinstance(sprite["x"], int) or isinstance(sprite["x"], bool): + raise SceneError( + f"sprite {i}: x must be int, got {type(sprite['x']).__name__}" + ) + if not isinstance(sprite["y"], int) or isinstance(sprite["y"], bool): + raise SceneError( + f"sprite {i}: y must be int, got {type(sprite['y']).__name__}" + ) + if not isinstance(sprite["flip_x"], bool): + raise SceneError( + f"sprite {i}: flip_x must be bool, got {type(sprite['flip_x']).__name__}" + ) + if not isinstance(sprite["flip_y"], bool): + raise SceneError( + f"sprite {i}: flip_y must be bool, got {type(sprite['flip_y']).__name__}" + ) + if not isinstance(sprite["rotation"], int) or isinstance(sprite["rotation"], bool): + raise SceneError( + f"sprite {i}: rotation must be int, got {type(sprite['rotation']).__name__}" + ) + if sprite["rotation"] not in {0, 90, 180, 270}: + raise SceneError( + f"sprite {i}: rotation must be in {{0, 90, 180, 270}}, got {sprite['rotation']}" + ) + return transparent_idx, np.array(tile_map, np.uint8), sprites + + # Returns an unambiguous string representation of the scene parser. + # Input: none. + # Output: str. + def __repr__(self): + return f"SceneParser(path={self.__path!r})" + + +class BlitterException(Exception): + pass + + +FRAME_W = 640 +FRAME_H = 480 + + +class Blitter: + def __init__(self, vram, asset_type, idx, transparent_index, buffer): + self.__vram = vram + self.__asset_type, self.__position, self.transparent_index = self._validate( + asset_type, idx, transparent_index + ) + self._buffer = buffer + + # Validates asset_type, idx and transparent_index. + # Input: asset_type — "tile" or "sprite"; idx — int index into the respective sheet; transparent_index — palette index 0-15. + # Output: tuple (asset_type: str, idx: int, transparent_index: int). + def _validate(self, asset_type, idx, transparent_index): + if asset_type not in {"tile", "sprite"}: + raise BlitterException( + f"asset_type must be 'tile' or 'sprite', got {asset_type!r}" + ) + if not isinstance(idx, int): + raise BlitterException(f"idx must be int, got {type(idx).__name__}") + if asset_type == "tile" and not 0 <= idx <= 63: + raise BlitterException(f"tile idx must be in [0, 63], got {idx}") + if asset_type == "sprite" and not 0 <= idx <= 15: + raise BlitterException(f"sprite idx must be in [0, 15], got {idx}") + if not isinstance(transparent_index, int): + raise BlitterException( + f"transparent_index must be int, got {type(transparent_index).__name__}" + ) + if not 0 <= transparent_index <= 15: + raise BlitterException( + f"transparent_index must be in [0, 15], got {transparent_index}" + ) + return asset_type, idx, transparent_index + + # Blits the tile at self.__position onto the frame buffer at the given tilemap cell. + # Input: row — int in [0, 14]; col — int in [0, 19]. + # Output: none (writes to self._buffer in place). + def blit_tile(self, row, col): + if not isinstance(row, int): + raise BlitterException(f"row type must be int, got {type(row).__name__}") + if not isinstance(col, int): + raise BlitterException(f"col type must be int, got {type(col).__name__}") + if not 0 <= row <= 14: + raise BlitterException( + f"inserted row not valid, must be in [0,14], got {row}" + ) + if not 0 <= col <= 19: + raise BlitterException( + f"inserted col not valid, must be in [0,19], got {col}" + ) + tile_buf = self.__vram.get_tile(self.__position).copy() + self._buffer[row * 32 : row * 32 + 32, col * 32 : col * 32 + 32] = tile_buf + + # Applies flip and rotation transformations to a sprite matrix. + # Input: sprite_matrix — np.ndarray (64, 64) uint8; flip_x, flip_y — bool; rotation — 0/90/180/270. + # Output: np.ndarray (64, 64) uint8 transformed. + def _transform(self, sprite_matrix, flip_x, flip_y, rotation): + m = sprite_matrix + if flip_x: + m = np.fliplr(m) + if flip_y: + m = np.flipud(m) + if rotation != 0: + m = np.rot90(m, rotation // 90) + return m + + # Computes valid src/dst slice pairs to copy a 64x64 sprite at (x, y) into the frame buffer. + # Input: x, y — top-left position of the sprite in the frame buffer (can be negative). + # Output: tuple (dst, src) where each is a tuple of two slice objects (rows, cols). + # A sprite fully off-screen yields an empty destination; that case is returned as + # empty slices, since otherwise the negative width would produce inconsistent src + # slices (e.g. slice(0, -60) is not empty in numpy) and crash blit_sprite. + def _clip(self, x, y): + dst_r0 = max(0, y) + dst_r1 = min(FRAME_H, y + 64) + dst_c0 = max(0, x) + dst_c1 = min(FRAME_W, x + 64) + if dst_r1 <= dst_r0 or dst_c1 <= dst_c0: + empty = (slice(0, 0), slice(0, 0)) + return empty, empty + src_r0 = max(0, -y) + src_r1 = src_r0 + (dst_r1 - dst_r0) + src_c0 = max(0, -x) + src_c1 = src_c0 + (dst_c1 - dst_c0) + return (slice(dst_r0, dst_r1), slice(dst_c0, dst_c1)), ( + slice(src_r0, src_r1), + slice(src_c0, src_c1), + ) + + # Blits the sprite at self.__position onto the frame buffer with transformations and transparency. + # Input: x, y — top-left position in the frame buffer (can be negative); flip_x, flip_y — bool; rotation — 0/90/180/270. + # Output: none (writes to self._buffer in place). + def blit_sprite(self, x, y, flip_x, flip_y, rotation): + sprite_buf = self.__vram.get_sprite(self.__position).copy() + sprite_buf = self._transform(sprite_buf, flip_x, flip_y, rotation) + mask = sprite_buf != self.transparent_index + dst, src = self._clip(x, y) + self._buffer[dst][mask[src]] = sprite_buf[src][mask[src]] + + def __repr__(self): + return f"Blitter(asset_type={self.__asset_type!r}, position={self.__position})" + + +class RenderingException(Exception): + pass + + +class RenderingPipeline: + def __init__(self, palette_path, scene_path, tiles_path, sprites_path, output_path): + self._palette_path = palette_path + self._scene_path = scene_path + self._tiles_path = tiles_path + self._sprites_path = sprites_path + self._output_path = output_path + + # Returns an unambiguous string representation of the rendering pipeline. + # Input: none. + # Output: str. + def __repr__(self): + return ( + f"RenderingPipeline(palette_path={self._palette_path!r}, " + f"scene_path={self._scene_path!r}, tiles_path={self._tiles_path!r}, " + f"sprites_path={self._sprites_path!r}, output_path={self._output_path!r})" + ) + + # Runs the full pipeline: compose tiles and sprites onto the buffer, then export. + # Input: none. + # Output: none. + def render(self): + buf = RenderingPipeline.get_buf() + self._compose(buf) + self._export(buf) + + # Fills buf with all tiles from tile_map, then all sprites in scene order. + # Input: buf — np.ndarray (FRAME_H, FRAME_W) uint8, written in place. + # Output: none. + def _compose(self, buf): + vr = VirtualVRAM(self._tiles_path, self._sprites_path) + sp = SceneParser(self._scene_path) + for r, row in enumerate(sp.tile_map): + for c, tile_id in enumerate(row): + Blitter(vr, "tile", int(tile_id), sp.transparent_index, buf).blit_tile(r, c) + for sprite in sp.sprites: + Blitter(vr, "sprite", sprite["id"], sp.transparent_index, buf).blit_sprite( + sprite["x"], sprite["y"], sprite["flip_x"], sprite["flip_y"], sprite["rotation"] + ) + + # Maps palette indexes in buf to RGB and saves the result as PNG. + # Input: buf — np.ndarray (FRAME_H, FRAME_W) uint8 with palette indexes. + # Output: none (writes file to self._output_path). + def _export(self, buf): + palette = Palette(self._palette_path) + img = Image.fromarray(palette.data[buf]) + try: + img.save(self._output_path) + except (OSError, ValueError) as e: + raise RenderingException( + f"cannot save output PNG to {self._output_path!r}: {e}" + ) from e + + # Creates and returns a blank frame buffer. + # Input: none. + # Output: np.ndarray of shape (FRAME_H, FRAME_W) dtype uint8, all zeros. + @classmethod + def get_buf(cls): + return np.zeros((FRAME_H, FRAME_W), dtype=np.uint8) diff --git a/renderer/input/palette.json b/renderer/input/palette.json new file mode 100644 index 0000000..8efda84 --- /dev/null +++ b/renderer/input/palette.json @@ -0,0 +1,18 @@ +[ + [255, 0, 255], + [136, 208, 255], + [72, 144, 232], + [255, 255, 255], + [232, 248, 255], + [160, 232, 248], + [96, 168, 200], + [24, 48, 88], + [56, 88, 144], + [248, 240, 216], + [248, 200, 80], + [232, 136, 56], + [184, 136, 80], + [120, 80, 40], + [72, 128, 96], + [56, 72, 88] +] diff --git a/renderer/input/reference.png b/renderer/input/reference.png new file mode 100644 index 0000000000000000000000000000000000000000..c76e4559ccbc1b2319c90d67f772fc13c0eb6048 GIT binary patch literal 6389 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 CtJ*98 literal 0 HcmV?d00001 diff --git a/renderer/input/scene.json b/renderer/input/scene.json new file mode 100644 index 0000000..6cb066a --- /dev/null +++ b/renderer/input/scene.json @@ -0,0 +1,62 @@ +{ + "transparent_index": 0, + "tile_map": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 6, 6, 6, 7, 0, 0, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 8, 0, 0], + [0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 9, 0, 0], + [0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0], + [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4], + [5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5] + ], + "sprites": [ + { + "id": 0, + "x": 128, + "y": 352, + "flip_h": false, + "flip_v": false, + "rotation": 0 + }, + { + "id": 1, + "x": 208, + "y": 248, + "flip_h": false, + "flip_v": false, + "rotation": 0 + }, + { + "id": 1, + "x": 336, + "y": 152, + "flip_h": true, + "flip_v": false, + "rotation": 0 + }, + { + "id": 2, + "x": 544, + "y": 352, + "flip_h": false, + "flip_v": false, + "rotation": 0 + }, + { + "id": 3, + "x": 416, + "y": 352, + "flip_h": false, + "flip_v": false, + "rotation": 0 + } + ] +} diff --git a/renderer/input/sprites.bin b/renderer/input/sprites.bin new file mode 100644 index 0000000000000000000000000000000000000000..0910a3d4e4ab5aa1ca6ef3a6b9ec760975fbe595 GIT binary patch literal 32768 zcmeI0L2lwO6o#|&s;6Nwqgf+yoi31^f$nC3dxDk|AmsvGZ~!)p)B{ZKGXHZzupK{# zgjTbd-$D>b$mjPx``J)cY&1XvtpQ`L{f910?VS6E9T2>8kJAl2d<@$3n^eZ*-EQ(% zAO4&N?PKy?sy6%?0v-nR^ZEB=eiy!*E+7;B`TRb$1K=;d%VTl^TK>t}&_kAQtv~&?!2h{Ac8TW9y$9--_IxA2pv`~N zW?%7-`1dchv(D~cvlZA_OmAy110bvfo)xlgm$guSBOAf;JE@y%U`*>`uG66B_uKZmP zTziWydS7I5(riQiE$hD&KM1h$loMdx^M${61HSR#?&*+D#9Q`1e)7xuCkKP@sw(F1 z0&#Mf@MR)~_ZZ**N%&|M;}2~p!0e*<(+U*oZ<wxfKU`B_f&W~-LGdf{7uWWqk?<$-z4kVEc@C2&o(B9R0kQuX@sIH9H2x$3F@Hw> z%X}0+S4aMn5;QXY`=O?ezsdY~1}6Lp8Awy;`!JJ_;+w{w+&)ZzoZ5aWU-o`=Sbqmw z&|>}X^;a|Fm(6d)kL}C-`wsZ!2Lcj7Z68$rSp6||#LwJZ)_>Unbisz}fA%kb9jftH(FDx(y$<-f^BOYxT<3=aV7UC?CjAB| zxBZDAg*Q4s6p#%M{I&w37Gy6dVEa=A{#8IWeE;u{j6j4xoP9lD{!Z5&nGr`uqb4y9P{xRqzug!e9P=yC?h--~V{?KcVtVM+HBk z07WM5*OYgK{C7VdW$&a60GL7Ws-Ii>aSva5Ry)0ack;U*@6hm;y_5Jkz9D?=g6y}F z4-4^2e&uRy{C_z=>FVI^7Lk|6=im#O4@&W8E{{HHGt)jtCnmhPhu>vJ%g;@{xQl-i zp?uN*&M({HSsI`L8lV9hpaB}70UDqI8lV9hpaB}70UDqI8lV9hpaB}70UDqI8lV9h zpaB}70UDqI8lV9hpaB}70UDqI8lV9hpaB}70UDqI8lV9hpaB}70UDqI8lV9hpaB}7 z0UDqI8lV9hpaB}70UDqI8lV9hpaB}70UDqI8lV9hpaB}70UDqI8lV9hpaB}7floK^ E7Z-&?2mk;8 literal 0 HcmV?d00001 diff --git a/renderer/input/sprites.png b/renderer/input/sprites.png new file mode 100644 index 0000000000000000000000000000000000000000..309bb71ce5de756c367419888b4cdbd9a671dcc7 GIT binary patch literal 2222 zcmeH}={MVn0)~G?Q%!BX)j@1Mb-T>8RZEm94N9eEO6iy>S`DR9gM<=kltdV!L7m>- zs!nWeb-2?u6sKgVAWbb1Q6tV6G{htr#FCIiw%afFKe!*~oadb9{rr4-??r?o%pp(+ z008Ep7lJPXfYIk^1eh9s)^Aq+?f_tl4Gj*Ax>K_{b24k{0zl3bK71m-VzvL`6U$nj z@!q?`C=!ay8KUrvIcXDp=i22@DZ+?hWG^{45flFXtH%RlvRsKlPA!bebnu=;#J;0r z2qFG10m8+xQ$W{0jSFD^qr)B{u)idg6f?7z7N?A!n#7k7Zu`DgMP;k3?G8F%R_b<_ z9+eM0MgBY88^|u!&dFALls8R9~OL5 zhahI(Tk}}~MxZ*ddB?{3-0F)D;u*z+oVA>L-&prf%gb9w>*?q{Cm6_zGGhMEt1Ywr z_UsxLpFc3HH{Cmeeui2#qs_t(wp0YblF40$QGpY(T$SWb@G8;>eSQxg9=+s!DkRSj zd)xHmB)&kjz+B@=4;NWo>I+j4yx?{_B{9_}ekH30rH5b}NdymtdAnO(tRCBSX8$#( zWssvTviYwNqU+u1`tmJ}P(|5(kqqs+Pb|Ye*E89JtFS3F z)Tt`n`;nzv`cHVj4xb&)v}uQpio#~?)H^6~yiLg$-o|hmnD3+{`&T?Lk^DHJ`;heL z;t!!PhZKHrGv8#%!-&P$cW0!b^-H&x#N#4IejQ5FG)c&?Q@OWcRqUG}b@}3-shlT? zQ8axJb=O(`wltpDE`BiT2U516FQkQ&+P3-`;D2#dYC-07@^WQlym)Y0>z)O^DzJ;A zk%r_z0vaOTagLhN2V@{f6P0WHfpD2ck%kL_J1hIKY&+d@zYuVAD3&ONe(FwE}I`N3(2s#UF-ly^MY2X(4X zyl7Btk?G!C_7-YcQBpmGWv9$0CA8T#QEy6M|1s<#J>WV5{zxHFaTWjTZ4hda8LiY{ z;#XC^r-$<{g1A}N)uS!ba79{=P}tyTw)595+;2|pP(mqfp>DQ|djKOjJ5zFM$jXml7uKWjg8F+AnJcmGF^6Gp*PgpwF$Bgr zj;B3#4Pv$d+n{5W1catn2L1g7Pw%OJmgIh#`X$tsX4MoBl&9?CylI&Ct@m(b^qnj) z#I(vxeUo|CK{2tT`&94|zdWF<{d zct$|Xjk8GKE_Zq7Qn$F4(cCkZUXdTdmQcl0E#pB~b%_n;3J2Y`$o3_F0Hxr31JWUg zBH*j!K>j>a8zc1}%89dY_1KAvgG6=)4Ktjvn)lqpSWUm{CzoanAT7YDYXaybhP-=4 zPbvErXZ`&r&E06#X2K7&C!KOpV-7RA^yAw2LG-=yR9Vw;WWsf2Yn^#C)wp)WENa`W z!MpSW_WD$VjA?{W_Ua?zM#anOL6i;rx|3$)a zZQXV2&ZVE`sflor&%}E5T(^zYt7Ax?=&zk(;VZc^)1v!v%%>|i&QIbX;~55W4Eo49 z^U8#+%Dru^yP8>s=YDNde&F@t5$k5YcKD-%h3O6F2L3J7dlwj7PmMYlo#R>zfGH<| l#(jVDcY*&yfkQV9Mw!Dsq7*ec^79V?LeGZ>)6T^g{udiv=)wR1 literal 0 HcmV?d00001 diff --git a/renderer/input/tiles.bin b/renderer/input/tiles.bin new file mode 100644 index 0000000..727cf62 --- /dev/null +++ b/renderer/input/tiles.bin @@ -0,0 +1 @@ +DDDDDDDDDDDDDDDDÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿDDDDDDDDDDDDDDDDÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿDDDDDDDDDDDDDDDDÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿDDDDDDDDDDDDDDDDÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3331DDDDDDDDDDDDDDDDÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿffffffffffaÝÝÝÝÝÝÝÝÝÝÝÝ333331DDDDDDDDDDDDDDDDÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿeUUUUUUUUUUVÝÌÌÌÌÌÌÌÌÌÌÝ3333333333DDDDDDDDDDDDDDDDÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUUUUUaÜÜÌÌÌÌÌÌÌÌÍÍ3333333333313333DDDDDDDDDDDDDDDDÿÿýÝÿÿÿÿÿÿÿÿÿÿÿÿeUUUUUUUUUUUUVÜÍÌÌÌÌÌÌÌÌÜÍ33333133333333333331DDDDDDDDDDDDDDDDÿÿÿßÿÿÿÿÿßÿÿÿÿÿÿeDDDDDDDDDDDDVÜÌÜÌÌÌÌÌÌÍÌÍ3333333333333333333333DDDDDDDDDDDDDDDDÿÿÿÿÿÿÿÿýÝÿÿÿÿÿÿeUUUUUUUUUUUUVÜÌÍÌÌÌÌÌÌÜÌÍ333333333333333331333333ffVfffVfffVfffVfÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿeUUUUUUUUUUUUVÜÌÌÜÌÌÌÌÍÌÌÍ33333331333333333333333333333ÿÿ_ÿÿÿ_ÿÿÿ_ÿÿÿ_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿeUUUUUUUUUUUUVÜÌÌÍÌÌÌÌÜÌÌÍ333333331333333333333331333333333ÿõÿÿÿõÿÿÿõÿÿÿõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿeU5UUUUUUUUUUVÜÌÌÌÝÝÝÝÌÌÌÍ33333333313333333333333313333333331ÿõÿÿÿõÿÿÿõÿÿÿõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿeUSUUUUUUUUUUVÜÌÌÌÝÌÌÝÌÌÌÍ333333333333333333333333333333333333ÿ_ÿÿÿ_ÿÿÿ_ÿÿÿ_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿeUU3UUUU5UUUUVÜÌÌÌÜÜÍÍÌÌÌÍ3333333333313333333333333333333333333331ÿ_ÿÿÿ_ÿÿÿ_ÿÿÿ_ÿÿÿÿÿÿÿÿÿÿÿÿÿýÿÿÿÿeUUU5UUUSUUUUVÜÌÌÌÜÍÜÍÌÌÌÍ3333333333313333333333333333333333333331ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÝßÿÿÿeUUUSUUUU5UUUVÜÌÌÌÜÍÜÍÌÌÌÍ3333333333313333333333333333333333333331ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿÿÿÿeUUUU5UUUS5UUVÜÌÌÌÜÜÍÍÌÌÌÍ3333333333313333333333333331333333333331ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿeUUUUS5UUUSUUVÜÌÌÌÝÌÌÝÌÌÌÍ333333333331333333333333331333333333331ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿeUUUUUSUUUU5UVÜÌÌÌÝÝÝÝÌÌÌÍ33333333333333333333333333333333333ÿÿÿßÿÿÿßÿÿÿýÿÿßÿÿÿÿÿýÝÿÿÿÿÿÿÿÿÿÿeUUUUUU5UUUUUVÜÌÌÍÌÌÌÌÜÌÌÍ333333333133333333133313333333331ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿeUUUUUUUUUUUUVÜÌÌÜÌÌÌÌÍÌÌÍ33333333333333331333333333ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿeUUUUUUUUUUUUVÜÌÍÌÌÌÌÌÌÜÌÍ""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿeUUUUUUUUUUUUVÜÌÜÌÌÌÌÌÌÍÌÍ33333333331ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßÿeUUUUUUUUUUUUVÜÍÌÌÌÌÌÌÌÌÜÍÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÝÿUUUUUUUUUUUUaÜÜÌÌÌÌÌÌÌÌÍÍÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßÿeUUUUUUUUUUVÝÌÌÌÌÌÌÌÌÌÌÝÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿffffffffffaÝÝÝÝÝÝÝÝÝÝÝÝÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÝÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿqffffffffçffffffffffaeUUUUUUVqçDDDDDDDDDDaeU5UUUUVG~îqDDDDDDDDDDaeU5UUUUVtDqîîçDDDDDDDDDDaeU5UUUUVDDGîîçDDDDDDDDDDaeU5UUUUVDDG~îîîqDDDDDDDDDDaeU5UUUUVtDDDqîîîîçDDDDDDDDDDaeU5UUUUVDDDDGîîîîçffffffffffaeU5UUUUV~îîîîîq~îîîîîqUUUUUUUUaeU5UUUUVîîîîîîçîîîîîîçUUUUUUUUaeU5UUUUV~îîîîîîîqîîîîîîçUUUUUUUUaeU5UUUUV~îîîîîîîq~îîîîîîîqUUUUUUUUaeU5UUUUVîîîîîîîîçîîîîîîîîçUUUUUUUUaeU5UUUUV~îîîîîîîîîqîîîîîîîîçUUUUUUUUaeU5UUUUVwwwîîîîçwww~îîîîîîîîîqUUUUUUUUaeU5UUUUV~îîîîîqîîîîîîîîîîçUUUUUUUUaeU5UUUUV~îîîîîqîîîîîîîîîîçUUUUUUUUaeU5UUUUVîîîîîîçwwww}ÝÝ×wwwwqUUUUUUUUaeU5UUUUV~îîîîîîîqÝÝÑUUUUUUUUaeU5UUUUV~îîîîîîîqÝÝÑUUUUUUUUaeU5UUUUVîîîîîîîîçÝÝÑUUUUUUUUaeU5UUUUV~îîîîîîîîîqÝÝÑUUUUUUUUaeU5UUUUVîîîîîîîîîîçÝÝÑUUUUUUUUaeU5UUUUVîîîîîîîîîîçÝÝÑUUUUUUUUaeU5UUUUV~îîîîîîîîîîîqÝÝÑUUUUUUUUaeU5UUUUVîîîîîîîîîîîîçÝÝÑUUUUUUUUaeU5UUUUVîîîîîîîîîîîîçÝÝÑUUUUUUUUaeU5UUUUVwwwwwwwwwwwwwwqÝÝÑeUUUUUUVeU5UUUUVÝÝÑfUUUUUUfeU5UUUUVÝÝÑffffffeUUUUUUVÝÝÑffffffff \ No newline at end of file diff --git a/renderer/input/tiles.png b/renderer/input/tiles.png new file mode 100644 index 0000000000000000000000000000000000000000..fa626975b07b9fad85caad0214e427797079d4de GIT binary patch literal 1967 zcmeHI`B#z&6#bNfG?$#Lsl=sKJLwqi%P5r^nW>p+IwsPhnU=WWm!KkYG+V~bWx~rpS0~!SYhzx(LhhWC~doEEEt~lB5KiEHYv2COZtW)02&~`eymI-s>LFQaAaUlUz zL(+Dw{@t`}@0mq=dlnPYNWoSVsYYRzyIU_eUe{_c(qz1!1`3wPdvxbUcR^4#vOZrf zpHR&z=OvHN8lW4MJ5jl1K3Z|K!jOt1HqB7>`U=J#0~@;`m$XFl;1cU3=jyh!+!(SDg$0G5 zbEqTUsfuroaSd%BQe9Wm7=Qn$m&M!5(;wpqnA~yHCxSI}QNhl-)-4j3K4pD>xd@15 zRlK!FXT{tUqhq+vXu33oE`B4`$34IHA)WMPFY|xBNYHpZna6!NyExF8wWuEIOzX^Fgb1>!^@!@hZuFOD z&sToMrQ^}$lGhY|OOx86`}Rk=4}A7dYR>RS^DO22X^o|eY<@uKS6;v=uta#+ZLr(i zEHgQcKU45s(d2&TUuskYDx1dZ-8#(y8Sn<|o~V)h>I15bp>q)TQ(Y}v?$SjC;_G&N zB1KQ)VCQ0w?A>&*v>D~tQ5!u`zPMES8NO;;UhL58+G1COqfV7^xqHr%~`EsGcu zFOqcGtjBnHTfH1L!7XOah{-HpG#hXoO1htqhk8@F*dY0vdSOpF#gWF>CKkWF z9ZKI0lZw++mnYgEGp&;#=W0q&u!tRy^f~Mw!(*&riD1cbc7ZbPOmi``%`)mDO&bY1 zTAEZNFSu!mO=c1DYrceCmYJWQv|pci1CR4UR6gzGBuzJ#)nqL$x4F_X@I1)Ovr)~k z(R_{SoWT#Uvj-D-LeWZQc_hqj*^(J6tJkv<6bFB_*&#i_B zOjM}=*Zx!5hHQ|C&U2p{y}!|2kNcY2P@m=Th~iH!B?PEq=R*1rk$!+wyix+0amkdp|`@AJ~sJ$YX$O2YdB?18xc0?TE!wR%daK9+&4+h02 literal 0 HcmV?d00001 diff --git a/renderer/main.py b/renderer/main.py new file mode 100644 index 0000000..529c6b4 --- /dev/null +++ b/renderer/main.py @@ -0,0 +1,19 @@ +import argparse +from classes import RenderingPipeline + + +def main(): + parser = argparse.ArgumentParser(description="PAP pixel art renderer") + parser.add_argument("palette", help="path to palette.json") + parser.add_argument("scene", help="path to scene.json") + parser.add_argument("tiles", help="path to tiles.bin") + parser.add_argument("sprites", help="path to sprites.bin") + parser.add_argument("output", help="path to output PNG") + args = parser.parse_args() + RenderingPipeline( + args.palette, args.scene, args.tiles, args.sprites, args.output + ).render() + + +if __name__ == "__main__": + main() diff --git a/renderer/output/.gitkeep b/renderer/output/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/renderer/output/example.png b/renderer/output/example.png new file mode 100644 index 0000000000000000000000000000000000000000..c76e4559ccbc1b2319c90d67f772fc13c0eb6048 GIT binary patch literal 6389 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 CtJ*98 literal 0 HcmV?d00001 diff --git a/renderer/project_python.pdf b/renderer/project_python.pdf new file mode 100644 index 0000000000000000000000000000000000000000..28519c82830dd214f8bf3e4339db8408457310ef GIT binary patch literal 178065 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 xDOliB*Bp9<{(MX{!rTdzecPsy=q)8dD86-@ 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

17z#%wMA?%c=Z?A70^yTjoF;e|^R2)NV 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 literal 0 HcmV?d00001 diff --git a/renderer/pyproject.toml b/renderer/pyproject.toml new file mode 100644 index 0000000..db7a8c8 --- /dev/null +++ b/renderer/pyproject.toml @@ -0,0 +1,14 @@ +[project] +name = "pap-render" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.14" +dependencies = [ + "numpy>=2.4.6", + "pillow>=12.2.0", + "pytest>=9.0.3", +] + +[tool.pytest.ini_options] +python_files = ["test_*.py", "tests.py"] diff --git a/renderer/test_data/palette_ok.json b/renderer/test_data/palette_ok.json new file mode 100644 index 0000000..7531050 --- /dev/null +++ b/renderer/test_data/palette_ok.json @@ -0,0 +1,18 @@ +[ + [0, 0, 0 ], + [255, 0, 0 ], + [0, 255, 0 ], + [0, 0, 255], + [255, 255, 0 ], + [0, 255, 255], + [255, 0, 255], + [255, 255, 255], + [128, 0, 0 ], + [0, 128, 0 ], + [0, 0, 128], + [128, 128, 0 ], + [0, 128, 128], + [128, 0, 128], + [128, 128, 128], + [255, 128, 0 ] +] diff --git a/renderer/test_data/palette_wrong_count.json b/renderer/test_data/palette_wrong_count.json new file mode 100644 index 0000000..9280d30 --- /dev/null +++ b/renderer/test_data/palette_wrong_count.json @@ -0,0 +1,5 @@ +[ + [0, 0, 0 ], + [255, 0, 0 ], + [0, 255, 0 ] +] diff --git a/renderer/test_data/palette_wrong_value.json b/renderer/test_data/palette_wrong_value.json new file mode 100644 index 0000000..ec437e2 --- /dev/null +++ b/renderer/test_data/palette_wrong_value.json @@ -0,0 +1,18 @@ +[ + [0, 0, 0 ], + [255, 0, 0 ], + [0, 255, 0 ], + [0, 0, 300], + [255, 255, 0 ], + [0, 255, 255], + [255, 0, 255], + [255, 255, 255], + [128, 0, 0 ], + [0, 128, 0 ], + [0, 0, 128], + [128, 128, 0 ], + [0, 128, 128], + [128, 0, 128], + [128, 128, 128], + [255, 128, 0 ] +] diff --git a/renderer/tests.py b/renderer/tests.py new file mode 100644 index 0000000..6ad9c86 --- /dev/null +++ b/renderer/tests.py @@ -0,0 +1,1151 @@ +# %% +import sys +import pytest +import json +import numpy as np +from pathlib import Path +from PIL import Image as PILImage +from classes import Palette, PaletteError, VirtualVRAM, VRAMError, SceneParser, SceneError, Blitter, BlitterException, RenderingPipeline, RenderingException, FRAME_W, FRAME_H +from main import main + +VALID = "test_data/palette_ok.json" + + +@pytest.fixture +def test_dir(tmp_path): + # File generati dai test finiscono nella tmp dir per-test di pytest, + # non nella cartella del repo. + return tmp_path + + +def make_tmp(base_dir, data): + f = base_dir / "palette.json" + f.write_text(json.dumps(data)) + return str(f) + + +# -- happy path --------------------------------------------------------------- + +def test_load_ok(): + p = Palette(VALID) + assert p.data.shape == (16, 3) + assert p.data.dtype == np.uint8 + +def test_getitem_first(): + p = Palette(VALID) + assert list(p[0]) == [0, 0, 0] + +def test_getitem_last(): + p = Palette(VALID) + assert list(p[15]) == [255, 128, 0] + +def test_boundary_values(test_dir): + data = [([0, 0, 0] if i % 2 == 0 else [255, 255, 255]) for i in range(16)] + p = Palette(make_tmp(test_dir, data)) + assert list(p[0]) == [0, 0, 0] + assert list(p[1]) == [255, 255, 255] + + +# -- file errors -------------------------------------------------------------- + +def test_file_not_found(): + with pytest.raises(FileNotFoundError): + Palette("non_esiste.json") + +def test_invalid_json(test_dir): + f = test_dir / "palette.json" + f.write_text("questo non e json {{{") + with pytest.raises(PaletteError): + Palette(str(f)) + + +# -- wrong color count -------------------------------------------------------- + +def test_too_few_colors(test_dir): + data = [[0, 0, 0]] * 3 + with pytest.raises(PaletteError): + Palette(make_tmp(test_dir, data)) + +def test_too_many_colors(test_dir): + data = [[0, 0, 0]] * 17 + with pytest.raises(PaletteError): + Palette(make_tmp(test_dir, data)) + +def test_empty_palette(test_dir): + with pytest.raises(PaletteError): + Palette(make_tmp(test_dir, [])) + + +# -- wrong color format ------------------------------------------------------- + +def test_color_too_few_components(test_dir): + data = [[0, 0]] + [[0, 0, 0]] * 15 + with pytest.raises(PaletteError): + Palette(make_tmp(test_dir, data)) + +def test_color_too_many_components(test_dir): + data = [[0, 0, 0, 0]] + [[0, 0, 0]] * 15 + with pytest.raises(PaletteError): + Palette(make_tmp(test_dir, data)) + + +# -- out of range values ------------------------------------------------------ + +def test_value_above_255(test_dir): + data = [[300, 0, 0]] + [[0, 0, 0]] * 15 + with pytest.raises(PaletteError): + Palette(make_tmp(test_dir, data)) + +def test_value_negative(test_dir): + data = [[-1, 0, 0]] + [[0, 0, 0]] * 15 + with pytest.raises(PaletteError): + Palette(make_tmp(test_dir, data)) + +# C4 regression: a color component must be an integer (the PDF says integers). +def test_value_float_component(test_dir): + data = [[255.5, 0, 0]] + [[0, 0, 0]] * 15 + with pytest.raises(PaletteError): + Palette(make_tmp(test_dir, data)) + +def test_value_bool_component(test_dir): + data = [[True, 0, 0]] + [[0, 0, 0]] * 15 + with pytest.raises(PaletteError): + Palette(make_tmp(test_dir, data)) + +def test_value_exact_255(test_dir): + data = [[255, 255, 255]] * 16 + p = Palette(make_tmp(test_dir, data)) + assert list(p[0]) == [255, 255, 255] + + +# -- __getitem__ bounds ------------------------------------------------------- + +def test_getitem_out_of_range_high(): + p = Palette(VALID) + with pytest.raises(PaletteError): + p[16] + +def test_getitem_out_of_range_low(): + p = Palette(VALID) + with pytest.raises(PaletteError): + p[-1] + + +# -- VirtualVRAM helpers ------------------------------------------------------ + +def make_tmp_bin(base_dir, data, name="sheet.bin"): + f = base_dir / name + f.write_bytes(data) + return str(f) + + +# -- VirtualVRAM happy path --------------------------------------------------- + +def test_vram_load_ok(test_dir): + t = make_tmp_bin(test_dir, bytes(32768), "tiles.bin") + s = make_tmp_bin(test_dir, bytes(32768), "sprites.bin") + vram = VirtualVRAM(t, s) + assert vram._tiles_mx.shape == (256, 256) + assert vram._sprites_mx.shape == (256, 256) + assert vram._tiles_mx.dtype == np.uint8 + assert vram._sprites_mx.dtype == np.uint8 + +def test_vram_decode_all_zeros(test_dir): + t = make_tmp_bin(test_dir, bytes(32768), "tiles.bin") + s = make_tmp_bin(test_dir, bytes(32768), "sprites.bin") + vram = VirtualVRAM(t, s) + assert vram._tiles_mx.max() == 0 + +def test_vram_decode_all_ff(test_dir): + t = make_tmp_bin(test_dir, bytes([0xFF] * 32768), "tiles.bin") + s = make_tmp_bin(test_dir, bytes(32768), "sprites.bin") + vram = VirtualVRAM(t, s) + assert vram._tiles_mx.max() == 15 + assert vram._tiles_mx.min() == 15 + +def test_vram_decode_nibbles(test_dir): + # 0xAB → high nibble = 10 (primo pixel), low nibble = 11 (secondo pixel) + data = bytes([0xAB]) + bytes(32767) + t = make_tmp_bin(test_dir, data, "tiles.bin") + s = make_tmp_bin(test_dir, bytes(32768), "sprites.bin") + vram = VirtualVRAM(t, s) + assert vram._tiles_mx[0, 0] == 10 + assert vram._tiles_mx[0, 1] == 11 + + +# -- VirtualVRAM file errors -------------------------------------------------- + +def test_vram_tiles_not_found(test_dir): + s = make_tmp_bin(test_dir, bytes(32768), "sprites.bin") + with pytest.raises(FileNotFoundError): + VirtualVRAM("non_esiste.bin", s) + +def test_vram_sprites_not_found(test_dir): + t = make_tmp_bin(test_dir, bytes(32768), "tiles.bin") + with pytest.raises(FileNotFoundError): + VirtualVRAM(t, "non_esiste.bin") + + +# -- VirtualVRAM wrong size --------------------------------------------------- + +def test_vram_tiles_wrong_size(test_dir): + t = make_tmp_bin(test_dir, bytes(100), "tiles.bin") + s = make_tmp_bin(test_dir, bytes(32768), "sprites.bin") + with pytest.raises(VRAMError): + VirtualVRAM(t, s) + +def test_vram_sprites_wrong_size(test_dir): + t = make_tmp_bin(test_dir, bytes(32768), "tiles.bin") + s = make_tmp_bin(test_dir, bytes(100), "sprites.bin") + with pytest.raises(VRAMError): + VirtualVRAM(t, s) + + +# -- get_tile / get_sprite sheet builders ------------------------------------- + +def make_tile_sheet(tile_id, fill_byte=0xFF): + data = bytearray(32768) + row_start = (tile_id // 8) * 32 + col_start = (tile_id % 8) * 32 + for r in range(32): + offset = (row_start + r) * 128 + col_start // 2 + for b in range(16): + data[offset + b] = fill_byte + return bytes(data) + +def make_sprite_sheet(sprite_id, fill_byte=0xFF): + data = bytearray(32768) + row_start = (sprite_id // 4) * 64 + col_start = (sprite_id % 4) * 64 + for r in range(64): + offset = (row_start + r) * 128 + col_start // 2 + for b in range(32): + data[offset + b] = fill_byte + return bytes(data) + + +# -- get_tile happy path ------------------------------------------------------ + +def test_get_tile_shape(test_dir): + t = make_tmp_bin(test_dir, bytes(32768), "tiles.bin") + s = make_tmp_bin(test_dir, bytes(32768), "sprites.bin") + vram = VirtualVRAM(t, s) + assert vram.get_tile(0).shape == (32, 32) + assert vram.get_tile(0).dtype == np.uint8 + +def test_get_tile_first(test_dir): + t = make_tmp_bin(test_dir, make_tile_sheet(0), "tiles.bin") + s = make_tmp_bin(test_dir, bytes(32768), "sprites.bin") + vram = VirtualVRAM(t, s) + assert vram.get_tile(0).min() == 15 + assert vram.get_tile(0).max() == 15 + +def test_get_tile_last(test_dir): + t = make_tmp_bin(test_dir, make_tile_sheet(63), "tiles.bin") + s = make_tmp_bin(test_dir, bytes(32768), "sprites.bin") + vram = VirtualVRAM(t, s) + assert vram.get_tile(63).min() == 15 + assert vram.get_tile(63).max() == 15 + +def test_get_tile_isolation(test_dir): + t = make_tmp_bin(test_dir, make_tile_sheet(5), "tiles.bin") + s = make_tmp_bin(test_dir, bytes(32768), "sprites.bin") + vram = VirtualVRAM(t, s) + assert vram.get_tile(5).min() == 15 + assert vram.get_tile(0).max() == 0 + + +# -- get_tile error path ------------------------------------------------------ + +def test_get_tile_not_int(test_dir): + t = make_tmp_bin(test_dir, bytes(32768), "tiles.bin") + s = make_tmp_bin(test_dir, bytes(32768), "sprites.bin") + vram = VirtualVRAM(t, s) + with pytest.raises(VRAMError): + vram.get_tile("0") + +def test_get_tile_out_of_range_high(test_dir): + t = make_tmp_bin(test_dir, bytes(32768), "tiles.bin") + s = make_tmp_bin(test_dir, bytes(32768), "sprites.bin") + vram = VirtualVRAM(t, s) + with pytest.raises(VRAMError): + vram.get_tile(64) + +def test_get_tile_out_of_range_low(test_dir): + t = make_tmp_bin(test_dir, bytes(32768), "tiles.bin") + s = make_tmp_bin(test_dir, bytes(32768), "sprites.bin") + vram = VirtualVRAM(t, s) + with pytest.raises(VRAMError): + vram.get_tile(-1) + + +# -- get_sprite happy path ---------------------------------------------------- + +def test_get_sprite_shape(test_dir): + t = make_tmp_bin(test_dir, bytes(32768), "tiles.bin") + s = make_tmp_bin(test_dir, bytes(32768), "sprites.bin") + vram = VirtualVRAM(t, s) + assert vram.get_sprite(0).shape == (64, 64) + assert vram.get_sprite(0).dtype == np.uint8 + +def test_get_sprite_first(test_dir): + t = make_tmp_bin(test_dir, bytes(32768), "tiles.bin") + s = make_tmp_bin(test_dir, make_sprite_sheet(0), "sprites.bin") + vram = VirtualVRAM(t, s) + assert vram.get_sprite(0).min() == 15 + assert vram.get_sprite(0).max() == 15 + +def test_get_sprite_last(test_dir): + t = make_tmp_bin(test_dir, bytes(32768), "tiles.bin") + s = make_tmp_bin(test_dir, make_sprite_sheet(15), "sprites.bin") + vram = VirtualVRAM(t, s) + assert vram.get_sprite(15).min() == 15 + assert vram.get_sprite(15).max() == 15 + +def test_get_sprite_isolation(test_dir): + t = make_tmp_bin(test_dir, bytes(32768), "tiles.bin") + s = make_tmp_bin(test_dir, make_sprite_sheet(7), "sprites.bin") + vram = VirtualVRAM(t, s) + assert vram.get_sprite(7).min() == 15 + assert vram.get_sprite(0).max() == 0 + + +# -- get_sprite error path ---------------------------------------------------- + +def test_get_sprite_not_int(test_dir): + t = make_tmp_bin(test_dir, bytes(32768), "tiles.bin") + s = make_tmp_bin(test_dir, bytes(32768), "sprites.bin") + vram = VirtualVRAM(t, s) + with pytest.raises(VRAMError): + vram.get_sprite("0") + +def test_get_sprite_out_of_range_high(test_dir): + t = make_tmp_bin(test_dir, bytes(32768), "tiles.bin") + s = make_tmp_bin(test_dir, bytes(32768), "sprites.bin") + vram = VirtualVRAM(t, s) + with pytest.raises(VRAMError): + vram.get_sprite(16) + +def test_get_sprite_out_of_range_low(test_dir): + t = make_tmp_bin(test_dir, bytes(32768), "tiles.bin") + s = make_tmp_bin(test_dir, bytes(32768), "sprites.bin") + vram = VirtualVRAM(t, s) + with pytest.raises(VRAMError): + vram.get_sprite(-1) + + +# -- SceneParser helpers ------------------------------------------------------ + +VALID_TILE_MAP = [[0] * 20 for _ in range(15)] +VALID_SPRITE = {"id": 0, "x": 0, "y": 0, "flip_x": False, "flip_y": False, "rotation": 0} + +def make_scene(base_dir, data, name="scene.json"): + f = base_dir / name + f.write_text(json.dumps(data)) + return str(f) + +def valid_scene(): + return { + "transparent_index": 0, + "tile_map": VALID_TILE_MAP, + "sprites": [VALID_SPRITE], + } + + +# -- SceneParser happy path --------------------------------------------------- + +def test_scene_load_ok(test_dir): + p = SceneParser(make_scene(test_dir, valid_scene())) + assert p.transparent_index == 0 + assert p.tile_map.shape == (15, 20) + assert p.tile_map.dtype == np.uint8 + assert isinstance(p.sprites, list) + +def test_scene_transparent_index_boundary(test_dir): + data = valid_scene() + data["transparent_index"] = 15 + p = SceneParser(make_scene(test_dir, data)) + assert p.transparent_index == 15 + +def test_scene_empty_sprites(test_dir): + data = valid_scene() + data["sprites"] = [] + p = SceneParser(make_scene(test_dir, data)) + assert p.sprites == [] + +def test_scene_sprite_fields(test_dir): + p = SceneParser(make_scene(test_dir, valid_scene())) + s = p.sprites[0] + assert s["id"] == 0 + assert s["x"] == 0 + assert s["y"] == 0 + assert s["flip_x"] is False + assert s["flip_y"] is False + assert s["rotation"] == 0 + + +# -- SceneParser file errors -------------------------------------------------- + +def test_scene_file_not_found(): + with pytest.raises(FileNotFoundError): + SceneParser("non_esiste.json") + +def test_scene_invalid_json(test_dir): + f = test_dir / "scene.json" + f.write_text("questo non e json {{{") + with pytest.raises(SceneError): + SceneParser(str(f)) + + +# -- SceneParser missing keys ------------------------------------------------- + +def test_scene_missing_transparent_index(test_dir): + data = valid_scene() + del data["transparent_index"] + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_missing_tile_map(test_dir): + data = valid_scene() + del data["tile_map"] + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_missing_sprites(test_dir): + data = valid_scene() + del data["sprites"] + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + + +# -- SceneParser transparent_index errors ------------------------------------- + +def test_scene_transparent_index_not_int(test_dir): + data = valid_scene() + data["transparent_index"] = "0" + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_transparent_index_too_high(test_dir): + data = valid_scene() + data["transparent_index"] = 16 + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_transparent_index_negative(test_dir): + data = valid_scene() + data["transparent_index"] = -1 + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + + +# -- C2 regression: bool rejected where int is required ----------------------- +# In Python bool is a subclass of int, so a JSON `true` would pass isinstance int. + +def test_scene_transparent_index_bool(test_dir): + data = valid_scene() + data["transparent_index"] = True + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_tile_map_bool(test_dir): + data = valid_scene() + data["tile_map"] = [[0] * 20 for _ in range(15)] + data["tile_map"][0][0] = True + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_sprite_x_bool(test_dir): + data = valid_scene() + data["sprites"] = [{"id": 0, "x": True, "y": 0, + "flip_x": False, "flip_y": False, "rotation": 0}] + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_sprite_rotation_bool(test_dir): + data = valid_scene() + data["sprites"] = [{"id": 0, "x": 0, "y": 0, + "flip_x": False, "flip_y": False, "rotation": True}] + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + + +# -- SceneParser tile_map errors ---------------------------------------------- + +def test_scene_tile_map_wrong_rows(test_dir): + data = valid_scene() + data["tile_map"] = [[0] * 20 for _ in range(10)] + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_tile_map_wrong_cols(test_dir): + data = valid_scene() + data["tile_map"] = [[0] * 15 for _ in range(15)] + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_tile_map_value_too_high(test_dir): + data = valid_scene() + data["tile_map"][0][0] = 64 + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_tile_map_value_negative(test_dir): + data = valid_scene() + data["tile_map"][0][0] = -1 + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_tile_map_value_not_int(test_dir): + data = valid_scene() + data["tile_map"][0][0] = 1.5 + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + + +# -- SceneParser sprite errors ------------------------------------------------ + +def test_scene_sprites_not_list(test_dir): + data = valid_scene() + data["sprites"] = "not a list" + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_sprite_missing_field(test_dir): + data = valid_scene() + del data["sprites"][0]["rotation"] + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_sprite_id_not_int(test_dir): + data = valid_scene() + data["sprites"][0]["id"] = "0" + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_sprite_id_too_high(test_dir): + data = valid_scene() + data["sprites"][0]["id"] = 16 + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_sprite_id_negative(test_dir): + data = valid_scene() + data["sprites"][0]["id"] = -1 + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_sprite_x_not_int(test_dir): + data = valid_scene() + data["sprites"][0]["x"] = 1.5 + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_sprite_y_not_int(test_dir): + data = valid_scene() + data["sprites"][0]["y"] = "0" + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_sprite_flip_x_not_bool(test_dir): + data = valid_scene() + data["sprites"][0]["flip_x"] = 0 + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_sprite_flip_y_not_bool(test_dir): + data = valid_scene() + data["sprites"][0]["flip_y"] = 1 + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_sprite_rotation_not_int(test_dir): + data = valid_scene() + data["sprites"][0]["rotation"] = "90" + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_sprite_rotation_invalid(test_dir): + data = valid_scene() + data["sprites"][0]["rotation"] = 45 + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + +def test_scene_tile_map_jagged(test_dir): + data = valid_scene() + data["tile_map"][5] = [0] * 15 + with pytest.raises(SceneError): + SceneParser(make_scene(test_dir, data)) + + +# -- Blitter helpers ---------------------------------------------------------- + +def make_vram(base_dir, tile_data=None, sprite_data=None): + t = make_tmp_bin(base_dir, tile_data or bytes(32768), "tiles.bin") + s = make_tmp_bin(base_dir, sprite_data or bytes(32768), "sprites.bin") + return VirtualVRAM(t, s) + + +# -- Blitter init / _validate ------------------------------------------------- + +def test_blitter_init_tile_ok(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "tile", 0, 0, RenderingPipeline.get_buf()) + assert b.transparent_index == 0 + assert b._buffer.shape == (FRAME_H, FRAME_W) + assert b._buffer.dtype == np.uint8 + +def test_blitter_init_sprite_ok(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "sprite", 15, 7, RenderingPipeline.get_buf()) + assert b.transparent_index == 7 + +def test_blitter_invalid_asset_type(test_dir): + vram = make_vram(test_dir) + with pytest.raises(BlitterException): + Blitter(vram, "block", 0, 0, RenderingPipeline.get_buf()) + +def test_blitter_idx_not_int(test_dir): + vram = make_vram(test_dir) + with pytest.raises(BlitterException): + Blitter(vram, "tile", "0", 0, RenderingPipeline.get_buf()) + +def test_blitter_tile_idx_out_of_range_high(test_dir): + vram = make_vram(test_dir) + with pytest.raises(BlitterException): + Blitter(vram, "tile", 64, 0, RenderingPipeline.get_buf()) + +def test_blitter_tile_idx_out_of_range_low(test_dir): + vram = make_vram(test_dir) + with pytest.raises(BlitterException): + Blitter(vram, "tile", -1, 0, RenderingPipeline.get_buf()) + +def test_blitter_sprite_idx_out_of_range_high(test_dir): + vram = make_vram(test_dir) + with pytest.raises(BlitterException): + Blitter(vram, "sprite", 16, 0, RenderingPipeline.get_buf()) + +def test_blitter_sprite_idx_out_of_range_low(test_dir): + vram = make_vram(test_dir) + with pytest.raises(BlitterException): + Blitter(vram, "sprite", -1, 0, RenderingPipeline.get_buf()) + +def test_blitter_transparent_index_not_int(test_dir): + vram = make_vram(test_dir) + with pytest.raises(BlitterException): + Blitter(vram, "tile", 0, "0", RenderingPipeline.get_buf()) + +def test_blitter_transparent_index_too_high(test_dir): + vram = make_vram(test_dir) + with pytest.raises(BlitterException): + Blitter(vram, "tile", 0, 16, RenderingPipeline.get_buf()) + +def test_blitter_transparent_index_negative(test_dir): + vram = make_vram(test_dir) + with pytest.raises(BlitterException): + Blitter(vram, "tile", 0, -1, RenderingPipeline.get_buf()) + + +# -- blit_tile ---------------------------------------------------------------- + +def test_blit_tile_writes_to_buffer(test_dir): + vram = make_vram(test_dir, tile_data=make_tile_sheet(0, 0xFF)) + b = Blitter(vram, "tile", 0, 0, RenderingPipeline.get_buf()) + b.blit_tile(0, 0) + assert b._buffer[0:32, 0:32].min() == 15 + assert b._buffer[0:32, 0:32].max() == 15 + +def test_blit_tile_correct_position(test_dir): + vram = make_vram(test_dir, tile_data=make_tile_sheet(0, 0xFF)) + b = Blitter(vram, "tile", 0, 0, RenderingPipeline.get_buf()) + b.blit_tile(1, 2) + assert b._buffer[32:64, 64:96].min() == 15 + assert b._buffer[0:32, 0:32].max() == 0 + +def test_blit_tile_row_not_int(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "tile", 0, 0, RenderingPipeline.get_buf()) + with pytest.raises(BlitterException): + b.blit_tile("0", 0) + +def test_blit_tile_col_not_int(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "tile", 0, 0, RenderingPipeline.get_buf()) + with pytest.raises(BlitterException): + b.blit_tile(0, "0") + +def test_blit_tile_row_out_of_range_high(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "tile", 0, 0, RenderingPipeline.get_buf()) + with pytest.raises(BlitterException): + b.blit_tile(15, 0) + +def test_blit_tile_row_out_of_range_low(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "tile", 0, 0, RenderingPipeline.get_buf()) + with pytest.raises(BlitterException): + b.blit_tile(-1, 0) + +def test_blit_tile_col_out_of_range_high(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "tile", 0, 0, RenderingPipeline.get_buf()) + with pytest.raises(BlitterException): + b.blit_tile(0, 20) + +def test_blit_tile_col_out_of_range_low(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "tile", 0, 0, RenderingPipeline.get_buf()) + with pytest.raises(BlitterException): + b.blit_tile(0, -1) + + +# -- _transform --------------------------------------------------------------- + +def test_transform_identity(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + m = np.arange(64 * 64, dtype=np.uint8).reshape(64, 64) % 16 + assert np.array_equal(b._transform(m, False, False, 0), m) + +def test_transform_flip_x(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + m = np.arange(64 * 64, dtype=np.uint8).reshape(64, 64) % 16 + assert np.array_equal(b._transform(m, True, False, 0), np.fliplr(m)) + +def test_transform_flip_y(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + m = np.arange(64 * 64, dtype=np.uint8).reshape(64, 64) % 16 + assert np.array_equal(b._transform(m, False, True, 0), np.flipud(m)) + +def test_transform_rotation_90(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + m = np.arange(64 * 64, dtype=np.uint8).reshape(64, 64) % 16 + assert np.array_equal(b._transform(m, False, False, 90), np.rot90(m, 1)) + +def test_transform_rotation_180(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + m = np.arange(64 * 64, dtype=np.uint8).reshape(64, 64) % 16 + assert np.array_equal(b._transform(m, False, False, 180), np.rot90(m, 2)) + +def test_transform_rotation_270(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + m = np.arange(64 * 64, dtype=np.uint8).reshape(64, 64) % 16 + assert np.array_equal(b._transform(m, False, False, 270), np.rot90(m, 3)) + +def test_transform_flip_x_and_y(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + m = np.arange(64 * 64, dtype=np.uint8).reshape(64, 64) % 16 + assert np.array_equal(b._transform(m, True, True, 0), np.flipud(np.fliplr(m))) + + +# -- _clip -------------------------------------------------------------------- + +def test_clip_fully_inside(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + dst, src = b._clip(0, 0) + assert dst == (slice(0, 64), slice(0, 64)) + assert src == (slice(0, 64), slice(0, 64)) + +def test_clip_centered(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + dst, src = b._clip(100, 50) + assert dst == (slice(50, 114), slice(100, 164)) + assert src == (slice(0, 64), slice(0, 64)) + +def test_clip_top(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + dst, src = b._clip(0, -10) + assert dst == (slice(0, 54), slice(0, 64)) + assert src == (slice(10, 64), slice(0, 64)) + +def test_clip_left(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + dst, src = b._clip(-10, 0) + assert dst == (slice(0, 64), slice(0, 54)) + assert src == (slice(0, 64), slice(10, 64)) + +def test_clip_bottom(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + dst, src = b._clip(0, FRAME_H - 32) + assert dst == (slice(FRAME_H - 32, FRAME_H), slice(0, 64)) + assert src == (slice(0, 32), slice(0, 64)) + +def test_clip_right(test_dir): + vram = make_vram(test_dir) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + dst, src = b._clip(FRAME_W - 32, 0) + assert dst == (slice(0, 64), slice(FRAME_W - 32, FRAME_W)) + assert src == (slice(0, 64), slice(0, 32)) + + +# -- blit_sprite -------------------------------------------------------------- + +def test_blit_sprite_all_opaque(test_dir): + vram = make_vram(test_dir, sprite_data=make_sprite_sheet(0, 0xFF)) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + b.blit_sprite(0, 0, False, False, 0) + assert b._buffer[0:64, 0:64].min() == 15 + assert b._buffer[0:64, 0:64].max() == 15 + +def test_blit_sprite_all_transparent(test_dir): + vram = make_vram(test_dir, sprite_data=make_sprite_sheet(0, 0x00)) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + b.blit_sprite(0, 0, False, False, 0) + assert b._buffer[0:64, 0:64].max() == 0 + +def test_blit_sprite_correct_position(test_dir): + vram = make_vram(test_dir, sprite_data=make_sprite_sheet(0, 0xFF)) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + b.blit_sprite(100, 50, False, False, 0) + assert b._buffer[50:114, 100:164].min() == 15 + assert b._buffer[0:50, 0:100].max() == 0 + +def test_blit_sprite_clipping_left(test_dir): + vram = make_vram(test_dir, sprite_data=make_sprite_sheet(0, 0xFF)) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + b.blit_sprite(-32, 0, False, False, 0) + assert b._buffer[0:64, 0:32].min() == 15 + assert b._buffer[0:64, 32:64].max() == 0 + +def test_blit_sprite_mixed_transparency(test_dir): + # 0xF0 → pixels alternano 15 (opaco) e 0 (trasparente) + vram = make_vram(test_dir, sprite_data=make_sprite_sheet(0, 0xF0)) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + b.blit_sprite(0, 0, False, False, 0) + assert b._buffer[0, 0] == 15 + assert b._buffer[0, 1] == 0 + +def test_blit_sprite_clipping_top(test_dir): + vram = make_vram(test_dir, sprite_data=make_sprite_sheet(0, 0xFF)) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + b.blit_sprite(0, -32, False, False, 0) + assert b._buffer[0:32, 0:64].min() == 15 + assert b._buffer[32:64, 0:64].max() == 0 + +def test_blit_sprite_clipping_right(test_dir): + vram = make_vram(test_dir, sprite_data=make_sprite_sheet(0, 0xFF)) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + b.blit_sprite(FRAME_W - 32, 0, False, False, 0) + assert b._buffer[0:64, FRAME_W - 32:FRAME_W].min() == 15 + assert b._buffer[0:64, FRAME_W - 96:FRAME_W - 32].max() == 0 + +def test_blit_sprite_clipping_bottom(test_dir): + vram = make_vram(test_dir, sprite_data=make_sprite_sheet(0, 0xFF)) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + b.blit_sprite(0, FRAME_H - 32, False, False, 0) + assert b._buffer[FRAME_H - 32:FRAME_H, 0:64].min() == 15 + assert b._buffer[FRAME_H - 96:FRAME_H - 32, 0:64].max() == 0 + +def test_blit_sprite_fully_outside_right(test_dir): + vram = make_vram(test_dir, sprite_data=make_sprite_sheet(0, 0xFF)) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + b.blit_sprite(FRAME_W, 0, False, False, 0) + assert b._buffer.max() == 0 + +def test_blit_sprite_fully_outside_bottom(test_dir): + vram = make_vram(test_dir, sprite_data=make_sprite_sheet(0, 0xFF)) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + b.blit_sprite(0, FRAME_H, False, False, 0) + assert b._buffer.max() == 0 + +def test_blit_sprite_fully_outside_left(test_dir): + vram = make_vram(test_dir, sprite_data=make_sprite_sheet(0, 0xFF)) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + b.blit_sprite(-64, 0, False, False, 0) + assert b._buffer.max() == 0 + +def test_blit_sprite_fully_outside_top(test_dir): + vram = make_vram(test_dir, sprite_data=make_sprite_sheet(0, 0xFF)) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + b.blit_sprite(0, -64, False, False, 0) + assert b._buffer.max() == 0 + +# C1 regression: sprite beyond the positive edge (x > FRAME_W, y > FRAME_H). +# Before the _clip fix the negative dst width produced inconsistent src slices +# -> IndexError. The exact-edge tests (FRAME_W/-64) did not catch it. +def test_blit_sprite_beyond_right_no_crash(test_dir): + vram = make_vram(test_dir, sprite_data=make_sprite_sheet(0, 0xFF)) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + b.blit_sprite(FRAME_W + 60, 32, False, False, 0) + assert b._buffer.max() == 0 + +def test_blit_sprite_beyond_bottom_no_crash(test_dir): + vram = make_vram(test_dir, sprite_data=make_sprite_sheet(0, 0xFF)) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + b.blit_sprite(32, FRAME_H + 20, False, False, 0) + assert b._buffer.max() == 0 + +def test_blit_sprite_far_left_no_crash(test_dir): + vram = make_vram(test_dir, sprite_data=make_sprite_sheet(0, 0xFF)) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + b.blit_sprite(-100, 32, False, False, 0) + assert b._buffer.max() == 0 + +def test_blit_sprite_transform_and_clip(test_dir): + vram = make_vram(test_dir, sprite_data=make_sprite_sheet(0, 0xFF)) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + b.blit_sprite(-32, 0, True, False, 90) + assert b._buffer[0:64, 0:32].min() == 15 + +def test_blit_sprite_z_order(test_dir): + vram = make_vram(test_dir, sprite_data=make_sprite_sheet(0, 0xFF)) + b = Blitter(vram, "sprite", 0, 0, RenderingPipeline.get_buf()) + b.blit_sprite(0, 0, False, False, 0) + # second blit with all-transparent sprite should not overwrite + vram2 = make_vram(test_dir, sprite_data=make_sprite_sheet(0, 0x00)) + b2 = Blitter(vram2, "sprite", 0, 0, b._buffer) + b2.blit_sprite(0, 0, False, False, 0) + assert b2._buffer[0:64, 0:64].min() == 15 + + +# -- RenderingPipeline helpers ------------------------------------------------ + +PIPELINE_PALETTE = [[i * 17, i * 17, i * 17] for i in range(16)] + + +def pack_matrix(matrix): + flat = matrix.ravel().astype(np.uint8) + return ((flat[0::2] << 4) | flat[1::2]).tobytes() + + +def make_pipeline_palette(base_dir): + f = base_dir / "palette.json" + f.write_text(json.dumps(PIPELINE_PALETTE)) + return str(f) + + +def make_pipeline_tiles(base_dir, tile0_index=1): + matrix = np.zeros((256, 256), dtype=np.uint8) + matrix[0:32, 0:32] = tile0_index + f = base_dir / "tiles.bin" + f.write_bytes(pack_matrix(matrix)) + return str(f) + + +def make_pipeline_sprites(base_dir, sprite0_index=2): + matrix = np.zeros((256, 256), dtype=np.uint8) + matrix[0:64, 0:64] = sprite0_index + f = base_dir / "sprites.bin" + f.write_bytes(pack_matrix(matrix)) + return str(f) + + +def make_pipeline_scene(base_dir, transparent_index=0, sprites=None): + data = { + "transparent_index": transparent_index, + "tile_map": [[0] * 20 for _ in range(15)], + "sprites": sprites or [], + } + f = base_dir / "scene.json" + f.write_text(json.dumps(data)) + return str(f) + + +def make_pipeline(base_dir, tile0_index=1, sprite0_index=2, transparent_index=0, sprites=None): + palette = make_pipeline_palette(base_dir) + tiles = make_pipeline_tiles(base_dir, tile0_index) + sprites_bin = make_pipeline_sprites(base_dir, sprite0_index) + scene = make_pipeline_scene(base_dir, transparent_index, sprites) + output = str(base_dir / "output.png") + return RenderingPipeline(palette, scene, tiles, sprites_bin, output) + + +# -- get_buf ------------------------------------------------------------------ + +def test_pipeline_get_buf_shape(): + assert RenderingPipeline.get_buf().shape == (FRAME_H, FRAME_W) + +def test_pipeline_get_buf_dtype(): + assert RenderingPipeline.get_buf().dtype == np.uint8 + +def test_pipeline_get_buf_zeros(): + assert np.all(RenderingPipeline.get_buf() == 0) + +def test_pipeline_get_buf_independent(): + buf1 = RenderingPipeline.get_buf() + buf2 = RenderingPipeline.get_buf() + buf1[0, 0] = 5 + assert buf2[0, 0] == 0 + + +# -- __repr__ ----------------------------------------------------------------- + +def test_pipeline_repr(test_dir): + rp = make_pipeline(test_dir) + r = repr(rp) + assert "palette" in r + assert "scene" in r + assert "tiles" in r + assert "sprites" in r + assert "output" in r + + +# -- _export ------------------------------------------------------------------ + +def test_pipeline_export_creates_file(test_dir): + rp = make_pipeline(test_dir) + rp._export(RenderingPipeline.get_buf()) + assert Path(rp._output_path).exists() + +def test_pipeline_export_image_size(test_dir): + rp = make_pipeline(test_dir) + rp._export(RenderingPipeline.get_buf()) + img = PILImage.open(rp._output_path) + assert img.size == (FRAME_W, FRAME_H) + +def test_pipeline_export_pixel_color(test_dir): + rp = make_pipeline(test_dir) + buf = RenderingPipeline.get_buf() + buf[0, 0] = 1 # palette index 1 → [17, 17, 17] + rp._export(buf) + assert PILImage.open(rp._output_path).getpixel((0, 0)) == (17, 17, 17) + +def test_pipeline_export_bad_path(test_dir): + rp = make_pipeline(test_dir) + rp._output_path = str(test_dir / "non_esiste" / "output.png") # directory mancante + with pytest.raises(RenderingException): + rp._export(RenderingPipeline.get_buf()) + + +# -- _compose ----------------------------------------------------------------- + +def test_pipeline_compose_tiles_written(test_dir): + rp = make_pipeline(test_dir, tile0_index=1) + buf = RenderingPipeline.get_buf() + rp._compose(buf) + assert buf[0, 0] == 1 + +def test_pipeline_compose_full_tilemap(test_dir): + rp = make_pipeline(test_dir, tile0_index=1) + buf = RenderingPipeline.get_buf() + rp._compose(buf) + assert buf[479, 639] == 1 + +def test_pipeline_compose_sprite_over_tile(test_dir): + sprite = {"id": 0, "x": 0, "y": 0, "flip_x": False, "flip_y": False, "rotation": 0} + rp = make_pipeline(test_dir, tile0_index=1, sprite0_index=2, transparent_index=0, sprites=[sprite]) + buf = RenderingPipeline.get_buf() + rp._compose(buf) + assert buf[0, 0] == 2 + +def test_pipeline_compose_transparent_sprite_not_drawn(test_dir): + sprite = {"id": 0, "x": 0, "y": 0, "flip_x": False, "flip_y": False, "rotation": 0} + rp = make_pipeline(test_dir, tile0_index=1, sprite0_index=0, transparent_index=0, sprites=[sprite]) + buf = RenderingPipeline.get_buf() + rp._compose(buf) + assert buf[0, 0] == 1 + + +# -- render() ----------------------------------------------------------------- + +def test_pipeline_render_creates_output(test_dir): + rp = make_pipeline(test_dir) + rp.render() + assert Path(rp._output_path).exists() + +def test_pipeline_render_output_size(test_dir): + rp = make_pipeline(test_dir) + rp.render() + assert PILImage.open(rp._output_path).size == (FRAME_W, FRAME_H) + +def test_pipeline_compose_sprite_z_order(test_dir): + # sprite 0 (index 2) drawn first, sprite 1 (index 3) drawn second at same position — sprite 1 wins + matrix = np.zeros((256, 256), dtype=np.uint8) + matrix[0:64, 0:64] = 2 # sprite 0 + matrix[0:64, 64:128] = 3 # sprite 1 + (test_dir / "sprites.bin").write_bytes(pack_matrix(matrix)) + scene_data = { + "transparent_index": 0, + "tile_map": [[0] * 20 for _ in range(15)], + "sprites": [ + {"id": 0, "x": 0, "y": 0, "flip_x": False, "flip_y": False, "rotation": 0}, + {"id": 1, "x": 0, "y": 0, "flip_x": False, "flip_y": False, "rotation": 0}, + ], + } + (test_dir / "scene.json").write_text(json.dumps(scene_data)) + rp = RenderingPipeline( + make_pipeline_palette(test_dir), + str(test_dir / "scene.json"), + make_pipeline_tiles(test_dir), + str(test_dir / "sprites.bin"), + str(test_dir / "output.png"), + ) + buf = RenderingPipeline.get_buf() + rp._compose(buf) + assert buf[0, 0] == 3 + +def test_pipeline_compose_sprite_transformation(test_dir): + # sprite 0: left half (cols 0-31) opaque index 2, right half transparent index 0 + # with flip_x the halves swap: buf[0,0] stays tile=1, buf[0,32] becomes 2 + matrix = np.zeros((256, 256), dtype=np.uint8) + matrix[0:64, 0:32] = 2 + (test_dir / "sprites.bin").write_bytes(pack_matrix(matrix)) + scene_data = { + "transparent_index": 0, + "tile_map": [[0] * 20 for _ in range(15)], + "sprites": [{"id": 0, "x": 0, "y": 0, "flip_x": True, "flip_y": False, "rotation": 0}], + } + (test_dir / "scene.json").write_text(json.dumps(scene_data)) + rp = RenderingPipeline( + make_pipeline_palette(test_dir), + str(test_dir / "scene.json"), + make_pipeline_tiles(test_dir, tile0_index=1), + str(test_dir / "sprites.bin"), + str(test_dir / "output.png"), + ) + buf = RenderingPipeline.get_buf() + rp._compose(buf) + assert buf[0, 0] == 1 # flip moved opaque pixels away from col 0 + assert buf[0, 32] == 2 # flip moved opaque pixels to col 32 + +def test_pipeline_compose_sprite_clipping(test_dir): + # sprite at x=-32: only cols 32-63 of the sprite are visible (frame cols 0-31) + sprite = {"id": 0, "x": -32, "y": 0, "flip_x": False, "flip_y": False, "rotation": 0} + rp = make_pipeline(test_dir, tile0_index=1, sprite0_index=2, transparent_index=0, sprites=[sprite]) + buf = RenderingPipeline.get_buf() + rp._compose(buf) + assert buf[0, 0] == 2 # visible part of sprite drawn + assert buf[0, 32] == 1 # tile only — sprite does not reach here + + +# -- main --------------------------------------------------------------------- + +def test_main_help(monkeypatch): + monkeypatch.setattr(sys, "argv", ["main.py", "--help"]) + with pytest.raises(SystemExit) as exc: + main() + assert exc.value.code == 0 + +def test_main_missing_args(monkeypatch): + monkeypatch.setattr(sys, "argv", ["main.py"]) + with pytest.raises(SystemExit) as exc: + main() + assert exc.value.code != 0 + +def test_main_runs_ok(monkeypatch, test_dir): + rp = make_pipeline(test_dir) + monkeypatch.setattr(sys, "argv", [ + "main.py", rp._palette_path, rp._scene_path, + rp._tiles_path, rp._sprites_path, rp._output_path, + ]) + main() + assert Path(rp._output_path).exists() + +def test_main_file_not_found(monkeypatch, test_dir): + output = str(test_dir / "output.png") + monkeypatch.setattr(sys, "argv", [ + "main.py", "no.json", "no.json", "no.bin", "no.bin", output, + ]) + with pytest.raises(FileNotFoundError): + main() diff --git a/renderer/uv.lock b/renderer/uv.lock new file mode 100644 index 0000000..d19ab82 --- /dev/null +++ b/renderer/uv.lock @@ -0,0 +1,143 @@ +version = 1 +revision = 3 +requires-python = ">=3.14" + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pap-render" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [ + { name = "numpy", specifier = ">=2.4.6" }, + { name = "pillow", specifier = ">=12.2.0" }, + { name = "pytest", specifier = ">=9.0.3" }, +] + +[[package]] +name = "pillow" +version = "12.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, + { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, + { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, + { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, + { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, + { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, + { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, + { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, + { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, + { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, + { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] From a274c8ea9534f22789e951b33c51de30a90f6360 Mon Sep 17 00:00:00 2001 From: Manuel Magnabosco Date: Tue, 25 Aug 2026 00:28:12 +0200 Subject: [PATCH 2/2] Run renderer tests in isolated CI --- .github/workflows/ci.yml | 20 ++++++++++++++++++++ README.md | 18 +++++++++++++++++- renderer/UPSTREAM.md | 6 +++--- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c31c720..d111f10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,26 @@ jobs: - name: Build and test run: make check + renderer-tests: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - name: Install uv and Python + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 + with: + python-version: "3.14" + + - name: Test renderer + working-directory: renderer + run: uv run --locked pytest -q + strict-compilers: runs-on: ubuntu-latest diff --git a/README.md b/README.md index 6f5d9d1..7482fc4 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ tileability. ## Current status -Implemented and tested as of 24 August 2026: +Implemented and tested as of 25 August 2026: - canonical static definition of the 23 atomic Wang tiles; - generalized-tile family metadata kept outside solver semantics; @@ -243,6 +243,9 @@ Implemented and tested as of 24 August 2026: - a JSON Lines comparison suite over fixed `.cm13` inputs, with separate prepared-Region and file-to-verified-decision scopes for the native reference, native optimized, Boolean Z3, and Wang Z3 paths; +- a provenance-pinned PAP Render snapshot under `renderer/`, preserving its + standalone Python 3.14 project and 144-test suite without coupling its + graphical dependencies to the native core or Python reference tools; - C17/OpenMP build scaffold and GitHub Actions CI with strict GCC/Clang, ASan, UBSan, GCC static analysis, Memcheck, and Cachegrind paths. @@ -284,6 +287,19 @@ make clean make check ``` +The imported renderer remains a separate locked Python project. Its Pillow and +NumPy dependencies are not installed by the root project or exercised by +`make check`. Run its 144 tests independently: + +```sh +cd renderer +uv run --locked pytest -q +``` + +CI mirrors that command in a separate read-only Python 3.14 job. Snapshot +provenance and update instructions are recorded in +[`renderer/UPSTREAM.md`](renderer/UPSTREAM.md). + Useful individual targets: ```sh diff --git a/renderer/UPSTREAM.md b/renderer/UPSTREAM.md index 31179aa..f4a305f 100644 --- a/renderer/UPSTREAM.md +++ b/renderer/UPSTREAM.md @@ -40,9 +40,9 @@ renderer environment under `renderer/.venv`; its local `.gitignore` excludes that environment and generated Python/build files. The preserved `renderer/.github/workflows/ci.yml` is nested upstream evidence; -GitHub does not run it as a workflow of the containing repository. Adding a -separate top-level renderer CI job is deliberately outside this snapshot-only -import. +GitHub does not run it as a workflow of the containing repository. The +top-level `.github/workflows/ci.yml` instead runs the same locked suite in a +separate read-only Python 3.14 job. ## Updating the snapshot