@leejungkiin/awkit 1.7.0 → 1.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/awk.js +576 -84
- package/core/CLAUDE.md +1 -1
- package/core/GEMINI.md +148 -167
- package/core/GEMINI.md.bak +149 -116
- package/core/skill-runtime-manifest.json +3 -0
- package/docs/Claude Fable 5.md +3826 -0
- package/docs/android_kotlin_system_instruction.md +210 -0
- package/docs/brainstorm_ponytail_integration.md +146 -0
- package/docs/brainstorm_smart_setup.md +113 -0
- package/docs/deep-research-report (1).md +293 -0
- package/docs/history/GEMINI.v1.md +135 -0
- package/docs/history/brainstorm_antigravity_unified_architecture.v1.md +105 -0
- package/docs/history/implementation_plan.v1.md +58 -0
- package/package.json +4 -1
- package/scripts/artifact-storage.js +130 -0
- package/scripts/automation-gate.js +40 -7
- package/scripts/claude-plan.js +76 -0
- package/scripts/dependency-manager.js +210 -0
- package/scripts/exec-rtk.js +11 -5
- package/scripts/i18n-helper.js +381 -0
- package/scripts/multi-model-pipeline.js +144 -0
- package/skill-packs/mobile-ios/pack.json +4 -2
- package/skill-packs/reverse-engineering/pack.json +1 -0
- package/skills/CATALOG.md +20 -0
- package/skills/GEMINI.md +9 -1
- package/skills/TRIGGER_INDEX.md +10 -0
- package/skills/ai-music/SKILL.md +275 -0
- package/skills/android-re-analyzer/SKILL.md +238 -0
- package/skills/android-re-analyzer/references/api-extraction-patterns.md +119 -0
- package/skills/android-re-analyzer/references/call-flow-analysis.md +176 -0
- package/skills/android-re-analyzer/references/fernflower-usage.md +115 -0
- package/skills/android-re-analyzer/references/jadx-usage.md +116 -0
- package/skills/android-re-analyzer/references/setup-guide.md +221 -0
- package/skills/android-re-analyzer/scripts/check-deps.sh +129 -0
- package/skills/android-re-analyzer/scripts/decompile.sh +375 -0
- package/skills/android-re-analyzer/scripts/find-api-calls.sh +118 -0
- package/skills/android-re-analyzer/scripts/install-dep.sh +448 -0
- package/skills/animal-island-ui-style/SKILL.md +1450 -0
- package/skills/app-store-review-agent/SKILL.md +164 -0
- package/skills/app-store-review-agent/references/guidelines/README.md +154 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/ai_apps.md +37 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/all_apps.md +50 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/crypto_finance.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/games.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/health_fitness.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/kids.md +27 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/macos.md +38 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/social_ugc.md +32 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/subscription_iap.md +34 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/vpn.md +18 -0
- package/skills/app-store-review-agent/references/rules/design/minimum_functionality.md +96 -0
- package/skills/app-store-review-agent/references/rules/design/sign_in_with_apple.md +54 -0
- package/skills/app-store-review-agent/references/rules/entitlements/unused_entitlements.md +83 -0
- package/skills/app-store-review-agent/references/rules/metadata/accurate_metadata.md +54 -0
- package/skills/app-store-review-agent/references/rules/metadata/apple_trademark.md +99 -0
- package/skills/app-store-review-agent/references/rules/metadata/china_storefront.md +72 -0
- package/skills/app-store-review-agent/references/rules/metadata/competitor_terms.md +56 -0
- package/skills/app-store-review-agent/references/rules/metadata/subscription_metadata.md +81 -0
- package/skills/app-store-review-agent/references/rules/privacy/privacy_manifest.md +84 -0
- package/skills/app-store-review-agent/references/rules/privacy/unnecessary_data.md +60 -0
- package/skills/app-store-review-agent/references/rules/subscription/misleading_pricing.md +63 -0
- package/skills/app-store-review-agent/references/rules/subscription/missing_tos_pp.md +54 -0
- package/skills/awf-ponytail/SKILL.md +91 -0
- package/skills/awf-ponytail-review/SKILL.md +67 -0
- package/skills/awf-session-restore/SKILL.md +3 -3
- package/skills/brainstorm-agent/SKILL.md +11 -2
- package/skills/brainstorm-agent/templates/brief-template.md +8 -0
- package/skills/claude-planner/SKILL.md +47 -0
- package/skills/code-review/SKILL.md +87 -0
- package/skills/expo-game-development/SKILL.md +163 -0
- package/skills/flutter/LICENSE.txt +202 -0
- package/skills/flutter/SKILL.md +127 -0
- package/skills/flutter-project-creater/LICENSE.txt +202 -0
- package/skills/flutter-project-creater/SKILL.md +106 -0
- package/skills/game-developer/SKILL.md +163 -0
- package/skills/game-developer/references/ecs-patterns.md +501 -0
- package/skills/game-developer/references/multiplayer-networking.md +475 -0
- package/skills/game-developer/references/performance-optimization.md +422 -0
- package/skills/game-developer/references/unity-patterns.md +271 -0
- package/skills/game-developer/references/unreal-cpp.md +352 -0
- package/skills/generate-gui-assets/SKILL.md +305 -0
- package/skills/generate-gui-assets/agents/openai.yaml +4 -0
- package/skills/generate-gui-assets/references/catalog-schema.md +58 -0
- package/skills/generate-gui-assets/references/extraction-techniques.md +21 -0
- package/skills/generate-gui-assets/references/prompt-patterns.md +58 -0
- package/skills/generate-gui-assets/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
- package/skills/generate-gui-assets/scripts/build_gui_contact_sheet.py +51 -0
- package/skills/generate-gui-assets/scripts/clean_chroma_edges.py +262 -0
- package/skills/generate-gui-assets/scripts/copy_approved_icons.py +64 -0
- package/skills/generate-gui-assets/scripts/prepare_gui_asset_run.py +91 -0
- package/skills/generate-gui-assets/scripts/suggest_grid_options.py +63 -0
- package/skills/generate-gui-assets/scripts/validate_gui_catalog.py +50 -0
- package/skills/godot-game-development/SKILL.md +142 -0
- package/skills/hatch-pet/LICENSE.txt +201 -0
- package/skills/hatch-pet/SKILL.md +420 -0
- package/skills/hatch-pet/agents/openai.yaml +4 -0
- package/skills/hatch-pet/references/animation-rows.md +29 -0
- package/skills/hatch-pet/references/codex-pet-contract.md +35 -0
- package/skills/hatch-pet/references/qa-rubric.md +60 -0
- package/skills/hatch-pet/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
- package/skills/hatch-pet/scripts/clean_chroma_edges.py +262 -0
- package/skills/hatch-pet/scripts/compose_atlas.py +150 -0
- package/skills/hatch-pet/scripts/derive_running_left_from_running_right.py +143 -0
- package/skills/hatch-pet/scripts/extract_strip_frames.py +323 -0
- package/skills/hatch-pet/scripts/finalize_pet_run.py +382 -0
- package/skills/hatch-pet/scripts/generate_pet_images.py +287 -0
- package/skills/hatch-pet/scripts/inspect_frames.py +246 -0
- package/skills/hatch-pet/scripts/make_contact_sheet.py +96 -0
- package/skills/hatch-pet/scripts/package_custom_pet.py +108 -0
- package/skills/hatch-pet/scripts/pet_job_status.py +117 -0
- package/skills/hatch-pet/scripts/prepare_pet_run.py +673 -0
- package/skills/hatch-pet/scripts/queue_pet_repairs.py +172 -0
- package/skills/hatch-pet/scripts/record_imagegen_result.py +250 -0
- package/skills/hatch-pet/scripts/render_animation_videos.py +134 -0
- package/skills/hatch-pet/scripts/render_animation_videos.sh +5 -0
- package/skills/hatch-pet/scripts/validate_atlas.py +139 -0
- package/skills/i18n-orchestrator/SKILL.md +37 -0
- package/skills/ios-simulator-skill/SKILL.md +390 -0
- package/skills/ios-simulator-skill/scripts/accessibility_audit.py +300 -0
- package/skills/ios-simulator-skill/scripts/app_launcher.py +326 -0
- package/skills/ios-simulator-skill/scripts/app_state_capture.py +400 -0
- package/skills/ios-simulator-skill/scripts/appearance.py +385 -0
- package/skills/ios-simulator-skill/scripts/build_and_test.py +348 -0
- package/skills/ios-simulator-skill/scripts/clipboard.py +103 -0
- package/skills/ios-simulator-skill/scripts/common/__init__.py +61 -0
- package/skills/ios-simulator-skill/scripts/common/cache_utils.py +289 -0
- package/skills/ios-simulator-skill/scripts/common/device_utils.py +462 -0
- package/skills/ios-simulator-skill/scripts/common/env_config.py +35 -0
- package/skills/ios-simulator-skill/scripts/common/hang_pipeline.py +862 -0
- package/skills/ios-simulator-skill/scripts/common/hang_sessions.py +490 -0
- package/skills/ios-simulator-skill/scripts/common/idb_utils.py +180 -0
- package/skills/ios-simulator-skill/scripts/common/screenshot_utils.py +338 -0
- package/skills/ios-simulator-skill/scripts/container.py +668 -0
- package/skills/ios-simulator-skill/scripts/gesture.py +394 -0
- package/skills/ios-simulator-skill/scripts/hang_watcher.py +1533 -0
- package/skills/ios-simulator-skill/scripts/keyboard.py +391 -0
- package/skills/ios-simulator-skill/scripts/localization_audit.py +483 -0
- package/skills/ios-simulator-skill/scripts/location.py +467 -0
- package/skills/ios-simulator-skill/scripts/log_monitor.py +493 -0
- package/skills/ios-simulator-skill/scripts/model_inspector.py +645 -0
- package/skills/ios-simulator-skill/scripts/navigator.py +461 -0
- package/skills/ios-simulator-skill/scripts/privacy_manager.py +310 -0
- package/skills/ios-simulator-skill/scripts/push_notification.py +240 -0
- package/skills/ios-simulator-skill/scripts/screen_mapper.py +296 -0
- package/skills/ios-simulator-skill/scripts/sim_health_check.sh +245 -0
- package/skills/ios-simulator-skill/scripts/sim_list.py +299 -0
- package/skills/ios-simulator-skill/scripts/simctl_boot.py +312 -0
- package/skills/ios-simulator-skill/scripts/simctl_create.py +316 -0
- package/skills/ios-simulator-skill/scripts/simctl_delete.py +357 -0
- package/skills/ios-simulator-skill/scripts/simctl_erase.py +351 -0
- package/skills/ios-simulator-skill/scripts/simctl_shutdown.py +290 -0
- package/skills/ios-simulator-skill/scripts/simulator_selector.py +375 -0
- package/skills/ios-simulator-skill/scripts/status_bar.py +250 -0
- package/skills/ios-simulator-skill/scripts/test_recorder.py +323 -0
- package/skills/ios-simulator-skill/scripts/visual_diff.py +235 -0
- package/skills/ios-simulator-skill/scripts/xcode/__init__.py +13 -0
- package/skills/ios-simulator-skill/scripts/xcode/builder.py +397 -0
- package/skills/ios-simulator-skill/scripts/xcode/cache.py +204 -0
- package/skills/ios-simulator-skill/scripts/xcode/config.py +178 -0
- package/skills/ios-simulator-skill/scripts/xcode/reporter.py +343 -0
- package/skills/ios-simulator-skill/scripts/xcode/xcresult.py +451 -0
- package/skills/ios-visual-qa-strategist/SKILL.md +111 -0
- package/skills/ios-visual-qa-strategist/agents/openai.yaml +4 -0
- package/skills/ios-visual-qa-strategist/references/ios-tool-selection.md +61 -0
- package/skills/ios-visual-qa-strategist/references/minimal-capture-policy.md +56 -0
- package/skills/ios-visual-qa-strategist/references/visual-reasoning-heuristics.md +53 -0
- package/skills/orchestrator/SKILL.md +0 -20
- package/skills/persistent-storage/SKILL.md +55 -0
- package/skills/short-maker/SKILL.md +23 -0
- package/skills/short-maker/scripts/effects.js +56 -0
- package/skills/short-maker/scripts/shortmaker-bridge.js +332 -0
- package/skills/short-maker/scripts/videomix.js +601 -0
- package/skills/short-maker/templates/hyperframes/cinematic-character.template.html +172 -0
- package/skills/short-maker/templates/hyperframes/index.template.html +194 -0
- package/skills/smali-to-kotlin/SKILL.md +128 -0
- package/skills/smali-to-kotlin/examples/getting-started/tech-stack.md +58 -0
- package/skills/smali-to-kotlin/examples/pipeline/data-ui-parity.md +118 -0
- package/skills/smali-to-kotlin/examples/pipeline/scanner-and-bootstrap.md +106 -0
- package/skills/smali-to-kotlin/library-patterns.md +189 -0
- package/skills/smali-to-kotlin/phase-0-discovery.md +128 -0
- package/skills/smali-to-kotlin/phase-1-architecture.md +166 -0
- package/skills/smali-to-kotlin/phase-2-blueprint-ui.md +347 -0
- package/skills/smali-to-kotlin/phase-2-blueprint.md +228 -0
- package/skills/smali-to-kotlin/phase-3-build.md +248 -0
- package/skills/smali-to-kotlin/phase-3-logic-build.md +268 -0
- package/skills/smali-to-kotlin/smali-reading-guide.md +310 -0
- package/skills/smali-to-kotlin/templates/app-map.md +101 -0
- package/skills/smali-to-kotlin/templates/architecture.md +142 -0
- package/skills/smali-to-kotlin/templates/blueprint.md +145 -0
- package/skills/spec-gate/SKILL.md +6 -2
- package/skills/symphony-enforcer/SKILL.md +8 -0
- package/skills/symphony-enforcer/examples/mindful-stop.md +2 -0
- package/skills/symphony-enforcer/examples/three-phase.md +16 -0
- package/skills/symphony-enforcer/examples/trigger-points.md +7 -1
- package/skills/unity-game-development/SKILL.md +231 -0
- package/skills/verification-gate/SKILL.md +4 -2
- package/skills/video-edit/SKILL.md +36 -0
- package/skills/video-edit/scripts/video_edit.py +324 -0
- package/templates/setup-mapping.json +48 -0
- package/templates/specs/design-template.md +161 -71
- package/templates/specs/requirements-template.md +65 -133
- package/templates/specs/task-spec-template.xml +3 -0
- package/workflows/_uncategorized/critic.md +40 -0
- package/workflows/_uncategorized/git-rebase-flow.md +81 -0
- package/workflows/_uncategorized/image-gen.md +118 -0
- package/workflows/_uncategorized/multi-model-pipeline.md +60 -0
- package/workflows/_uncategorized/pixel-gen.md +86 -0
- package/workflows/_uncategorized/pixel-setup.md +90 -0
- package/workflows/_uncategorized/ponytail-review.md +59 -0
- package/workflows/_uncategorized/reverse-android-build.md +222 -0
- package/workflows/_uncategorized/reverse-android-design.md +139 -0
- package/workflows/_uncategorized/reverse-android-discover.md +150 -0
- package/workflows/_uncategorized/reverse-android-scan.md +158 -0
- package/workflows/_uncategorized/reverse-android.md +143 -0
- package/workflows/_uncategorized/reverse-ios-build.md +240 -0
- package/workflows/_uncategorized/reverse-ios-design.md +112 -0
- package/workflows/_uncategorized/reverse-ios-discover.md +120 -0
- package/workflows/_uncategorized/reverse-ios-scan.md +155 -0
- package/workflows/_uncategorized/reverse-ios.md +152 -0
- package/workflows/_uncategorized/safety-router.md +34 -0
- package/workflows/_uncategorized/teach.md +89 -0
- package/workflows/_uncategorized/verify-ui.md +53 -0
- package/workflows/_uncategorized/visualize-screenshots.md +34 -0
- package/workflows/ads/ads-analyst.md +201 -0
- package/workflows/ads/ads-audit.md +106 -0
- package/workflows/ads/ads-optimize.md +97 -0
- package/workflows/ads/ads-targeting.md +241 -0
- package/workflows/ads/adsExpert.md +160 -0
- package/workflows/ads/smali-ads-config.md +400 -0
- package/workflows/ads/smali-ads-flow.md +331 -0
- package/workflows/ads/smali-ads-interstitial.md +377 -0
- package/workflows/ads/smali-ads-native.md +382 -0
- package/workflows/context/teach.md +89 -0
- package/workflows/gitnexus.md +8 -8
- package/workflows/lifecycle/brainstorm.md +43 -0
- package/workflows/lifecycle/code.md +5 -0
- package/workflows/lifecycle/init.md +23 -5
- package/workflows/lifecycle/multi-model-pipeline.md +60 -0
- package/workflows/quality/ponytail-review.md +59 -0
- package/workflows/roles/critic.md +40 -0
- package/workflows/roles/safety-router.md +34 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# GUI Asset Catalog Schema
|
|
2
|
+
|
|
3
|
+
## catalog.json
|
|
4
|
+
|
|
5
|
+
Use one catalog per generation run.
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"runId": "gui-v1",
|
|
10
|
+
"createdAt": "2026-05-23T00:00:00Z",
|
|
11
|
+
"styleNotes": "cozy mobile wellness garden icons",
|
|
12
|
+
"chromaKey": "#FF00FF",
|
|
13
|
+
"packs": [
|
|
14
|
+
{
|
|
15
|
+
"id": "ui_nav_core",
|
|
16
|
+
"grid": "4x3",
|
|
17
|
+
"icons": ["home", "garden"],
|
|
18
|
+
"promptFile": "prompts/ui_nav_core.txt",
|
|
19
|
+
"rawFile": "raw/ui_nav_core.png",
|
|
20
|
+
"source": {
|
|
21
|
+
"generator": "imagegen",
|
|
22
|
+
"path": "/absolute/path/to/generated/ig_*.png",
|
|
23
|
+
"recordedAt": "2026-05-23T00:00:00Z"
|
|
24
|
+
},
|
|
25
|
+
"qa": {
|
|
26
|
+
"state": "pending",
|
|
27
|
+
"notes": ""
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## icons_manifest.json
|
|
35
|
+
|
|
36
|
+
Use after extraction/copy approval.
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"runId": "gui-v1",
|
|
41
|
+
"sourceDir": "assets/ui/generated/gui-v1/manual_crop/extracted_icons_mask",
|
|
42
|
+
"technique": "copy-approved-icons-as-is",
|
|
43
|
+
"icons": [
|
|
44
|
+
{
|
|
45
|
+
"name": "home",
|
|
46
|
+
"file": "icons/home.png",
|
|
47
|
+
"source": "source-file.png"
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Rules
|
|
54
|
+
|
|
55
|
+
- Keep paths relative to the run directory when possible.
|
|
56
|
+
- Store absolute generator source paths only for provenance.
|
|
57
|
+
- Use `qa.state` values: `pending`, `approved`, `regenerate`, or `rejected`.
|
|
58
|
+
- Do not mark a pack approved until a contact sheet or direct visual inspection exists.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Extraction Techniques
|
|
2
|
+
|
|
3
|
+
## Approved Extraction Rule
|
|
4
|
+
|
|
5
|
+
When a source package says an extraction method is approved, do not recut, resegment, resize, or visually modify the source icons.
|
|
6
|
+
|
|
7
|
+
Allowed deterministic actions:
|
|
8
|
+
|
|
9
|
+
- Copy icons as-is into a flat `icons/` folder.
|
|
10
|
+
- Rename by semantic mapping.
|
|
11
|
+
- Add duplicate-safe suffixes such as `_2` and `_3`.
|
|
12
|
+
- Record full provenance in `icons_manifest.json`.
|
|
13
|
+
|
|
14
|
+
## When To Stop
|
|
15
|
+
|
|
16
|
+
Stop and ask for direction if:
|
|
17
|
+
|
|
18
|
+
- The source icon count does not match the semantic mapping.
|
|
19
|
+
- The source package includes mixed extraction methods and no approved technique is identified.
|
|
20
|
+
- The user asks for visual cleanup that would alter approved pixels.
|
|
21
|
+
- File names cannot be mapped deterministically to semantic names.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# GUI Asset Prompt Patterns
|
|
2
|
+
|
|
3
|
+
## Master Atlas
|
|
4
|
+
|
|
5
|
+
Use for early style discovery across many related packs.
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
Create one high-resolution GUI asset master atlas on a flat #FF00FF chroma-key background.
|
|
9
|
+
Style: <product/game style>, consistent palette, crisp clean edges, readable silhouettes.
|
|
10
|
+
Groups: <number> labeled only in this prompt, not in the image.
|
|
11
|
+
Each group uses its own grid: <pack name> <grid> with icons <names in row order>.
|
|
12
|
+
Layout: equal spacing, centered icons, 10% safe margin, no object touches cell borders.
|
|
13
|
+
Quality rules: no text, no labels, no guide marks, no shadows, no glow, no scenery, no duplicate generic placeholders, no clipping, no overlap.
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Bulk Canvas Atlas
|
|
17
|
+
|
|
18
|
+
Use after the user confirms preview style and chooses a uniform slicing grid. This is the default bulk strategy for reducing generation count.
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
Create one GUI icon atlas on a flat #FF00FF chroma-key background.
|
|
22
|
+
Style lock: match the approved preview exactly: <frozen style language>.
|
|
23
|
+
Canvas grid: <columns>x<rows> total icon cells, equal cell size, consistent spacing, centered icons, 10% safe margin.
|
|
24
|
+
Icons in row order, left to right then top to bottom: <names>.
|
|
25
|
+
Keep related icons near each other when possible, but preserve one icon per grid cell.
|
|
26
|
+
Quality rules: no text, no labels, no guide marks, no shadows, no glow, no scenery, no duplicate generic placeholders, no clipping, no overlap.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Focused Pack
|
|
30
|
+
|
|
31
|
+
Use when one pack is weak but the global style is approved.
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
Create one GUI icon atlas on a flat #FF00FF chroma-key background.
|
|
35
|
+
Style lock: match the approved pack family: <frozen style language>.
|
|
36
|
+
Grid: <columns>x<rows>, equal cells, centered icons, generous padding.
|
|
37
|
+
Icons in row order: <names>.
|
|
38
|
+
Quality rules: no text, no labels, no shadows, no glow, no guides, no duplicate placeholders, no clipping, no overlap.
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Game HUD Pack
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
Create a game HUD asset atlas on a flat #FF00FF chroma-key background.
|
|
45
|
+
Style: <game art direction>, readable at small sizes, clean silhouettes.
|
|
46
|
+
Grid: <columns>x<rows>, equal cells.
|
|
47
|
+
Assets in row order: <health, mana, coin, timer, etc.>.
|
|
48
|
+
Do not include numbers, text, labels, panels, background scenery, cast shadows, glow, or particle effects.
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Repair Language
|
|
52
|
+
|
|
53
|
+
Add only the failing constraint:
|
|
54
|
+
|
|
55
|
+
- For clipping: `Every icon must stay 10-12% away from cell borders; no icon touches or crosses an edge.`
|
|
56
|
+
- For semantic drift: `Each icon must clearly represent its exact named concept; do not use generic circles or repeated placeholders.`
|
|
57
|
+
- For style drift: `Match the approved family exactly: same outline weight, palette, shape language, and shading depth.`
|
|
58
|
+
- For extraction issues: `Use a perfectly flat #FF00FF background with no antialiasing into the background color.`
|
|
Binary file
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Build a GUI asset contact sheet from catalog raw files."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import math
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main() -> None:
|
|
15
|
+
parser = argparse.ArgumentParser()
|
|
16
|
+
parser.add_argument("--run-dir", required=True)
|
|
17
|
+
parser.add_argument("--output", default="qa/contact_sheet.png")
|
|
18
|
+
parser.add_argument("--background", default="#FFF9F1")
|
|
19
|
+
args = parser.parse_args()
|
|
20
|
+
|
|
21
|
+
run_dir = Path(args.run_dir)
|
|
22
|
+
catalog = json.loads((run_dir / "catalog.json").read_text(encoding="utf-8"))
|
|
23
|
+
images = [run_dir / pack["rawFile"] for pack in catalog.get("packs", []) if (run_dir / pack["rawFile"]).exists()]
|
|
24
|
+
if not images:
|
|
25
|
+
raise SystemExit("no raw pack images found")
|
|
26
|
+
|
|
27
|
+
output = run_dir / args.output
|
|
28
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
|
|
30
|
+
magick = shutil.which("magick")
|
|
31
|
+
montage = shutil.which("montage")
|
|
32
|
+
if magick:
|
|
33
|
+
tile_cols = min(3, max(1, math.ceil(math.sqrt(len(images)))))
|
|
34
|
+
tile = f"{tile_cols}x"
|
|
35
|
+
subprocess.run(
|
|
36
|
+
[magick, "montage", *map(str, images), "-tile", tile, "-geometry", "+18+18", "-background", args.background, str(output)],
|
|
37
|
+
check=True,
|
|
38
|
+
)
|
|
39
|
+
elif montage:
|
|
40
|
+
subprocess.run(
|
|
41
|
+
[montage, *map(str, images), "-tile", "3x", "-geometry", "+18+18", "-background", args.background, str(output)],
|
|
42
|
+
check=True,
|
|
43
|
+
)
|
|
44
|
+
else:
|
|
45
|
+
raise SystemExit("ImageMagick not found: install magick or montage to build contact sheet")
|
|
46
|
+
|
|
47
|
+
print(output)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
main()
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Clean leftover chroma-key fringe from transparent PNG assets.
|
|
3
|
+
|
|
4
|
+
This is intentionally conservative: it only targets low-alpha pixels close to the
|
|
5
|
+
configured key color, then bleeds nearby foreground RGB into fully transparent
|
|
6
|
+
edge pixels so image scaling cannot sample hidden chroma color.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
from PIL import Image
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
DEFAULT_PATHS = (
|
|
19
|
+
"assets/ui/icons/v2/final",
|
|
20
|
+
"assets/mascot/v2/final",
|
|
21
|
+
"assets/mascot/v3/final",
|
|
22
|
+
"assets/illustrations/v1/final",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def smoothstep(edge0: float, edge1: float, x: np.ndarray) -> np.ndarray:
|
|
27
|
+
t = np.clip((x - edge0) / (edge1 - edge0), 0.0, 1.0)
|
|
28
|
+
return t * t * (3.0 - 2.0 * t)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def shifted(values: np.ndarray, dy: int, dx: int) -> np.ndarray:
|
|
32
|
+
out = np.zeros_like(values)
|
|
33
|
+
src_y0 = max(0, -dy)
|
|
34
|
+
src_y1 = values.shape[0] - max(0, dy)
|
|
35
|
+
src_x0 = max(0, -dx)
|
|
36
|
+
src_x1 = values.shape[1] - max(0, dx)
|
|
37
|
+
dst_y0 = max(0, dy)
|
|
38
|
+
dst_y1 = values.shape[0] - max(0, -dy)
|
|
39
|
+
dst_x0 = max(0, dx)
|
|
40
|
+
dst_x1 = values.shape[1] - max(0, -dx)
|
|
41
|
+
out[dst_y0:dst_y1, dst_x0:dst_x1] = values[src_y0:src_y1, src_x0:src_x1]
|
|
42
|
+
return out
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def bleed_rgb(
|
|
46
|
+
rgb: np.ndarray,
|
|
47
|
+
alpha: np.ndarray,
|
|
48
|
+
chroma_mask: np.ndarray,
|
|
49
|
+
target_mask: np.ndarray,
|
|
50
|
+
iterations: int,
|
|
51
|
+
) -> np.ndarray:
|
|
52
|
+
result = rgb.copy()
|
|
53
|
+
valid = (alpha > 8) & ~chroma_mask
|
|
54
|
+
seed = valid.copy()
|
|
55
|
+
remaining = target_mask.copy()
|
|
56
|
+
|
|
57
|
+
for _ in range(iterations):
|
|
58
|
+
accum = np.zeros_like(result, dtype=np.float32)
|
|
59
|
+
counts = np.zeros(alpha.shape, dtype=np.float32)
|
|
60
|
+
|
|
61
|
+
for dy in (-1, 0, 1):
|
|
62
|
+
for dx in (-1, 0, 1):
|
|
63
|
+
if dy == 0 and dx == 0:
|
|
64
|
+
continue
|
|
65
|
+
neighbor_valid = shifted(seed, dy, dx)
|
|
66
|
+
fill = remaining & neighbor_valid
|
|
67
|
+
if not np.any(fill):
|
|
68
|
+
continue
|
|
69
|
+
neighbor_rgb = shifted(result, dy, dx)
|
|
70
|
+
accum[fill] += neighbor_rgb[fill]
|
|
71
|
+
counts[fill] += 1.0
|
|
72
|
+
|
|
73
|
+
filled = counts > 0
|
|
74
|
+
if not np.any(filled):
|
|
75
|
+
break
|
|
76
|
+
result[filled] = np.clip(accum[filled] / counts[filled, None], 0, 255).astype(np.uint8)
|
|
77
|
+
seed[filled] = True
|
|
78
|
+
remaining[filled] = False
|
|
79
|
+
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def expand_mask(mask: np.ndarray, iterations: int) -> np.ndarray:
|
|
84
|
+
result = mask.copy()
|
|
85
|
+
for _ in range(iterations):
|
|
86
|
+
expanded = result.copy()
|
|
87
|
+
for dy in (-1, 0, 1):
|
|
88
|
+
for dx in (-1, 0, 1):
|
|
89
|
+
if dy == 0 and dx == 0:
|
|
90
|
+
continue
|
|
91
|
+
expanded |= shifted(result, dy, dx)
|
|
92
|
+
if np.array_equal(expanded, result):
|
|
93
|
+
break
|
|
94
|
+
result = expanded
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def clean_array(
|
|
99
|
+
rgba: np.ndarray,
|
|
100
|
+
key: tuple[int, int, int],
|
|
101
|
+
hard_dist: float,
|
|
102
|
+
soft_dist: float,
|
|
103
|
+
max_alpha: int,
|
|
104
|
+
bleed_iterations: int,
|
|
105
|
+
edge_despill: bool,
|
|
106
|
+
edge_radius: int,
|
|
107
|
+
edge_strength: float,
|
|
108
|
+
) -> tuple[np.ndarray, dict[str, int]]:
|
|
109
|
+
arr = rgba.copy()
|
|
110
|
+
rgb = arr[..., :3].astype(np.float32)
|
|
111
|
+
alpha = arr[..., 3].astype(np.float32)
|
|
112
|
+
key_rgb = np.array(key, dtype=np.float32)
|
|
113
|
+
dist = np.sqrt(np.sum((rgb - key_rgb) ** 2, axis=2))
|
|
114
|
+
|
|
115
|
+
candidate = (alpha > 0) & (alpha <= max_alpha) & (dist < soft_dist)
|
|
116
|
+
hard = candidate & (dist <= hard_dist)
|
|
117
|
+
feather = candidate & (dist > hard_dist)
|
|
118
|
+
|
|
119
|
+
matte = smoothstep(hard_dist, soft_dist, dist)
|
|
120
|
+
new_alpha = alpha.copy()
|
|
121
|
+
new_alpha[hard] = 0.0
|
|
122
|
+
new_alpha[feather] = np.minimum(new_alpha[feather], alpha[feather] * matte[feather])
|
|
123
|
+
|
|
124
|
+
old_a = np.clip(alpha / 255.0, 1.0 / 255.0, 1.0)
|
|
125
|
+
unmixed = (rgb - key_rgb * (1.0 - old_a[..., None])) / old_a[..., None]
|
|
126
|
+
spill = candidate & ~hard
|
|
127
|
+
rgb[spill] = np.clip(unmixed[spill], 0, 255)
|
|
128
|
+
|
|
129
|
+
chroma_mask = dist < soft_dist
|
|
130
|
+
rgb_uint8 = np.clip(rgb, 0, 255).astype(np.uint8)
|
|
131
|
+
alpha_uint8 = np.clip(np.rint(new_alpha), 0, 255).astype(np.uint8)
|
|
132
|
+
near_foreground = expand_mask(alpha_uint8 > 8, min(3, bleed_iterations))
|
|
133
|
+
hidden_chroma = (alpha <= 8) & chroma_mask & near_foreground
|
|
134
|
+
rgb_uint8 = bleed_rgb(rgb_uint8, alpha_uint8, chroma_mask, hidden_chroma, bleed_iterations)
|
|
135
|
+
|
|
136
|
+
arr[..., :3] = rgb_uint8
|
|
137
|
+
arr[..., 3] = alpha_uint8
|
|
138
|
+
|
|
139
|
+
edge_pixels = 0
|
|
140
|
+
if edge_despill:
|
|
141
|
+
arr, edge_pixels = despill_opaque_edge(arr, edge_radius, edge_strength, bleed_iterations)
|
|
142
|
+
|
|
143
|
+
changed = np.any(arr != rgba, axis=2)
|
|
144
|
+
stats = {
|
|
145
|
+
"changed": int(np.count_nonzero(changed)),
|
|
146
|
+
"keyed": int(np.count_nonzero(hard)),
|
|
147
|
+
"feathered": int(np.count_nonzero(feather)),
|
|
148
|
+
"edge": edge_pixels,
|
|
149
|
+
}
|
|
150
|
+
return arr, stats
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def despill_opaque_edge(
|
|
154
|
+
rgba: np.ndarray,
|
|
155
|
+
radius: int,
|
|
156
|
+
strength: float,
|
|
157
|
+
bleed_iterations: int,
|
|
158
|
+
) -> tuple[np.ndarray, int]:
|
|
159
|
+
arr = rgba.copy()
|
|
160
|
+
rgb = arr[..., :3].astype(np.uint8)
|
|
161
|
+
alpha = arr[..., 3]
|
|
162
|
+
opaque = alpha > 8
|
|
163
|
+
edge = expand_mask(~opaque, radius) & opaque
|
|
164
|
+
|
|
165
|
+
r = rgb[..., 0].astype(np.int16)
|
|
166
|
+
g = rgb[..., 1].astype(np.int16)
|
|
167
|
+
b = rgb[..., 2].astype(np.int16)
|
|
168
|
+
magenta_score = (np.minimum(r, b) - g) + (b - g)
|
|
169
|
+
edge_spill = (
|
|
170
|
+
edge
|
|
171
|
+
& (r > 130)
|
|
172
|
+
& (b > 115)
|
|
173
|
+
& ((np.minimum(r, b) - g) > 22)
|
|
174
|
+
& ((b - g) > 18)
|
|
175
|
+
)
|
|
176
|
+
if not np.any(edge_spill):
|
|
177
|
+
return arr, 0
|
|
178
|
+
|
|
179
|
+
repaired_rgb = bleed_rgb(rgb, alpha, edge_spill, edge_spill, bleed_iterations)
|
|
180
|
+
matte = smoothstep(55.0, 275.0, magenta_score.astype(np.float32))
|
|
181
|
+
new_alpha = alpha.astype(np.float32)
|
|
182
|
+
new_alpha[edge_spill] *= 1.0 - np.clip(strength, 0.0, 1.0) * matte[edge_spill]
|
|
183
|
+
|
|
184
|
+
arr[..., :3] = repaired_rgb
|
|
185
|
+
arr[..., 3] = np.clip(np.rint(new_alpha), 0, 255).astype(np.uint8)
|
|
186
|
+
return arr, int(np.count_nonzero(edge_spill))
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def png_paths(paths: list[str]) -> list[Path]:
|
|
190
|
+
files: list[Path] = []
|
|
191
|
+
for item in paths:
|
|
192
|
+
path = Path(item)
|
|
193
|
+
if path.is_file() and path.suffix.lower() == ".png":
|
|
194
|
+
files.append(path)
|
|
195
|
+
elif path.is_dir():
|
|
196
|
+
files.extend(sorted(path.rglob("*.png")))
|
|
197
|
+
return sorted(set(files))
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def parse_key(value: str) -> tuple[int, int, int]:
|
|
201
|
+
raw = value.strip().lstrip("#")
|
|
202
|
+
if len(raw) != 6:
|
|
203
|
+
raise argparse.ArgumentTypeError("key must be a 6-digit hex color")
|
|
204
|
+
return tuple(int(raw[i : i + 2], 16) for i in (0, 2, 4))
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def main() -> int:
|
|
208
|
+
parser = argparse.ArgumentParser()
|
|
209
|
+
parser.add_argument("paths", nargs="*", default=list(DEFAULT_PATHS))
|
|
210
|
+
parser.add_argument("--key", type=parse_key, default=parse_key("#ff00ff"))
|
|
211
|
+
parser.add_argument("--hard-dist", type=float, default=42.0)
|
|
212
|
+
parser.add_argument("--soft-dist", type=float, default=118.0)
|
|
213
|
+
parser.add_argument("--max-alpha", type=int, default=180)
|
|
214
|
+
parser.add_argument("--bleed-iterations", type=int, default=10)
|
|
215
|
+
parser.add_argument("--edge-despill", action="store_true")
|
|
216
|
+
parser.add_argument("--edge-radius", type=int, default=3)
|
|
217
|
+
parser.add_argument("--edge-strength", type=float, default=0.75)
|
|
218
|
+
parser.add_argument("--apply", action="store_true")
|
|
219
|
+
args = parser.parse_args()
|
|
220
|
+
|
|
221
|
+
files = png_paths(args.paths)
|
|
222
|
+
totals = {"files": 0, "changed": 0, "keyed": 0, "feathered": 0}
|
|
223
|
+
|
|
224
|
+
for path in files:
|
|
225
|
+
original = np.array(Image.open(path).convert("RGBA"))
|
|
226
|
+
cleaned, stats = clean_array(
|
|
227
|
+
original,
|
|
228
|
+
args.key,
|
|
229
|
+
args.hard_dist,
|
|
230
|
+
args.soft_dist,
|
|
231
|
+
args.max_alpha,
|
|
232
|
+
args.bleed_iterations,
|
|
233
|
+
args.edge_despill,
|
|
234
|
+
args.edge_radius,
|
|
235
|
+
args.edge_strength,
|
|
236
|
+
)
|
|
237
|
+
if stats["changed"] == 0:
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
totals["files"] += 1
|
|
241
|
+
totals["changed"] += stats["changed"]
|
|
242
|
+
totals["keyed"] += stats["keyed"]
|
|
243
|
+
totals["feathered"] += stats["feathered"]
|
|
244
|
+
totals["edge"] = totals.get("edge", 0) + stats["edge"]
|
|
245
|
+
action = "cleaned" if args.apply else "would clean"
|
|
246
|
+
print(
|
|
247
|
+
f"{action} {path}: changed={stats['changed']} "
|
|
248
|
+
f"keyed={stats['keyed']} feathered={stats['feathered']} edge={stats['edge']}"
|
|
249
|
+
)
|
|
250
|
+
if args.apply:
|
|
251
|
+
Image.fromarray(cleaned, "RGBA").save(path)
|
|
252
|
+
|
|
253
|
+
mode = "APPLIED" if args.apply else "DRY_RUN"
|
|
254
|
+
print(
|
|
255
|
+
f"{mode} files={totals['files']} changed={totals['changed']} "
|
|
256
|
+
f"keyed={totals['keyed']} feathered={totals['feathered']} edge={totals.get('edge', 0)}"
|
|
257
|
+
)
|
|
258
|
+
return 0
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
if __name__ == "__main__":
|
|
262
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Copy approved extracted GUI icons as-is and write a manifest."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import shutil
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def unique_name(name: str, seen: defaultdict[str, int]) -> str:
|
|
14
|
+
seen[name] += 1
|
|
15
|
+
if seen[name] == 1:
|
|
16
|
+
return name
|
|
17
|
+
return f"{name}_{seen[name]}"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main() -> None:
|
|
21
|
+
parser = argparse.ArgumentParser()
|
|
22
|
+
parser.add_argument("--source-dir", required=True)
|
|
23
|
+
parser.add_argument("--run-dir", required=True)
|
|
24
|
+
parser.add_argument("--mapping", required=True, help="JSON list of {source,name} entries")
|
|
25
|
+
parser.add_argument("--technique", default="copy-approved-icons-as-is")
|
|
26
|
+
args = parser.parse_args()
|
|
27
|
+
|
|
28
|
+
source_dir = Path(args.source_dir)
|
|
29
|
+
run_dir = Path(args.run_dir)
|
|
30
|
+
mapping = json.loads(Path(args.mapping).read_text(encoding="utf-8"))
|
|
31
|
+
icons_dir = run_dir / "final" / "icons"
|
|
32
|
+
icons_dir.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
seen: defaultdict[str, int] = defaultdict(int)
|
|
35
|
+
records = []
|
|
36
|
+
for item in mapping:
|
|
37
|
+
source_name = item["source"]
|
|
38
|
+
semantic_name = unique_name(item["name"], seen)
|
|
39
|
+
source_path = source_dir / source_name
|
|
40
|
+
if not source_path.exists():
|
|
41
|
+
raise SystemExit(f"missing source icon: {source_path}")
|
|
42
|
+
destination = icons_dir / f"{semantic_name}{source_path.suffix.lower()}"
|
|
43
|
+
shutil.copy2(source_path, destination)
|
|
44
|
+
records.append(
|
|
45
|
+
{
|
|
46
|
+
"name": semantic_name,
|
|
47
|
+
"file": str(destination.relative_to(run_dir / "final")),
|
|
48
|
+
"source": str(source_path),
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
manifest = {
|
|
53
|
+
"runId": run_dir.name,
|
|
54
|
+
"sourceDir": str(source_dir),
|
|
55
|
+
"technique": args.technique,
|
|
56
|
+
"icons": records,
|
|
57
|
+
}
|
|
58
|
+
manifest_path = run_dir / "final" / "icons_manifest.json"
|
|
59
|
+
manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
|
|
60
|
+
print(manifest_path)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
main()
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Prepare a GUI asset generation run folder and catalog."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def parse_pack(value: str) -> dict:
|
|
13
|
+
parts = value.split(":", 2)
|
|
14
|
+
if len(parts) != 3:
|
|
15
|
+
raise argparse.ArgumentTypeError("pack must be id:grid:icon1,icon2")
|
|
16
|
+
pack_id, grid, icons_csv = parts
|
|
17
|
+
icons = [item.strip() for item in icons_csv.split(",") if item.strip()]
|
|
18
|
+
if not pack_id or "x" not in grid or not icons:
|
|
19
|
+
raise argparse.ArgumentTypeError("pack must include id, grid like 4x3, and icons")
|
|
20
|
+
return {"id": pack_id.strip(), "grid": grid.strip(), "icons": icons}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def prompt_for_pack(pack: dict, style_notes: str, chroma_key: str, phase: str) -> str:
|
|
24
|
+
phase_line = (
|
|
25
|
+
"Mode: PREVIEW. Prioritize style and semantic fidelity for approval before bulk generation."
|
|
26
|
+
if phase == "preview"
|
|
27
|
+
else "Mode: BULK. Keep style locked to approved preview and maximize consistency across many packs."
|
|
28
|
+
)
|
|
29
|
+
return "\n".join(
|
|
30
|
+
[
|
|
31
|
+
f"Create one GUI icon atlas on a flat {chroma_key} chroma-key background.",
|
|
32
|
+
phase_line,
|
|
33
|
+
f"Style: {style_notes}.",
|
|
34
|
+
f"Grid: {pack['grid']}, equal cells, centered icons, 10% safe margin.",
|
|
35
|
+
"Icons in row order: " + ", ".join(pack["icons"]) + ".",
|
|
36
|
+
(
|
|
37
|
+
"Quality rules: no text, no labels, no shadows, no glow, no guides, "
|
|
38
|
+
"no duplicate generic placeholders, no clipping, no overlap, no scenery."
|
|
39
|
+
),
|
|
40
|
+
"Output only the atlas image.",
|
|
41
|
+
]
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def main() -> None:
|
|
46
|
+
parser = argparse.ArgumentParser()
|
|
47
|
+
parser.add_argument("--run-id", required=True)
|
|
48
|
+
parser.add_argument("--output-root", default="assets/ui/generated")
|
|
49
|
+
parser.add_argument("--style-notes", required=True)
|
|
50
|
+
parser.add_argument("--chroma-key", default="#FF00FF")
|
|
51
|
+
parser.add_argument("--phase", choices=["preview", "bulk"], default="preview")
|
|
52
|
+
parser.add_argument("--pack", action="append", type=parse_pack, required=True)
|
|
53
|
+
args = parser.parse_args()
|
|
54
|
+
|
|
55
|
+
run_dir = Path(args.output_root) / args.run_id
|
|
56
|
+
raw_dir = run_dir / "raw"
|
|
57
|
+
prompts_dir = run_dir / "prompts"
|
|
58
|
+
qa_dir = run_dir / "qa"
|
|
59
|
+
for directory in (raw_dir, prompts_dir, qa_dir):
|
|
60
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
|
|
62
|
+
packs = []
|
|
63
|
+
for pack in args.pack:
|
|
64
|
+
prompt_path = prompts_dir / f"{pack['id']}.txt"
|
|
65
|
+
prompt_path.write_text(prompt_for_pack(pack, args.style_notes, args.chroma_key, args.phase), encoding="utf-8")
|
|
66
|
+
packs.append(
|
|
67
|
+
{
|
|
68
|
+
"id": pack["id"],
|
|
69
|
+
"grid": pack["grid"],
|
|
70
|
+
"icons": pack["icons"],
|
|
71
|
+
"promptFile": str(prompt_path.relative_to(run_dir)),
|
|
72
|
+
"rawFile": str((raw_dir / f"{pack['id']}.png").relative_to(run_dir)),
|
|
73
|
+
"source": None,
|
|
74
|
+
"qa": {"state": "pending", "notes": ""},
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
catalog = {
|
|
79
|
+
"runId": args.run_id,
|
|
80
|
+
"createdAt": datetime.now(timezone.utc).isoformat(),
|
|
81
|
+
"phase": args.phase,
|
|
82
|
+
"styleNotes": args.style_notes,
|
|
83
|
+
"chromaKey": args.chroma_key,
|
|
84
|
+
"packs": packs,
|
|
85
|
+
}
|
|
86
|
+
(run_dir / "catalog.json").write_text(json.dumps(catalog, indent=2) + "\n", encoding="utf-8")
|
|
87
|
+
print(run_dir)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
main()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Suggest GUI atlas canvas options ordered from large to small."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
CANVAS_ORDER = [
|
|
10
|
+
("8x6", 48),
|
|
11
|
+
("8x5", 40),
|
|
12
|
+
("6x6", 36),
|
|
13
|
+
("6x5", 30),
|
|
14
|
+
("7x4", 28),
|
|
15
|
+
("5x5", 25),
|
|
16
|
+
("6x4", 24),
|
|
17
|
+
("4x4", 16),
|
|
18
|
+
("4x3", 12),
|
|
19
|
+
("3x3", 9),
|
|
20
|
+
]
|
|
21
|
+
QUALITY_ORDER = [("6x5", 30), ("5x5", 25), ("6x4", 24), ("4x4", 16), ("4x3", 12), ("3x3", 9), ("8x6", 48)]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def quality_note(grid: str) -> str:
|
|
25
|
+
if grid == "8x6":
|
|
26
|
+
return "maximum throughput, up to 48 icons in one image; use after preview approval"
|
|
27
|
+
if grid in {"8x5", "6x6"}:
|
|
28
|
+
return "high throughput with slightly more breathing room than 8x6"
|
|
29
|
+
if grid in {"6x5", "7x4", "5x5", "6x4"}:
|
|
30
|
+
return "balanced bulk mode for cleaner semantics"
|
|
31
|
+
if grid == "4x4":
|
|
32
|
+
return "quality-priority compact bulk mode"
|
|
33
|
+
return "precision fallback for difficult icon semantics"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def main() -> None:
|
|
37
|
+
parser = argparse.ArgumentParser()
|
|
38
|
+
parser.add_argument("--icon-count", type=int, required=True)
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
"--strategy",
|
|
41
|
+
choices=["throughput", "quality"],
|
|
42
|
+
default="throughput",
|
|
43
|
+
help="throughput: recommend max icon count per image; quality: recommend safer dense canvas",
|
|
44
|
+
)
|
|
45
|
+
args = parser.parse_args()
|
|
46
|
+
|
|
47
|
+
icon_count = max(1, args.icon_count)
|
|
48
|
+
print(f"icon_count={icon_count}")
|
|
49
|
+
print("canvas_options_large_to_small:")
|
|
50
|
+
for grid, capacity in CANVAS_ORDER:
|
|
51
|
+
fits = "fits" if capacity >= icon_count else "does_not_fit"
|
|
52
|
+
print(f"- {grid} capacity={capacity} {fits} note=\"{quality_note(grid)}\"")
|
|
53
|
+
|
|
54
|
+
if args.strategy == "throughput":
|
|
55
|
+
recommended = "8x6"
|
|
56
|
+
else:
|
|
57
|
+
recommended = next((grid for grid, capacity in QUALITY_ORDER if capacity >= icon_count), "5x5")
|
|
58
|
+
print(f"strategy={args.strategy}")
|
|
59
|
+
print(f"recommended_grid={recommended}")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
main()
|