Extend AI image tooling with variants; drop dev asset folders from git

asset_pipeline generate gains --variants N (saved as <stem>-1..N.png). .gitignore now excludes assets/models/concepts, assets/models/npc, assets/textures/npc/ai, and raw assets/textures/ai outputs; only game_ready and the game-facing NPC maps/models stay tracked.
This commit is contained in:
zwitschi committed 2026-11-01 12:08:34 +01:00
1 parent b24d0aeb61
commit 4943dbff16
32 files changed
+46 -16

No files matched your search

+8 -1
View File
@@ -43,4 +43,11 @@ user://
.gitea/logs/ .gitea/logs/
build/ build/
.env .env
# AI / asset-development outputs (regenerated by the tools; not shipped in the game)
assets/models/concepts/
assets/models/npc/
assets/textures/npc/ai/
assets/textures/ai/*
!assets/textures/ai/game_ready/
+10
View File
@@ -46,6 +46,16 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
Next: Phase 3 (`NpcAvatar._load_model` + slot tinting) consumes Next: Phase 3 (`NpcAvatar._load_model` + slot tinting) consumes
`palette.json` and the GLB; Phase 2's capture/judge item is blocked on that. `palette.json` and the GLB; Phase 2's capture/judge item is blocked on that.
- **Git hygiene + tooling (2026-11-01):** dev asset folders are now gitignored
and untracked: `assets/models/concepts/`, `assets/models/npc/` (Blender
sources), `assets/textures/npc/ai/` (raw AI fabrics), and the raw
`assets/textures/ai/*` outputs (only `assets/textures/ai/game_ready/` stays).
Kept in the repo: `assets/textures/npc/*_{albedo,normal,orm}.png` +
`palette.json`, `assets/models/generated/*` (props + npc_base.glb), and the
authored `assets/textures/*` set. `tools/asset_pipeline.py generate` gained
`--variants N` — generates N alternatives saved as `<stem>-1..N.png` (N=1
keeps the unsuffixed path). Python tooling tests still pass (exit 0).
- Repo: `/run/media/system/DATA/Documents/06-games/security-game` (also reachable at - Repo: `/run/media/system/DATA/Documents/06-games/security-game` (also reachable at
`/var/home/zwitschi/Documents/06-games/security-game`). Godot: `~/.local/bin/godot` (4.3.stable). `/var/home/zwitschi/Documents/06-games/security-game`). Godot: `~/.local/bin/godot` (4.3.stable).
Tests: `godot --headless --path . --script res://src/test/test_runner.gd` → **177/177**. Tests: `godot --headless --path . --script res://src/test/test_runner.gd` → **177/177**.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 856 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 929 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 671 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 578 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 568 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 675 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 801 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 465 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 463 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 597 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 KiB

+28 -15
View File
@@ -258,6 +258,13 @@ def image_paths(values):
return sorted(set(found)) return sorted(set(found))
def _variant_output(output: Path, index: int, count: int) -> Path:
"""Suffix the output path for alternative images (N > 1 only)."""
if count <= 1:
return output
return output.with_name(f"{output.stem}-{index}{output.suffix}")
def _generate(args): def _generate(args):
profile = PROFILES[args.role] profile = PROFILES[args.role]
if (args.width is None) != (args.height is None): if (args.width is None) != (args.height is None):
@@ -267,21 +274,24 @@ def _generate(args):
transparent = args.transparent if args.transparent is not None else profile.get( transparent = args.transparent if args.transparent is not None else profile.get(
"alpha") is True "alpha") is True
prompt = f"{args.prompt.strip()}. {ROLE_GUIDANCE[args.role]}." prompt = f"{args.prompt.strip()}. {ROLE_GUIDANCE[args.role]}."
image = generate_textures.generate_image( count = max(1, int(getattr(args, "variants", 1) or 1))
prompt, args.model, args.request_size, transparent, output = resolve_path(args.output)
aspect_ratio=API_ASPECTS[args.role]) for index in range(1, count + 1):
foreground = getattr(args, "foreground", None) image = generate_textures.generate_image(
if foreground: prompt, args.model, args.request_size, transparent,
image = colorize_alpha(image, foreground) aspect_ratio=API_ASPECTS[args.role])
if args.role == "icon" and has_alpha(image): foreground = getattr(args, "foreground", None)
image = resize_image_from_image( if foreground:
image, width, height, "contain", (0, 0, 0, 0)) image = colorize_alpha(image, foreground)
else: if args.role == "icon" and has_alpha(image):
image = ImageOps.fit(image, (width, height), image = resize_image_from_image(
method=Image.Resampling.LANCZOS) image, width, height, "contain", (0, 0, 0, 0))
if args.role == "tile": else:
image = make_seamless(image) image = ImageOps.fit(image, (width, height),
save_image(image, resolve_path(args.output)) method=Image.Resampling.LANCZOS)
if args.role == "tile":
image = make_seamless(image)
save_image(image, _variant_output(output, index, count))
def resize_image_from_image(image, width, height, fit, background): def resize_image_from_image(image, width, height, fit, background):
@@ -365,6 +375,9 @@ def build_parser():
default=generate_textures.GEN_SIZE) default=generate_textures.GEN_SIZE)
generate.add_argument("--width", type=int) generate.add_argument("--width", type=int)
generate.add_argument("--height", type=int) generate.add_argument("--height", type=int)
generate.add_argument("--variants", type=int, default=1,
help="generate N alternative images; saved as "
"<stem>-1.png ... <stem>-N.png when N > 1")
generate.add_argument( generate.add_argument(
"--foreground", type=parse_color, "--foreground", type=parse_color,
help="replace visible RGB with this color while preserving alpha") help="replace visible RGB with this color while preserving alpha")