mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-14 17:50:47 +01:00
Closes the two CPU-only cells of the art bake-off and builds the bridge the diffusion cell will reuse. Pins the bake-off to the SAME twelve subjects item-icons measured, with its easy/hard split intact. Without this a route tested on distinctive silhouettes beats the 6/12 baseline for free and the decision table means nothing. It also makes the subject-routing rule falsifiable: a generator route is predicted to lift specifically the six human-prior failures, and if it does not, the rule is recorded as wrong. Route (a) procedural: all twelve subjects drawn deterministically from a 7-colour locked palette. Every shape gets a distinguishing feature up front, because item-icons measured that plain primitives read as placeholders however correct the geometry is. Machine gate: 12/12 -- which proves "not malformed", NOT quality. A procedural generator satisfies palette/alpha/canvas constraints trivially; the human contact sheet is the real verdict. Route (d) code-as-art: SVG emitted as text and rasterised by rsvg-convert, so no new Python dependency (cairosvg/svgwrite stay absent). The structural finding, measured on real rasteriser output rather than a synthetic stand-in: raw vector renders pass 0/5 -- every one fails ALPHA_NOT_BINARY, most also TOO_MANY_COLORS. After the finishing stage, 5/5. Continuous-tone routes are therefore GENERATORS FEEDING A FINISHER, not pixel-art generators. finish.py binarises alpha before quantising colour, mirroring the gate order: quantising first blends transparent pixels' arbitrary RGB into the palette. Verified: 92 tests, 100% branch coverage across all ten modules, zero suppressions, all hooks pass. The PLR2004 lint refactor was confirmed pixel-neutral by byte-comparing all 12 icons before and after (12/12 identical). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T9rQ12yZ7sJxiPQTCS5dUF
172 lines
4.8 KiB
Python
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, 24, 17, 27, "shadow")
|
|
_disc(c, 15.5, 28.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
|