testsAndMisc/python_pkg/artgate/routes/shapes.py
Krzysztof kuhy Rudnicki e6e0ba5ab3
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
artgate: diffusion + 3D routes, full vector coverage, and the margin gate
Closes the remaining bake-off cells. The phase's real result was not a route
score but a MISSING GATE.

All twelve SDXL icons measured 100% opaque coverage -- square tiles with no
sprite silhouette -- and passed every gate in the harness. Gate-pass was
measuring pipeline hygiene, not usability. MARGIN_TOO_SMALL inverts the table
from a meaningless 12/12/12 tie to:

    diffusion   0/12      (every output full-bleed)
    vector     10/12
    procedural 11/12

The two non-diffusion failures were true positives in MY shapes, not gate
error: the procedural sword pommel ran off the bottom edge and the vector meat
bone clipped the top. Both fixed; both routes back to 12/12.

Diffusion recovers to 12/12 with flood-fill background removal, but visual
inspection shows the fill eats into the art (bone reduced to a fragment, sword
nearly erased). Reported as inadequate rather than shipped -- proper matting is
needed before route (b) is a usable pipeline.

Blender exits 0 even when its embedded script raises: BLENDER_EEVEE_NEXT does
not exist in 5.2, and the failure produced a clean exit and no file. render()
now asserts the output exists, or the route would report phantom successes.

Model gating verified at execution time, never recalled: SDXL base and
nerijs/pixel-art-xl are ungated, FLUX.1-dev is gated=auto, and
runwayml/stable-diffusion-v1-5 now 307-redirects.

The finishing stage now generalises across three continuous-tone sources
(SVG raster, Blender render, SDXL): each is 0/N raw and N/N finished.

Verified: 114 tests, 100% branch coverage on 13 modules, zero suppressions,
all hooks pass. Note: a local pytest run masked a missing branch that the
pre-commit hook caught -- the hook's run is authoritative.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T9rQ12yZ7sJxiPQTCS5dUF
2026-08-15 22:51:31 +02:00

172 lines
4.8 KiB
Python

"""Route (a): the twelve subject drawings.
Each shape is given at least one distinguishing FEATURE, because the
`item-icons` measurement found that geometric correctness alone reads as a
placeholder -- a circle with a rim was rejected as "very barebones". The
features here are the fix that finding implies, applied up front.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Final
from python_pkg.artgate.routes.procedural import (
_blank,
_disc,
_put,
_rect,
_ring,
outline,
)
if TYPE_CHECKING: # pragma: no cover
from collections.abc import Callable
import numpy as np
from numpy.typing import NDArray
def _key(c: NDArray[np.uint8]) -> None:
"""Draw a key: bow, shaft and two wards."""
_ring(c, 10.5, 11.5, 5.0, 2.6, "gold")
_rect(c, 14, 15, 16, 25, "gold")
_rect(c, 17, 22, 20, 23, "gold")
_rect(c, 17, 25, 19, 26, "gold")
def _scroll(c: NDArray[np.uint8]) -> None:
"""Draw a scroll: parchment body with rolled ends."""
_rect(c, 7, 8, 24, 23, "light")
_rect(c, 6, 6, 25, 9, "mid")
_rect(c, 6, 22, 25, 25, "mid")
for y in (12, 15, 18):
_rect(c, 10, y, 21, y, "shadow")
def _book(c: NDArray[np.uint8]) -> None:
"""Draw a book: cover, spine and a clasp."""
_rect(c, 6, 6, 25, 26, "red")
_rect(c, 6, 6, 9, 26, "shadow")
_rect(c, 11, 9, 23, 10, "light")
_rect(c, 22, 14, 25, 18, "gold")
def _bone(c: NDArray[np.uint8]) -> None:
"""Draw a bone: shaft with four knobs."""
_rect(c, 12, 8, 19, 23, "light")
for cx, cy in ((11, 8), (20, 8), (11, 23), (20, 23)):
_disc(c, cx, cy, 4.0, "light")
_rect(c, 14, 12, 15, 19, "mid")
# The gem's widest row; above it the silhouette flares, below it tapers.
_GEM_WAIST = 13
def _gem(c: NDArray[np.uint8]) -> None:
"""Draw a gem: faceted lozenge with a highlight facet."""
for row in range(6, 27):
offset = row - _GEM_WAIST
span = 11 - abs(offset) if offset < 1 else 11 - offset
if span > 0:
_rect(c, 16 - span, row, 15 + span, row, "cyan")
_rect(c, 12, 9, 15, 12, "light")
def _bomb(c: NDArray[np.uint8]) -> None:
"""Draw a bomb: sphere, fuse and a spark."""
_disc(c, 15.0, 19.0, 9.0, "shadow")
_disc(c, 12.0, 16.0, 3.0, "mid")
_rect(c, 16, 6, 17, 10, "mid")
_rect(c, 18, 4, 19, 6, "gold")
_put(c, 20, 3, "red")
def _potion(c: NDArray[np.uint8]) -> None:
"""Draw a potion: round flask, neck, stopper and a liquid line."""
_disc(c, 15.5, 20.0, 8.0, "cyan")
_rect(c, 13, 7, 18, 13, "light")
_rect(c, 12, 5, 19, 7, "gold")
_rect(c, 9, 19, 22, 20, "light")
def _sword(c: NDArray[np.uint8]) -> None:
"""Draw a sword: blade, fuller, crossguard and pommel."""
_rect(c, 14, 3, 17, 21, "light")
_rect(c, 15, 5, 16, 19, "mid")
_rect(c, 9, 22, 22, 23, "gold")
_rect(c, 14, 23, 17, 26, "shadow")
_disc(c, 15.5, 27.0, 2.4, "gold")
def _shield(c: NDArray[np.uint8]) -> None:
"""Draw a shield: heater shape with boss and a chevron."""
_rect(c, 6, 5, 25, 17, "mid")
for row in range(18, 28):
inset = row - 17
_rect(c, 6 + inset, row, 25 - inset, row, "mid")
_rect(c, 6, 5, 25, 7, "gold")
_disc(c, 15.5, 15.0, 3.2, "gold")
def _coin(c: NDArray[np.uint8]) -> None:
"""Draw a coin: disc, rim and a struck emblem."""
_disc(c, 15.5, 15.5, 11.0, "gold")
_ring(c, 15.5, 15.5, 11.0, 9.0, "shadow")
_rect(c, 13, 11, 18, 12, "shadow")
_rect(c, 15, 11, 16, 20, "shadow")
def _ring_item(c: NDArray[np.uint8]) -> None:
"""Draw a ring: band with a raised setting and stone."""
_ring(c, 15.5, 18.0, 9.0, 6.0, "gold")
_rect(c, 12, 6, 19, 9, "gold")
_disc(c, 15.5, 7.0, 3.6, "cyan")
_put(c, 14, 6, "light")
def _meat(c: NDArray[np.uint8]) -> None:
"""Draw a meat cut: ham body with a protruding bone."""
_disc(c, 16.0, 19.0, 9.5, "red")
_disc(c, 12.0, 16.0, 3.0, "gold")
_rect(c, 14, 4, 17, 11, "light")
_disc(c, 14.0, 4.0, 2.2, "light")
_disc(c, 17.5, 4.0, 2.2, "light")
DRAWINGS: Final[dict[str, Callable[[NDArray[np.uint8]], None]]] = {
"key": _key,
"scroll": _scroll,
"book": _book,
"bone": _bone,
"gem": _gem,
"bomb": _bomb,
"potion": _potion,
"sword": _sword,
"shield": _shield,
"coin": _coin,
"ring": _ring_item,
"meat": _meat,
}
def draw(name: str) -> NDArray[np.uint8]:
"""Render one subject as a 32x32 RGBA array.
Args:
name: One of the twelve fixed subject names.
Returns:
The rendered canvas, outlined.
Raises:
KeyError: If the subject has no drawing.
"""
if name not in DRAWINGS:
msg = f"no procedural drawing for {name!r}"
raise KeyError(msg)
canvas = _blank()
DRAWINGS[name](canvas)
outline(canvas)
return canvas