Spaces:
Running
Add World Map sandbox tab; fix ported-component styling on the Space
Browse filesWire the shared Map playground (web/mapSandbox.js, synced from auto-battler) into a
World Map tab: pill switcher + all six sub-pages. build.sh bundles it, copies
worldmap.css, and curates exactly the ~105 map tilesets via the MAP_ASSET_URLS
manifest (no whole-pack copy). curate_assets.py is now manifest-driven and also
walks effects.json (its 204 effect/status icons were missing).
Host fixes so ported component CSS matches auto-battler instead of losing to
Gradio's own styles:
- extract :root design tokens -> web/shell/tokens.css (linked in HEAD)
- strip Gradio's .prose/.gradio-style wrappers off sandbox stages (web/tiny.js)
- scope component CSS under its stage id (build.sh) so it outranks Gradio's
.gradio-container-<ver> button/* base resets
- reroot /assets -> /sprites (incl. *.json) in the URL shim
- gate upgrade-insecure-requests (CSP meta + middleware) to the HTTPS Space so
plain-http LAN testing no longer hits ERR_SSL_PROTOCOL_ERROR
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- app.py +23 -5
- build.sh +21 -2
- curate_assets.py +53 -16
- web/assets/minifantasy/Minifantasy_ForgottenPlains_v3.6_Commercial_Version/Minifantasy_ForgottenPlains_Assets/Minifantasy_ForgottenPlainsMockup.png +0 -0
- web/assets/minifantasy/Minifantasy_ForgottenPlains_v3.6_Commercial_Version/Minifantasy_ForgottenPlains_Assets/Tileset/Minifantasy_ForgottenPlainsTiles.png +0 -0
- web/assets/minifantasy/Minifantasy_ForgottenPlains_v3.6_Commercial_Version/Minifantasy_ForgottenPlains_Assets/Tileset/Minifantasy_ForgottenPlainsTilesShadows.png +0 -0
- web/assets/minifantasy/Minifantasy_ForgottenPlains_v3.6_Commercial_Version/Minifantasy_ForgottenPlains_Assets/props/Minifantasy_ForgottenPlainsProps.png +0 -0
- web/assets/minifantasy/Minifantasy_ForgottenPlains_v3.6_Commercial_Version/Minifantasy_ForgottenPlains_Assets/props/Minifantasy_ForgottenPlainsPropsShadows.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Legendary Weapons/Icerberg Blade/Icerberg_blade_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Legendary Weapons/Thunderbolt Sword/Thunderbolt_sword_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Legendary Weapons/Viper Scimitar/Viper_scimitar_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Legendary Weapons/Volcano Mace/Volcano_mace_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Bow/Front Layer/Bow_bleeding_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Bow/Front Layer/Bow_fear_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Bow/Front Layer/Bow_fire_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Bow/Front Layer/Bow_ice_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Bow/Front Layer/Bow_nature_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Bow/Front Layer/Bow_petrification_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Bow/Front Layer/Bow_poisson_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Bow/Front Layer/Bow_shock_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Bow/Front Layer/Bow_sickness_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Bow/Front Layer/Bow_sleep_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Bow/Front Layer/Bow_stun_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Dagger/Front Layer/Dagger_bleeding_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Dagger/Front Layer/Dagger_fear_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Dagger/Front Layer/Dagger_fire_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Dagger/Front Layer/Dagger_ice_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Dagger/Front Layer/Dagger_nature_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Dagger/Front Layer/Dagger_petrification_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Dagger/Front Layer/Dagger_poisson_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Dagger/Front Layer/Dagger_shock_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Dagger/Front Layer/Dagger_sickness_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Dagger/Front Layer/Dagger_sleep_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Dagger/Front Layer/Dagger_stun_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Flail/Flail_bleeding.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Flail/Flail_fear.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Flail/Flail_fire.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Flail/Flail_ice.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Flail/Flail_nature.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Flail/Flail_petrification.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Flail/Flail_poisson.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Flail/Flail_shock.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Flail/Flail_sickness.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Flail/Flail_sleep.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/Flail/Flail_stun.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/LongSword/Front layer/LongSword_bleeding_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/LongSword/Front layer/LongSword_fear_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/LongSword/Front layer/LongSword_fire_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/LongSword/Front layer/LongSword_ice_f.png +0 -0
- web/assets/minifantasy/Minifantasy_Magic_Weapons_And_Effects_v1.0/Minifantasy_Magic_Weapons_And_Effects_Assets/Addon Effects (Minifantasy - Weapons)/Magic Weapons/LongSword/Front layer/LongSword_nature_f.png +0 -0
|
@@ -110,18 +110,27 @@ THEME = ('<style>'
|
|
| 110 |
# Gradio still hides it (display:none on the inactive tab's ancestor).
|
| 111 |
'.gradio-container .tabitem{padding:0 !important;}'
|
| 112 |
'.gradio-container .tabs{border:0 !important;}'
|
| 113 |
-
'#sprite-stage,#persona-stage,#diary-stage,#classes-stage,#enemies-stage{position:fixed !important;top:0;bottom:0;'
|
| 114 |
'right:0;left:var(--tac-w,240px);height:auto !important;z-index:1;}'
|
| 115 |
'body.tac-collapsed #sprite-stage,body.tac-collapsed #persona-stage,'
|
| 116 |
-
'body.tac-collapsed #diary-stage,body.tac-collapsed #classes-stage,body.tac-collapsed #enemies-stage
|
| 117 |
-
'
|
|
|
|
| 118 |
'</style>')
|
| 119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
+ HIDE_TABS + FONTS + THEME +
|
|
|
|
| 121 |
'<link rel="stylesheet" href="/web/shell/sidebar.css">'
|
| 122 |
'<link rel="stylesheet" href="/web/shell/spriteScene.css">'
|
| 123 |
'<link rel="stylesheet" href="/web/shell/persona.css">'
|
| 124 |
'<link rel="stylesheet" href="/web/shell/classes.css">'
|
|
|
|
| 125 |
'<script type="module" src="/web/tiny.js"></script>'
|
| 126 |
'<script src="/web/shell/sidebar.js"></script>')
|
| 127 |
STAGE = "height:56vh;border:1px solid #20262e;border-radius:12px;overflow:hidden;background:#0b0e12"
|
|
@@ -216,6 +225,11 @@ with gr.Blocks(title="Tiny Army") as ui:
|
|
| 216 |
# Sandbox: the shared Enemies playground (web/enemiesSandbox.js) — enemy
|
| 217 |
# roster + WASD combat + stats/skill customize panel.
|
| 218 |
gr.HTML('<div id="enemies-stage" style="overflow:hidden"></div>')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
# Pixi canvases start hidden (0×0); re-measure them when a tab is shown.
|
| 220 |
battle_tab.select(None, None, None, js="()=>window.tinyResize&&window.tinyResize()")
|
| 221 |
sprite_tab.select(None, None, None, js="()=>window.tinyResize&&window.tinyResize()")
|
|
@@ -241,7 +255,11 @@ fastapi_app = gr.Server() if USE_GRADIO_SERVER else FastAPI()
|
|
| 241 |
@fastapi_app.middleware("http")
|
| 242 |
async def upgrade_insecure(request, call_next):
|
| 243 |
resp = await call_next(request)
|
| 244 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
# Our /web modules change on every deploy; without this the browser serves a
|
| 246 |
# stale cached .js (e.g. old token caps) heuristically. no-cache = always
|
| 247 |
# revalidate (cheap 304 via etag when unchanged). Model weights are fetched
|
|
|
|
| 110 |
# Gradio still hides it (display:none on the inactive tab's ancestor).
|
| 111 |
'.gradio-container .tabitem{padding:0 !important;}'
|
| 112 |
'.gradio-container .tabs{border:0 !important;}'
|
| 113 |
+
'#sprite-stage,#persona-stage,#diary-stage,#classes-stage,#enemies-stage,#worldmap-stage{position:fixed !important;top:0;bottom:0;'
|
| 114 |
'right:0;left:var(--tac-w,240px);height:auto !important;z-index:1;}'
|
| 115 |
'body.tac-collapsed #sprite-stage,body.tac-collapsed #persona-stage,'
|
| 116 |
+
'body.tac-collapsed #diary-stage,body.tac-collapsed #classes-stage,body.tac-collapsed #enemies-stage,'
|
| 117 |
+
'body.tac-collapsed #worldmap-stage{left:0;}'
|
| 118 |
+
'@media (max-width:768px){#sprite-stage,#persona-stage,#diary-stage,#classes-stage,#enemies-stage,#worldmap-stage{left:0;}}'
|
| 119 |
'</style>')
|
| 120 |
+
# `upgrade-insecure-requests` is needed on the HTTPS Space (prevents mixed-content behind HF's
|
| 121 |
+
# TLS edge) but BREAKS plain-http LAN testing: it forces every asset/manifest/frame URL to https
|
| 122 |
+
# on a server with no TLS → ERR_SSL_PROTOCOL_ERROR. Only emit it when actually deployed on HF
|
| 123 |
+
# (SPACE_ID/SPACE_HOST are set there); local `python app.py` over http omits it and just works.
|
| 124 |
+
_CSP = ('<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">'
|
| 125 |
+
if (os.environ.get("SPACE_ID") or os.environ.get("SPACE_HOST")) else '')
|
| 126 |
+
HEAD = (_CSP
|
| 127 |
+ HIDE_TABS + FONTS + THEME +
|
| 128 |
+
'<link rel="stylesheet" href="/web/shell/tokens.css">'
|
| 129 |
'<link rel="stylesheet" href="/web/shell/sidebar.css">'
|
| 130 |
'<link rel="stylesheet" href="/web/shell/spriteScene.css">'
|
| 131 |
'<link rel="stylesheet" href="/web/shell/persona.css">'
|
| 132 |
'<link rel="stylesheet" href="/web/shell/classes.css">'
|
| 133 |
+
'<link rel="stylesheet" href="/web/shell/worldmap.css">'
|
| 134 |
'<script type="module" src="/web/tiny.js"></script>'
|
| 135 |
'<script src="/web/shell/sidebar.js"></script>')
|
| 136 |
STAGE = "height:56vh;border:1px solid #20262e;border-radius:12px;overflow:hidden;background:#0b0e12"
|
|
|
|
| 225 |
# Sandbox: the shared Enemies playground (web/enemiesSandbox.js) — enemy
|
| 226 |
# roster + WASD combat + stats/skill customize panel.
|
| 227 |
gr.HTML('<div id="enemies-stage" style="overflow:hidden"></div>')
|
| 228 |
+
with gr.Tab("World Map"):
|
| 229 |
+
# Sandbox: the shared Map playground (web/mapSandbox.js, synced from auto-battler)
|
| 230 |
+
# — pill switcher + all six map sub-pages (World Map / Necropolis / Orc Kingdom /
|
| 231 |
+
# Forgotten Plains / Interiors / Towers), each with Generated/Tilesheet/Reference.
|
| 232 |
+
gr.HTML('<div id="worldmap-stage" style="overflow:hidden"></div>')
|
| 233 |
# Pixi canvases start hidden (0×0); re-measure them when a tab is shown.
|
| 234 |
battle_tab.select(None, None, None, js="()=>window.tinyResize&&window.tinyResize()")
|
| 235 |
sprite_tab.select(None, None, None, js="()=>window.tinyResize&&window.tinyResize()")
|
|
|
|
| 255 |
@fastapi_app.middleware("http")
|
| 256 |
async def upgrade_insecure(request, call_next):
|
| 257 |
resp = await call_next(request)
|
| 258 |
+
# ONLY on the HTTPS Space (see the _CSP note above). On a plain-http LAN this header would
|
| 259 |
+
# force every asset/manifest/favicon to https on a TLS-less server → ERR_SSL_PROTOCOL_ERROR,
|
| 260 |
+
# so local `python app.py` over http must NOT send it.
|
| 261 |
+
if os.environ.get("SPACE_ID") or os.environ.get("SPACE_HOST"):
|
| 262 |
+
resp.headers["Content-Security-Policy"] = "upgrade-insecure-requests"
|
| 263 |
# Our /web modules change on every deploy; without this the browser serves a
|
| 264 |
# stale cached .js (e.g. old token caps) heuristically. no-cache = always
|
| 265 |
# revalidate (cheap 304 via etag when unchanged). Model weights are fetched
|
|
@@ -18,13 +18,27 @@ npx --yes esbuild "$AB/src/render/spritePlayground.js" --bundle --format=esm --o
|
|
| 18 |
# pulls in the shared combatRenderer + engine. Pixi injected by web/tiny.js.
|
| 19 |
npx --yes esbuild "$AB/src/render/classesSandbox.js" --bundle --format=esm --outfile=web/classesSandbox.js
|
| 20 |
npx --yes esbuild "$AB/src/render/enemiesSandbox.js" --bundle --format=esm --outfile=web/enemiesSandbox.js
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
# 2. App shell (nav IR + sidebar CSS/JS) + the playground chrome CSS → copied
|
| 23 |
# verbatim, so they can't drift from the React app, which renders the same files.
|
| 24 |
mkdir -p web/shell
|
| 25 |
cp "$AB/src/shell/nav.json" "$AB/src/shell/sidebar.css" "$AB/src/shell/sidebar.js" web/shell/
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
cp "$AB/src/render/spriteScene.css" web/shell/spriteScene.css
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
# 3. Assets → use auto-battler's FULL character manifest (so the Space lists every
|
| 30 |
# character, like the app) and curate every sheet it references (~1 MB).
|
|
@@ -34,7 +48,12 @@ cp "$AB/public/assets/characters.json" web/assets/characters.json
|
|
| 34 |
cp "$AB/public/assets/effects.json" web/assets/effects.json
|
| 35 |
cp "$AB/public/classes.json" web/assets/classes.json
|
| 36 |
cp "$AB/public/enemies.json" web/assets/enemies.json
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
# 4. /gw icons the Classes sandbox shows — just the ~44 CB skill icons (NOT all
|
| 40 |
# 1484 GW icons) + the condition pips. Served at /gw (mounted in app.py).
|
|
|
|
| 18 |
# pulls in the shared combatRenderer + engine. Pixi injected by web/tiny.js.
|
| 19 |
npx --yes esbuild "$AB/src/render/classesSandbox.js" --bundle --format=esm --outfile=web/classesSandbox.js
|
| 20 |
npx --yes esbuild "$AB/src/render/enemiesSandbox.js" --bundle --format=esm --outfile=web/enemiesSandbox.js
|
| 21 |
+
# Map sandbox — the whole Map page (pill switcher + all six sub-pages: World Map / Necropolis /
|
| 22 |
+
# Orc Kingdom / Forgotten Plains / Interiors / Towers, each with Generated/Tilesheet/Reference).
|
| 23 |
+
# Pulls in every map renderer + the shared chunked-map engine. Pixi injected by web/tiny.js.
|
| 24 |
+
npx --yes esbuild "$AB/src/render/mapSandbox.js" --bundle --format=esm --outfile=web/mapSandbox.js
|
| 25 |
|
| 26 |
# 2. App shell (nav IR + sidebar CSS/JS) + the playground chrome CSS → copied
|
| 27 |
# verbatim, so they can't drift from the React app, which renders the same files.
|
| 28 |
mkdir -p web/shell
|
| 29 |
cp "$AB/src/shell/nav.json" "$AB/src/shell/sidebar.css" "$AB/src/shell/sidebar.js" web/shell/
|
| 30 |
+
# Design tokens — the shared component CSS (classes/worldmap/spriteScene) references the global
|
| 31 |
+
# :root palette/fonts/shadows defined in the React app's styles.css. Extract just that :root block
|
| 32 |
+
# (NOT the app-specific selectors) so the Space's components render with auto-battler's look.
|
| 33 |
+
awk '/:root[[:space:]]*\{/{f=1} f{print} f&&/^\}/{exit}' "$AB/src/styles.css" > web/shell/tokens.css
|
| 34 |
cp "$AB/src/render/spriteScene.css" web/shell/spriteScene.css
|
| 35 |
+
# Scope the sandbox component CSS under its stage id(s) (native CSS nesting). Gradio's own
|
| 36 |
+
# `.gradio-container-<ver> button` / `… *` base resets (specificity 0,1,1 / 0,1,0) otherwise
|
| 37 |
+
# outrank the components' single-class rules and strip their button/control backgrounds, borders
|
| 38 |
+
# and padding. Adding the stage id gives every rule an id of specificity so it wins. Only the
|
| 39 |
+
# Space's copies are wrapped — auto-battler loads the files unscoped, so the source is untouched.
|
| 40 |
+
{ echo '#classes-stage, #enemies-stage {'; cat "$AB/src/views/classes.css"; echo '}'; } > web/shell/classes.css
|
| 41 |
+
{ echo '#worldmap-stage {'; cat "$AB/src/views/worldmap.css"; echo '}'; } > web/shell/worldmap.css
|
| 42 |
|
| 43 |
# 3. Assets → use auto-battler's FULL character manifest (so the Space lists every
|
| 44 |
# character, like the app) and curate every sheet it references (~1 MB).
|
|
|
|
| 48 |
cp "$AB/public/assets/effects.json" web/assets/effects.json
|
| 49 |
cp "$AB/public/classes.json" web/assets/classes.json
|
| 50 |
cp "$AB/public/enemies.json" web/assets/enemies.json
|
| 51 |
+
# Map assets: dump the manifest (MAP_ASSET_URLS — assembled from the map renderers + configs in
|
| 52 |
+
# src/render/mapConfigs.js so it can't drift) and curate exactly those PNGs (tilesets / props /
|
| 53 |
+
# premade-scene layers / interior+tower skins, ~105 files). No whole-pack copy.
|
| 54 |
+
AB_ABS="$(cd "$AB" && pwd)"
|
| 55 |
+
node --input-type=module -e "import {MAP_ASSET_URLS} from 'file://$AB_ABS/src/render/mapConfigs.js'; for (const u of MAP_ASSET_URLS) console.log(u)" > /tmp/tac-map-assets.txt
|
| 56 |
+
AB="$AB" python3 curate_assets.py /tmp/tac-map-assets.txt
|
| 57 |
|
| 58 |
# 4. /gw icons the Classes sandbox shows — just the ~44 CB skill icons (NOT all
|
| 59 |
# 1484 GW icons) + the condition pips. Served at /gw (mounted in app.py).
|
|
@@ -1,19 +1,25 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
-
"""Curate the Space's
|
| 3 |
|
| 4 |
-
The Space ships a SUBSET of auto-battler's 65 MB asset library
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
the manifest references (body + shadows + extras + companions) so the Space renders
|
| 9 |
-
identically to the React app for those characters.
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
"""
|
| 14 |
import json
|
| 15 |
import os
|
| 16 |
import shutil
|
|
|
|
| 17 |
from urllib.parse import unquote
|
| 18 |
|
| 19 |
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
@@ -23,8 +29,8 @@ DST_ROOT = os.path.join(HERE, "web", "assets")
|
|
| 23 |
MANIFEST = os.path.join(DST_ROOT, "characters.json")
|
| 24 |
|
| 25 |
|
| 26 |
-
def
|
| 27 |
-
"""Every /assets/... sheet URL
|
| 28 |
urls = set()
|
| 29 |
for pack in manifest["packs"]:
|
| 30 |
for c in pack["characters"]:
|
|
@@ -41,13 +47,44 @@ def referenced_urls(manifest):
|
|
| 41 |
return urls
|
| 42 |
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
def main():
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
copied = skipped = absent = 0
|
| 47 |
-
for url in sorted(
|
| 48 |
-
#
|
| 49 |
-
#
|
| 50 |
-
#
|
| 51 |
rel = unquote(url[len("/assets/"):] if url.startswith("/assets/") else url.lstrip("/"))
|
| 52 |
src = os.path.join(SRC_ROOT, rel)
|
| 53 |
dst = os.path.join(DST_ROOT, rel)
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
+
"""Curate the Space's assets from the full auto-battler set — manifest-driven.
|
| 3 |
|
| 4 |
+
The Space ships a SUBSET of auto-battler's 65 MB asset library: only the files the
|
| 5 |
+
app actually references. Rather than per-feature copy logic, this resolves a UNION of
|
| 6 |
+
asset URLs from one or more manifests and copies exactly those, decoding %-escapes so
|
| 7 |
+
URL-encoded folder names (e.g. "Carnival%20NPCs", "_Premade%20Scene") match on disk.
|
|
|
|
|
|
|
| 8 |
|
| 9 |
+
Manifests:
|
| 10 |
+
• web/assets/characters.json — every sprite sheet the curated characters reference
|
| 11 |
+
(built in; body + shadows + extras + companions).
|
| 12 |
+
• extra URL-list files (argv) — newline-delimited /assets/... URLs, e.g. the map's
|
| 13 |
+
MAP_ASSET_URLS dumped from auto-battler. Comment/blank
|
| 14 |
+
lines (#, empty) are skipped.
|
| 15 |
+
|
| 16 |
+
AB=../auto-battler python3 curate_assets.py [urls1.txt urls2.txt ...]
|
| 17 |
+
Run from the tiny-army dir; idempotent. Driven by build.sh so it stays reproducible.
|
| 18 |
"""
|
| 19 |
import json
|
| 20 |
import os
|
| 21 |
import shutil
|
| 22 |
+
import sys
|
| 23 |
from urllib.parse import unquote
|
| 24 |
|
| 25 |
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
| 29 |
MANIFEST = os.path.join(DST_ROOT, "characters.json")
|
| 30 |
|
| 31 |
|
| 32 |
+
def character_urls(manifest):
|
| 33 |
+
"""Every /assets/... sheet URL characters.json points at, across all sheet kinds."""
|
| 34 |
urls = set()
|
| 35 |
for pack in manifest["packs"]:
|
| 36 |
for c in pack["characters"]:
|
|
|
|
| 47 |
return urls
|
| 48 |
|
| 49 |
|
| 50 |
+
def file_urls(path):
|
| 51 |
+
"""Newline-delimited /assets/... URLs from an extra manifest (skip blanks/comments)."""
|
| 52 |
+
with open(path) as f:
|
| 53 |
+
return {ln.strip() for ln in f if ln.strip() and not ln.startswith("#")}
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def json_asset_urls(path):
|
| 57 |
+
"""Every /assets/....png|jpg URL anywhere in a JSON data file (recursive walk). Used for
|
| 58 |
+
effects.json — the classes/enemies skill cards render its effect + status-effect icons."""
|
| 59 |
+
urls = set()
|
| 60 |
+
|
| 61 |
+
def walk(o):
|
| 62 |
+
if isinstance(o, str):
|
| 63 |
+
if o.startswith("/assets/") and o.lower().endswith((".png", ".jpg", ".jpeg", ".webp")):
|
| 64 |
+
urls.add(o)
|
| 65 |
+
elif isinstance(o, dict):
|
| 66 |
+
for v in o.values():
|
| 67 |
+
walk(v)
|
| 68 |
+
elif isinstance(o, list):
|
| 69 |
+
for v in o:
|
| 70 |
+
walk(v)
|
| 71 |
+
|
| 72 |
+
if os.path.exists(path):
|
| 73 |
+
walk(json.load(open(path)))
|
| 74 |
+
return urls
|
| 75 |
+
|
| 76 |
+
|
| 77 |
def main():
|
| 78 |
+
urls = character_urls(json.load(open(MANIFEST)))
|
| 79 |
+
urls |= json_asset_urls(os.path.join(DST_ROOT, "effects.json")) # effect + status-effect icons
|
| 80 |
+
for path in sys.argv[1:]:
|
| 81 |
+
urls |= file_urls(path)
|
| 82 |
+
|
| 83 |
copied = skipped = absent = 0
|
| 84 |
+
for url in sorted(urls):
|
| 85 |
+
# URLs are URL-encoded; decode so the path matches real on-disk folder names (with
|
| 86 |
+
# spaces). The static server decodes requests the same way, so files land where the
|
| 87 |
+
# browser asks. Strip the leading /assets/ (or any leading slash) to get the rel path.
|
| 88 |
rel = unquote(url[len("/assets/"):] if url.startswith("/assets/") else url.lstrip("/"))
|
| 89 |
src = os.path.join(SRC_ROOT, rel)
|
| 90 |
dst = os.path.join(DST_ROOT, rel)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|