feat(tools): refimg — draw the design-alignment gate's reference images - #334
Merged
Conversation
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
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.pyis the first producer. Human-in-the-loop, advisory, never calledfrom 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'sOpenAI-compatibility layer exposes
/v1/images/generationsbut supports onlyprompt, model, n, size, response_format, has no image input, and silentlyignores 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-v3today) and anything else is refused.The full provider response is saved beside every image (
<stem>.json). Theanchor a series depends on is recoverable only from what the provider actually
returns, and the API reference documents
style_codesas an input withoutpromising one comes back. Record it and look — do not assume.
Failure modes are asymmetric, matching
i18n.md: absent config prints what toadd and exits 2; malformed config is a hard error (inline
api_key, unknownprovider, bad
rendering_speed). A typo must never silently downgrade the anchor.The key never enters a file —
api_key_envnames 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 themutually-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 thecontent 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.mdrow, and anadvisory 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.