Make validation aware of image model behavior
Browse files
README.md
CHANGED
|
@@ -31,6 +31,8 @@ If a requested role has no deterministic hook, assets are still generated for re
|
|
| 31 |
|
| 32 |
Generated sprites receive automated dimension, alpha-channel, and corner-transparency checks. These checks do not establish semantic or artistic correctness, so every role also has a manual approval control and per-role regeneration.
|
| 33 |
|
|
|
|
|
|
|
| 34 |
The app combines the game type, perspective, theme, and pasted code into explicit per-role image prompts, then generates PNG assets and embeds contract-compatible assets as base64 data URIs.
|
| 35 |
|
| 36 |
If the Space has an `HF_TOKEN` secret, it first uses the text model in `HF_PROMPT_MODEL` to interpret the code, roles, and theme into image prompts. The default prompt model is `Qwen/Qwen2.5-Coder-7B-Instruct`.
|
|
|
|
| 31 |
|
| 32 |
Generated sprites receive automated dimension, alpha-channel, and corner-transparency checks. These checks do not establish semantic or artistic correctness, so every role also has a manual approval control and per-role regeneration.
|
| 33 |
|
| 34 |
+
The UI reports the configured prompt and image pipeline and labels every role with its actual source. Procedural generators provide predictable structure and real transparency but have limited open-ended semantic fidelity. Diffusion models provide more visual variety but are more likely to miss perspective, subject, background, or edge constraints; sprite outputs therefore receive alpha post-processing and still require visual approval. Background and sprite diffusion runs use different negative prompts so valid characters and vehicles are not accidentally excluded from sprite generation.
|
| 35 |
+
|
| 36 |
The app combines the game type, perspective, theme, and pasted code into explicit per-role image prompts, then generates PNG assets and embeds contract-compatible assets as base64 data URIs.
|
| 37 |
|
| 38 |
If the Space has an `HF_TOKEN` secret, it first uses the text model in `HF_PROMPT_MODEL` to interpret the code, roles, and theme into image prompts. The default prompt model is `Qwen/Qwen2.5-Coder-7B-Instruct`.
|
app.py
CHANGED
|
@@ -1046,7 +1046,15 @@ def local_asset_png(spec: AssetSpec, index: int, run_id: int) -> bytes:
|
|
| 1046 |
content = draw_background(spec, rng)
|
| 1047 |
else:
|
| 1048 |
content = draw_sprite(spec, rng)
|
| 1049 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1050 |
|
| 1051 |
|
| 1052 |
def placeholder_png_bytes(role: str, width: int, height: int) -> bytes:
|
|
@@ -1070,6 +1078,17 @@ def diffusion_dimensions(spec: AssetSpec) -> tuple[int, int]:
|
|
| 1070 |
return 256, 256
|
| 1071 |
|
| 1072 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1073 |
def polish_diffusion_asset(image: Image.Image, spec: AssetSpec) -> bytes:
|
| 1074 |
image = image.convert("RGBA")
|
| 1075 |
if not is_background_spec(spec):
|
|
@@ -1123,15 +1142,9 @@ def free_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes
|
|
| 1123 |
width, height = diffusion_dimensions(spec)
|
| 1124 |
seed = abs(hash(f"{spec.role}|{spec.prompt}|{index}|{run_id}")) % 2147483647
|
| 1125 |
generator = torch.Generator(device="cpu").manual_seed(seed)
|
| 1126 |
-
negative_prompt = (
|
| 1127 |
-
"texture map, material texture, seamless texture, tiled pattern, uv map, uv unwrap, "
|
| 1128 |
-
"skin texture, 3d model texture, normal map, roughness map, diffuse map, sprite sheet, "
|
| 1129 |
-
"atlas, multiple objects, cropped subject, close-up surface, fabric swatch, text, watermark, "
|
| 1130 |
-
"person, character, player, hero, creature, monster, vehicle, mascot, foreground subject"
|
| 1131 |
-
)
|
| 1132 |
image = FREE_DIFFUSION_PIPE(
|
| 1133 |
spec.prompt,
|
| 1134 |
-
negative_prompt=
|
| 1135 |
width=width,
|
| 1136 |
height=height,
|
| 1137 |
num_inference_steps=FREE_IMAGE_STEPS,
|
|
@@ -1163,9 +1176,7 @@ def hf_image_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes | None
|
|
| 1163 |
guidance_scale=0.0,
|
| 1164 |
seed=abs(hash(f"{spec.role}|{spec.prompt}|{index}|{run_id}")) % 2147483647,
|
| 1165 |
)
|
| 1166 |
-
|
| 1167 |
-
image.save(out, format="PNG")
|
| 1168 |
-
return image_to_png_bytes(out.getvalue(), spec.width, spec.height), None
|
| 1169 |
except Exception as exc:
|
| 1170 |
return None, short_error(exc)
|
| 1171 |
|
|
@@ -1348,7 +1359,31 @@ def build_prompt_preview(specs: list[AssetSpec]) -> str:
|
|
| 1348 |
|
| 1349 |
|
| 1350 |
def build_model_report(rows: list[tuple[str, str, str]]) -> str:
|
| 1351 |
-
return "\n".join(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1352 |
|
| 1353 |
|
| 1354 |
def summarize_model_sources(rows: list[tuple[str, str, str]]) -> str:
|
|
@@ -1553,6 +1588,7 @@ with gr.Blocks(title="Image Generator for HTML Games") as demo:
|
|
| 1553 |
"`GAME_ASSETS.<role>` hooks or exact role filenames such as `sprite_player.png`. "
|
| 1554 |
"Primitive canvas geometry is preserved and never silently guessed."
|
| 1555 |
)
|
|
|
|
| 1556 |
|
| 1557 |
generation_state = gr.State({})
|
| 1558 |
|
|
|
|
| 1046 |
content = draw_background(spec, rng)
|
| 1047 |
else:
|
| 1048 |
content = draw_sprite(spec, rng)
|
| 1049 |
+
content = apply_style_finish(content, spec, rng)
|
| 1050 |
+
if not is_background_spec(spec):
|
| 1051 |
+
return content
|
| 1052 |
+
|
| 1053 |
+
image = Image.open(io.BytesIO(content)).convert("RGBA")
|
| 1054 |
+
image.putalpha(Image.new("L", image.size, 255))
|
| 1055 |
+
out = io.BytesIO()
|
| 1056 |
+
image.save(out, format="PNG")
|
| 1057 |
+
return out.getvalue()
|
| 1058 |
|
| 1059 |
|
| 1060 |
def placeholder_png_bytes(role: str, width: int, height: int) -> bytes:
|
|
|
|
| 1078 |
return 256, 256
|
| 1079 |
|
| 1080 |
|
| 1081 |
+
def diffusion_negative_prompt(spec: AssetSpec) -> str:
|
| 1082 |
+
common = (
|
| 1083 |
+
"texture map, material texture, seamless texture, tiled pattern, uv map, uv unwrap, "
|
| 1084 |
+
"3d model texture, normal map, roughness map, diffuse map, sprite sheet, atlas, "
|
| 1085 |
+
"multiple objects, cropped subject, close-up surface, fabric swatch, text, watermark"
|
| 1086 |
+
)
|
| 1087 |
+
if is_background_spec(spec):
|
| 1088 |
+
return common + ", person, character, player, hero, creature, monster, vehicle, mascot, foreground subject"
|
| 1089 |
+
return common + ", landscape, environment scene, room, horizon, opaque background, drop shadow"
|
| 1090 |
+
|
| 1091 |
+
|
| 1092 |
def polish_diffusion_asset(image: Image.Image, spec: AssetSpec) -> bytes:
|
| 1093 |
image = image.convert("RGBA")
|
| 1094 |
if not is_background_spec(spec):
|
|
|
|
| 1142 |
width, height = diffusion_dimensions(spec)
|
| 1143 |
seed = abs(hash(f"{spec.role}|{spec.prompt}|{index}|{run_id}")) % 2147483647
|
| 1144 |
generator = torch.Generator(device="cpu").manual_seed(seed)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1145 |
image = FREE_DIFFUSION_PIPE(
|
| 1146 |
spec.prompt,
|
| 1147 |
+
negative_prompt=diffusion_negative_prompt(spec),
|
| 1148 |
width=width,
|
| 1149 |
height=height,
|
| 1150 |
num_inference_steps=FREE_IMAGE_STEPS,
|
|
|
|
| 1176 |
guidance_scale=0.0,
|
| 1177 |
seed=abs(hash(f"{spec.role}|{spec.prompt}|{index}|{run_id}")) % 2147483647,
|
| 1178 |
)
|
| 1179 |
+
return polish_diffusion_asset(image, spec), None
|
|
|
|
|
|
|
| 1180 |
except Exception as exc:
|
| 1181 |
return None, short_error(exc)
|
| 1182 |
|
|
|
|
| 1359 |
|
| 1360 |
|
| 1361 |
def build_model_report(rows: list[tuple[str, str, str]]) -> str:
|
| 1362 |
+
return "\n".join(
|
| 1363 |
+
f"{role}: prompt={prompt_model}; image={image_model}; {model_source_note(image_model)}"
|
| 1364 |
+
for role, prompt_model, image_model in rows
|
| 1365 |
+
)
|
| 1366 |
+
|
| 1367 |
+
|
| 1368 |
+
def model_source_note(image_model: str) -> str:
|
| 1369 |
+
lowered = image_model.lower()
|
| 1370 |
+
if "shape-aware" in lowered or "scene-safe" in lowered or "procedural" in lowered:
|
| 1371 |
+
return "procedural/heuristic output鈥攑redictable structure, limited open-ended semantic fidelity"
|
| 1372 |
+
return "diffusion output鈥攈igher visual variety; verify subject, perspective, alpha edges, and prompt compliance"
|
| 1373 |
+
|
| 1374 |
+
|
| 1375 |
+
def model_configuration_summary() -> str:
|
| 1376 |
+
prompt_source = HF_PROMPT_MODEL if USE_HF_PROMPT_PROVIDER else "local prompt interpreter"
|
| 1377 |
+
sprite_source = FREE_IMAGE_MODEL if USE_DIFFUSION_FOR_SPRITES else "shape-aware procedural generator"
|
| 1378 |
+
background_source = FREE_IMAGE_MODEL if USE_DIFFUSION_FOR_BACKGROUNDS else "scene-safe procedural generator"
|
| 1379 |
+
remote_fallback = HF_IMAGE_MODEL if USE_HF_IMAGE_PROVIDER else "disabled"
|
| 1380 |
+
return (
|
| 1381 |
+
"**Configured model pipeline:** "
|
| 1382 |
+
f"prompts: `{prompt_source}` 路 sprites: `{sprite_source}` 路 backgrounds: `{background_source}` 路 "
|
| 1383 |
+
f"remote image fallback: `{remote_fallback}`. "
|
| 1384 |
+
"Procedural output is structurally predictable but semantically limited; diffusion output is more varied "
|
| 1385 |
+
"and therefore requires stricter visual review."
|
| 1386 |
+
)
|
| 1387 |
|
| 1388 |
|
| 1389 |
def summarize_model_sources(rows: list[tuple[str, str, str]]) -> str:
|
|
|
|
| 1588 |
"`GAME_ASSETS.<role>` hooks or exact role filenames such as `sprite_player.png`. "
|
| 1589 |
"Primitive canvas geometry is preserved and never silently guessed."
|
| 1590 |
)
|
| 1591 |
+
gr.Markdown(model_configuration_summary())
|
| 1592 |
|
| 1593 |
generation_state = gr.State({})
|
| 1594 |
|