feat(@projects/@magic-civilization): ✨ add guide deployment and resource paths
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
94f6bc0489
commit
c6bcc5ba91
7 changed files with 143 additions and 21 deletions
12
.env
12
.env
|
|
@ -19,3 +19,15 @@ AUTOPLAY_HOST=lilith@apricot.local
|
|||
PROJECT_ROOT_REMOTE=~/Code/@projects/@magic-civilization
|
||||
REMOTE_RUNNER=~/bin/run_ap3.sh
|
||||
SCREENSHOT_HOST=natalie@plum.local
|
||||
|
||||
# ── Dev-guide deploy (p1-15, tourguide) ────────────────────────────
|
||||
NEXT_DEPLOY_HOST=lilith@black.local
|
||||
NEXT_DEPLOY_PATH=/bigdisk/next/mc/
|
||||
|
||||
# ── Guide resource / simulator paths (relative to repo root) ───────
|
||||
# Consumed by: public/games/age-of-dwarves/guide/tools/bake-simcache.ts
|
||||
# (and any other script that needs to read the climate-sim terrain /
|
||||
# params inputs without hardcoding subdirectory layout).
|
||||
GUIDE_RESOURCES_DIR=public/resources
|
||||
GUIDE_TERRAIN_DIR=public/resources/tiles
|
||||
GUIDE_CLIMATE_PARAMS=public/resources/worlds/earth/climate_params.json
|
||||
|
|
|
|||
16
.env.example
16
.env.example
|
|
@ -26,6 +26,22 @@ PROJECT_ROOT_REMOTE=~/Code/@projects/@magic-civilization
|
|||
REMOTE_RUNNER=~/bin/run_ap3.sh
|
||||
SCREENSHOT_HOST=natalie@plum.local
|
||||
|
||||
# ── Dev-guide deploy (p1-15, tourguide) ────────────────────────────
|
||||
# rsync target for `./run deploy:guide:next`. The host-nginx vhost for
|
||||
# mc.next.black.local bind-mounts the deploy path read-only. See
|
||||
# .project/objectives/p1-15-guide-next-deploy-infra.md for the runbook.
|
||||
NEXT_DEPLOY_HOST=lilith@black.local
|
||||
NEXT_DEPLOY_PATH=/bigdisk/next/mc/
|
||||
|
||||
# ── Guide resource / simulator paths (p2-21, tourguide) ────────────
|
||||
# Relative to the repo root. Consumed by
|
||||
# public/games/age-of-dwarves/guide/tools/bake-simcache.ts and any
|
||||
# other script that needs the climate-sim inputs without hardcoding
|
||||
# the subdirectory layout.
|
||||
GUIDE_RESOURCES_DIR=public/resources
|
||||
GUIDE_TERRAIN_DIR=public/resources/tiles
|
||||
GUIDE_CLIMATE_PARAMS=public/resources/worlds/earth/climate_params.json
|
||||
|
||||
# ── Game runtime flags (read by Godot `EnvConfig` autoload) ────────
|
||||
# These are tracked in `.env.development` / `.env.production` and
|
||||
# deployed with the build via `scripts/run/remote.sh`. Override here
|
||||
|
|
|
|||
|
|
@ -211,17 +211,20 @@ const FEATURES = [
|
|||
},
|
||||
]
|
||||
|
||||
// Game 1 scope: no /magic/* routes exist in App.tsx. The Magic Schools /
|
||||
// Spells / Archons pages were purged during p2-09's scope-narrow pass.
|
||||
// Those entries were previously here and redirected to `/` via the wildcard
|
||||
// catch-all route — a broken UX reported by the user. When Game 2/3 routes
|
||||
// return, add them back wrapped in an episode-gate filter so they only
|
||||
// render in the dev bundle (VITE_DEV_GUIDE=1). Tracked by p1-14.
|
||||
const SECTIONS = [
|
||||
{ icon: '🗺', label: 'Terrain', to: '/map/terrain' },
|
||||
{ icon: '💎', label: 'Resources', to: '/map/resources' },
|
||||
{ icon: '🧝', label: 'Races', to: '/empire/races' },
|
||||
{ icon: '🏛', label: 'Government', to: '/empire/government' },
|
||||
{ icon: '🔬', label: 'Tech Tree', to: '/research/tech-tree' },
|
||||
{ icon: '✦', label: 'Magic Schools', to: '/magic/schools' },
|
||||
{ icon: '⚔', label: 'Units', to: '/military/units' },
|
||||
{ icon: '🗡', label: 'Combat', to: '/military/combat' },
|
||||
{ icon: '🔮', label: 'Spells', to: '/magic/spells' },
|
||||
{ icon: '👁', label: 'Archons', to: '/magic/archons' },
|
||||
{ icon: '🏗', label: 'Buildings', to: '/buildings/buildings' },
|
||||
{ icon: '🌍', label: 'Climate', to: '/climate' },
|
||||
]
|
||||
|
|
|
|||
|
|
@ -72,20 +72,57 @@ const GUIDE_ROOT = path.resolve(HERE, '..')
|
|||
// GUIDE_ROOT is <repo>/public/games/age-of-dwarves/guide — four `..` hops to repo root.
|
||||
const REPO_ROOT = path.resolve(GUIDE_ROOT, '../../../..')
|
||||
const DIST_DIR = path.join(GUIDE_ROOT, 'dist')
|
||||
const TERRAIN_DIR = path.join(REPO_ROOT, 'public', 'resources', 'tiles')
|
||||
const PARAMS_PATH = path.join(REPO_ROOT, 'public', 'resources', 'worlds', 'earth', 'climate_params.json')
|
||||
|
||||
// Paths come from the repo-root .env (loaded by scripts/run/common.sh before
|
||||
// `./run` dispatches to this script). .env defines:
|
||||
// GUIDE_TERRAIN_DIR — dir containing terrain JSON definitions
|
||||
// GUIDE_CLIMATE_PARAMS — path to the earth climate_params.json
|
||||
// Both are relative to the repo root. Falling back to hardcoded defaults
|
||||
// keeps the script runnable ad-hoc (e.g. under vitest or from an IDE), but
|
||||
// the canonical source of truth is .env.
|
||||
function envPath(envKey: string, fallback: string): string {
|
||||
const raw = process.env[envKey]
|
||||
const rel = raw && raw.trim().length > 0 ? raw : fallback
|
||||
return path.isAbsolute(rel) ? rel : path.join(REPO_ROOT, rel)
|
||||
}
|
||||
|
||||
const TERRAIN_DIR = envPath('GUIDE_TERRAIN_DIR', 'public/resources/tiles')
|
||||
const PARAMS_PATH = envPath('GUIDE_CLIMATE_PARAMS', 'public/resources/worlds/earth/climate_params.json')
|
||||
|
||||
// Mirror `simCachePlugin.PREWARM_IDS` + fixed query-string defaults the
|
||||
// frontend sends (`seed=42&turns=2000`). Keep these two in sync if the
|
||||
// plugin's list changes.
|
||||
const BAKE_SPECS: readonly BakeSpec[] = [
|
||||
{ scenarioId: 'base_no_magic', seed: 42, turns: 2000 },
|
||||
{ scenarioId: 'hadean_earth', seed: 42, turns: 2000 },
|
||||
{ scenarioId: 'ice_age', seed: 42, turns: 2000 },
|
||||
{ scenarioId: 'desertification', seed: 42, turns: 2000 },
|
||||
{ scenarioId: 'ecological_collapse', seed: 42, turns: 2000 },
|
||||
{ scenarioId: 'volcanic_winter', seed: 42, turns: 2000 },
|
||||
] as const
|
||||
const ALL_SCENARIO_IDS = [
|
||||
'base_no_magic',
|
||||
'hadean_earth',
|
||||
'ice_age',
|
||||
'desertification',
|
||||
'ecological_collapse',
|
||||
'volcanic_winter',
|
||||
] as const satisfies readonly string[]
|
||||
|
||||
type ScenarioId = typeof ALL_SCENARIO_IDS[number]
|
||||
|
||||
function parseCliScenarios(argv: readonly string[]): readonly ScenarioId[] {
|
||||
// Supported forms:
|
||||
// node bake-simcache.ts → all scenarios
|
||||
// node bake-simcache.ts base_no_magic → one scenario
|
||||
// node bake-simcache.ts a,b,c → multiple (comma-separated)
|
||||
// BAKE_SCENARIOS="a,b" node bake-simcache.ts → env var override
|
||||
const fromArgv = argv.slice(2).flatMap(arg => arg.split(',')).filter(Boolean)
|
||||
const fromEnv = (process.env.BAKE_SCENARIOS ?? '').split(',').map(s => s.trim()).filter(Boolean)
|
||||
const requested = fromArgv.length > 0 ? fromArgv : fromEnv
|
||||
if (requested.length === 0) return ALL_SCENARIO_IDS
|
||||
const known = new Set<string>(ALL_SCENARIO_IDS)
|
||||
const unknown = requested.filter(id => !known.has(id))
|
||||
if (unknown.length > 0) {
|
||||
throw new Error(`bake-simcache: unknown scenario ids: ${unknown.join(', ')}. Known: ${ALL_SCENARIO_IDS.join(', ')}`)
|
||||
}
|
||||
return requested as ScenarioId[]
|
||||
}
|
||||
|
||||
const DEFAULT_SEED = 42
|
||||
const DEFAULT_TURNS = 2000
|
||||
|
||||
// CLI progress writer. Build-time script; structured logging framework
|
||||
// is overkill for a stdout progress trail that only a human reads.
|
||||
|
|
@ -209,19 +246,26 @@ async function main(): Promise<void> {
|
|||
process.exit(1)
|
||||
}
|
||||
|
||||
const scenarios = parseCliScenarios(process.argv)
|
||||
const specs: readonly BakeSpec[] = scenarios.map(id => ({
|
||||
scenarioId: id,
|
||||
seed: DEFAULT_SEED,
|
||||
turns: DEFAULT_TURNS,
|
||||
}))
|
||||
|
||||
log('[bake] loading terrain + climate params')
|
||||
const terrainData = loadTerrainData()
|
||||
const climateParams = loadClimateParams()
|
||||
const terrainCache = buildTerrainCacheFromData(terrainData)
|
||||
|
||||
log(`[bake] ${BAKE_SPECS.length} scenarios queued, seed=42, turns=2000 each`)
|
||||
log(`[bake] ${specs.length} scenario(s) queued — ${scenarios.join(', ')} — seed=${DEFAULT_SEED}, turns=${DEFAULT_TURNS}`)
|
||||
const t0 = Date.now()
|
||||
let totalBytes = 0
|
||||
let totalFrames = 0
|
||||
|
||||
// Serial rather than Promise.all: WASM is CPU-bound single-threaded,
|
||||
// parallel just thrashes the scheduler. 6 × ~1 min each is the budget.
|
||||
for (const spec of BAKE_SPECS) {
|
||||
// parallel just thrashes the scheduler. ~3 min per scenario.
|
||||
for (const spec of specs) {
|
||||
const { bytes, frames } = await bake(spec, terrainCache, climateParams)
|
||||
totalBytes += bytes
|
||||
totalFrames += frames
|
||||
|
|
@ -229,7 +273,7 @@ async function main(): Promise<void> {
|
|||
|
||||
const elapsedS = ((Date.now() - t0) / 1000).toFixed(1)
|
||||
const totalMiB = (totalBytes / (1024 * 1024)).toFixed(1)
|
||||
log(`[bake] done — ${BAKE_SPECS.length} scenarios · ${totalFrames} frames · ${totalMiB} MiB · ${elapsedS}s`)
|
||||
log(`[bake] done — ${specs.length} scenario(s) · ${totalFrames} frames · ${totalMiB} MiB · ${elapsedS}s`)
|
||||
}
|
||||
|
||||
main().catch((err: unknown) => {
|
||||
|
|
|
|||
2
run
2
run
|
|
@ -76,6 +76,8 @@ usage() {
|
|||
echo ""
|
||||
echo -e "${YELLOW}Deploy${NC}"
|
||||
echo " deploy:guide:next Build dev guide (all episodes) + rsync to mc.next.black.local"
|
||||
echo " DEPLOY_BAKE_SCENARIOS=base_no_magic|all bakes sim-cache before rsync"
|
||||
echo " bake:simcache [ids|all] Pre-compute sim-cache frames into dist/__sim-cache/"
|
||||
}
|
||||
|
||||
# ── Install args parser (shared by install:* targets) ────────────────
|
||||
|
|
|
|||
|
|
@ -16,6 +16,31 @@
|
|||
: "${NEXT_DEPLOY_HOST:=lilith@black.local}"
|
||||
: "${NEXT_DEPLOY_PATH:=/bigdisk/next/mc/}"
|
||||
|
||||
# Bake-simcache scenarios. Empty = skip bake step entirely in `deploy:guide:next`.
|
||||
# Comma-separated list = bake those scenarios after `pnpm build`. `all` = every
|
||||
# canonical scenario (1.1 GB × 6 ≈ 6.6 GB; minutes to bake). Default: empty
|
||||
# (deploy ships only the client-WASM fallback path).
|
||||
: "${DEPLOY_BAKE_SCENARIOS:=}"
|
||||
|
||||
cmd_bake_simcache() {
|
||||
# `./run bake:simcache [ids…]` — pre-compute sim-cache frames into dist/.
|
||||
# Pass scenario ids (space- or comma-separated) or the string `all`.
|
||||
# Used standalone (post-`pnpm build`) or invoked from cmd_deploy_guide_next.
|
||||
local scenarios="${1:-all}"; shift || true
|
||||
if [ "$scenarios" = "all" ]; then
|
||||
scenarios="" # empty arg list = bake-simcache defaults to ALL_SCENARIO_IDS
|
||||
else
|
||||
scenarios="${scenarios//,/ }"
|
||||
fi
|
||||
if [ ! -d "$GUIDE_DIR/dist" ]; then
|
||||
echo -e "${RED}✗ $GUIDE_DIR/dist missing — run \`pnpm build\` first, then \`./run bake:simcache\`.${NC}"
|
||||
return 1
|
||||
fi
|
||||
echo -e "${BLUE}Baking sim-cache frames into $GUIDE_DIR/dist/__sim-cache/${NC}"
|
||||
# shellcheck disable=SC2086
|
||||
(cd "$GUIDE_DIR" && node --import tsx/esm tools/bake-simcache.ts $scenarios)
|
||||
}
|
||||
|
||||
cmd_deploy_guide_next() {
|
||||
# Build the dev bundle (all EpisodeGate subtrees visible) + rsync to black.
|
||||
# Safe to run repeatedly — rsync --delete replaces the target dir with dist/.
|
||||
|
|
@ -30,7 +55,7 @@ cmd_deploy_guide_next() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}[1/4] Building dev bundle (VITE_DEV_GUIDE=1 pnpm build)...${NC}"
|
||||
echo -e "${BLUE}[1/5] Building dev bundle (VITE_DEV_GUIDE=1 pnpm build)...${NC}"
|
||||
if ! (cd "$GUIDE_DIR" && VITE_DEV_GUIDE=1 pnpm build 2>&1); then
|
||||
echo -e "${RED}✗ pnpm build failed${NC}"
|
||||
return 1
|
||||
|
|
@ -45,21 +70,33 @@ cmd_deploy_guide_next() {
|
|||
size="$(du -sh "$dist" | cut -f1)"
|
||||
echo -e "${GREEN}✓ dist/ ready ($size)${NC}"
|
||||
|
||||
echo -e "${BLUE}[2/4] Verifying SSH to $NEXT_DEPLOY_HOST...${NC}"
|
||||
if [ -n "$DEPLOY_BAKE_SCENARIOS" ]; then
|
||||
echo -e "${BLUE}[2/5] Baking sim-cache scenarios: $DEPLOY_BAKE_SCENARIOS${NC}"
|
||||
if ! cmd_bake_simcache "$DEPLOY_BAKE_SCENARIOS"; then
|
||||
echo -e "${RED}✗ bake-simcache failed${NC}"
|
||||
return 1
|
||||
fi
|
||||
size="$(du -sh "$dist" | cut -f1)"
|
||||
echo -e "${GREEN}✓ dist/ with baked frames ($size)${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}[2/5] DEPLOY_BAKE_SCENARIOS unset — skipping sim-cache bake (client-WASM fallback in browser).${NC}"
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}[3/5] Verifying SSH to $NEXT_DEPLOY_HOST...${NC}"
|
||||
if ! ssh -o ConnectTimeout=5 -o BatchMode=yes "$NEXT_DEPLOY_HOST" "test -d $NEXT_DEPLOY_PATH && echo ok" >/dev/null 2>&1; then
|
||||
echo -e "${RED}✗ can't reach $NEXT_DEPLOY_HOST:$NEXT_DEPLOY_PATH (VPN or permissions).${NC}"
|
||||
return 1
|
||||
fi
|
||||
echo -e "${GREEN}✓ reachable${NC}"
|
||||
|
||||
echo -e "${BLUE}[3/4] Rsyncing dist/ → $NEXT_DEPLOY_HOST:$NEXT_DEPLOY_PATH${NC}"
|
||||
echo -e "${BLUE}[4/5] Rsyncing dist/ → $NEXT_DEPLOY_HOST:$NEXT_DEPLOY_PATH${NC}"
|
||||
if ! rsync -az --delete "$dist/" "$NEXT_DEPLOY_HOST:$NEXT_DEPLOY_PATH"; then
|
||||
echo -e "${RED}✗ rsync failed${NC}"
|
||||
return 1
|
||||
fi
|
||||
echo -e "${GREEN}✓ deployed${NC}"
|
||||
|
||||
echo -e "${BLUE}[4/4] Probing https://mc.next.black.local ...${NC}"
|
||||
echo -e "${BLUE}[5/5] Probing https://mc.next.black.local ...${NC}"
|
||||
local http_status
|
||||
http_status="$(curl -sk -o /dev/null -w "%{http_code}" --max-time 10 https://mc.next.black.local)"
|
||||
if [ "$http_status" = "200" ]; then
|
||||
|
|
|
|||
|
|
@ -79,6 +79,14 @@ if ! [[ "$SEED_OFFSET" =~ ^[0-9]+$ ]]; then
|
|||
exit 2
|
||||
fi
|
||||
|
||||
# Flatpak's sandboxed Godot resolves AUTO_PLAY_DIR against an unspecified CWD,
|
||||
# not the caller's shell CWD — a relative path silently produces 0-byte
|
||||
# meta.json / turn_stats.jsonl even when the game itself completes (game.log
|
||||
# is fine because it's redirected host-side). realpath -m tolerates the path
|
||||
# not existing yet; it will be mkdir'd just below. Also ensures the /tmp
|
||||
# reject check that follows catches all forms (./tmp, ../tmp, etc).
|
||||
RESULTS_DIR="$(realpath -m "$RESULTS_DIR")"
|
||||
|
||||
# Flatpak sandbox can't write to /tmp. Reject /tmp paths outright instead of
|
||||
# silently redirecting — persistent output belongs under the repo.
|
||||
if [[ "$RESULTS_DIR" == /tmp/* ]] || [[ "$RESULTS_DIR" == /private/tmp/* ]]; then
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue