Skip to content

feat(tools): refimg — draw the design-alignment gate's reference images - #334

Merged
stellarfeline merged 3 commits into
mainfrom
feat/refimg-ideogram
Aug 8, 2026
Merged

feat(tools): refimg — draw the design-alignment gate's reference images#334
stellarfeline merged 3 commits into
mainfrom
feat/refimg-ideogram

Conversation

@stellarfeline

Copy link
Copy Markdown
Owner

The design-alignment gate (skill step 4b) is confirmed against pictures. Owner
correction, 2026-08-07: the pictures it wants are reference images — concept
art drawn from the scene description before any prefab exists — not renders.
A render images a candidate prefab and belongs to contact-sheet curation later.
Building prefabs first and rendering them inverts the gate: it makes the owner
approve a build instead of a design.

tools/refimg.py is the first producer. Human-in-the-loop, advisory, never called
from a build.

Design decisions worth reviewing

The provider discriminant carries capability, not wire format. The obvious
move — reuse i18n's provider = "openai-compatible" — is a trap here. Google's
OpenAI-compatibility layer exposes /v1/images/generations but supports only
prompt, model, n, size, response_format, has no image input, and silently
ignores
anything else. A style-anchored request routed through it would drop the
anchor without an error and return eight unrelated pictures. So only verified
providers are listed (ideogram-v3 today) and anything else is refused.

The full provider response is saved beside every image (<stem>.json). The
anchor a series depends on is recoverable only from what the provider actually
returns, and the API reference documents style_codes as an input without
promising one comes back. Record it and look — do not assume.

Failure modes are asymmetric, matching i18n.md: absent config prints what to
add and exits 2; malformed config is a hard error (inline api_key, unknown
provider, bad rendering_speed). A typo must never silently downgrade the anchor.
The key never enters a file — api_key_env names an env var read at call time,
never stored or logged, and it is not in the HTTP error path either.

What CI proves, and what it does not

Every refusal above was exercised by hand (transcripts in the commit trail):
happy-path --dry-run, inline-key refusal, unknown-provider refusal, and the
mutually-exclusive-anchor refusal. No live API call has been made — nothing
here is proven against the provider, and the first real call is what will settle
whether a style code is returned at all.

Blast radius

None. .refimg/ is gitignored; ref images are never shipped and never enter the
content repo, so output licensing never touches a shipped asset (ADR-0013), and
nothing is a build input (ADR-0006).

Docs + skill updated in the same PR (tooling-sync rule): a tools.md row, and an
advisory line at step 4b naming the reference/render distinction.

Left open, deliberately

Step 4b still describes both near and far view in render vocabulary ("orbit
render", "player-POV shots"). Rewriting that is a workflow change, not a tool
addition — flagged for the owner rather than folded in here.

stellarfeline and others added 3 commits August 8, 2026 00:08
The gate between the plan and the authoring (skill step 4b) is confirmed against
PICTURES, and the pictures it wants are concept art drawn from the scene
description BEFORE any prefab exists. That is a different artifact from a render,
which images a candidate prefab and belongs to contact-sheet curation later. Two
stages, two producers — the skill said "render" at both, which inverts the gate:
it makes the owner approve a build instead of a design.

`tools/refimg.py` is the first producer. Human-in-the-loop and advisory: prompt
iteration is the point, and the tool exists so a creator's key has a destination.

## What is deliberate

**The provider name carries CAPABILITY, not wire format.** Google's
OpenAI-compatible images endpoint accepts a call, has no image input, and
*silently ignores* unknown parameters — so routing a style-anchored request
through the i18n `openai-compatible` shape would discard the anchor with no error
and produce N unrelated pictures. Only verified providers are listed
(`ideogram-v3`); anything else is refused, loudly.

**Malformed config is a hard error, absent config is a message.** An inline
`api_key`, an unknown provider, a bad `rendering_speed` each exit 2 saying what is
wrong. A typo must never silently downgrade the creator to a weaker anchor. The
key never enters a file: `api_key_env` names an env var, read at call time.

**The full provider response is written beside every image.** The anchor a series
depends on is only recoverable from what the provider actually RETURNS, and no
documentation promises a style code comes back — so record it and look, rather
than assume it can be recovered later.

**`--style-code` and `--style-ref` are mutually exclusive**, enforced locally
rather than discovered as a 4xx.

## What it cannot touch

Ref images are generation-time working material: `.refimg/` is gitignored, never
shipped, never in the content repo — so image-model output licensing never reaches
a shipped asset (ADR-0013). Nothing here is a build input; a delve's bytes are
untouched (ADR-0006).

Docs and skill updated in the same PR per the tooling-sync rule: a tools.md row,
and an advisory line at step 4b naming the reference/render distinction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjQ5p1Kv5MrkGPumi7yXWL
…tead of a dropped flag

Ideogram 3.0 was picked for its style-CODE anchor — an identifier reapplied
exactly, so a series cannot drift. Two measurements since:

1. The generate response does NOT return a style code (observed fields:
   is_image_safe, prompt, resolution, seed, style_type, upscaled_resolution, url),
   so the code can only come from the web UI. The anchor's advantage over a
   reference image survives, but only by leaving the command line.
2. On the same blind-preference leaderboard, Ideogram 3.0 sits far below the
   Gemini line, while the Ideogram model that ranks near it (4.0) has no anchor
   parameters at all. Optimising the anchor axis alone selected a much weaker
   model, and nothing in the original comparison checked the other axis.

So `gemini-native` lands beside it and the choice is config. Deliberately the
Interactions API and NOT the OpenAI-compatibility layer: that layer has no image
input and silently ignores unknown parameters, so an anchored request through it
would drop the anchor with no error.

Recorded because it contradicts the claim that sold it: Gemini's reference images
are UNTYPED — every one is submitted as `{"type":"image"}` with no field
distinguishing a style reference from a character or object one. The role is
carried by the prompt text, so the anchor is prose-mediated, not structural.

**A flag a provider cannot honour is refused, not dropped.** `--seed` on
gemini-native exits 1 and says what the loss costs (every edit becomes a full
reroll); `--style-code` on it names the flag that does work. This is the same
principle as the provider whitelist: a silently ignored parameter is the failure
mode this whole tool exists to prevent.

Image extraction is now a structural walk over the response rather than a schema
read: the provider's response shape for image bytes is undocumented, and the call
is already paid for by the time save() runs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjQ5p1Kv5MrkGPumi7yXWL
@stellarfeline
stellarfeline merged commit f3027f0 into main Aug 8, 2026
12 checks passed
stellarfeline added a commit that referenced this pull request Aug 8, 2026
…he pattern (#337)

`delvewright.local.toml_test` — a scratch copy made while testing the
refimg provider — was committed in #334 and is on public `main`.
`.gitignore` matched only the exact name `delvewright.local.toml`, so any
suffixed variant fell straight through.

NO KEY LEAKED, and that is a property of the design rather than of luck:
the file names an ENVIRONMENT VARIABLE (`api_key_env`), never a value, so
the worst a committed copy can disclose is a variable name and an aspect
ratio. History audited to confirm it, not assumed — this is the only
matching path ever added (#334's branch commit and its squash), and no
key-shaped literal appears in any .toml/.json/.py across the last 200
commits.

The file is removed and the pattern widened to `delvewright.local.toml*`,
so the next scratch copy is ignored instead of merged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant