feat(@projects/@magic-civilization): ✨ implement gpu-mcts rollouts
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
819f33ed38
commit
b70a396f14
13 changed files with 191 additions and 98 deletions
|
|
@ -51,7 +51,7 @@ Entry format: `YYYY-MM-DD HH:MM <short topic>: <what happened> (files=N) [ref: p
|
|||
2026-04-16 13:26 Task #7 MAP BALANCE (report-only): Seed 1 p0/p1 ring2 yields organically balanced (p0 25f/23p, p1 29f/22p, within 20% — food actually favors p1). REAL bias: `start_position.gd:_score_start_position` ignores tile.resource_id. p0 deterministically lands adjacent to production resources in ALL seeds (deep_crystal, alexandrite, wild_game); p1 never does. By T20: p0 prod=6, p1 prod=2. Unused `StartBalancer.ensure_fair_starts` exists at src/game/engine/src/generation/start_balancer.gd but is never wired. Follow-up task needed: wire StartBalancer into map_placer.gd:40 behind map_generator.use_balanced_starts flag. (map-balance-dev)
|
||||
2026-04-16 13:35 Task #6 DETERMINISM (partial, mc-ecology scope complete): commit 18f1f0d70. HashMap→BTreeMap for t10_count_by_diet, HashSet→BTreeSet for saturated_diets, added Ord to Diet enum. 11-line diff. Seed=1 50-turn 2x runs byte-identical turns 1-44 (was T35 divergence), diverge T45+. Remaining divergence in DataLoader (GDScript) — DirAccess.list_dir_begin() not order-guaranteed, Dictionary iteration post-JSON-parse. Follow-up task needed: audit data_loader.gd for sorted file enumeration + simple_heuristic_ai.gd Dictionary iteration. mc-ecology proven clean (266/266 tests pass). (determinism-dev)
|
||||
2026-04-16 14:00 Task #8 WIRE STARTBALANCER complete: StartBalancer.ensure_fair_starts now wired into map_placer.gd. balanced_retry_20260416_135710 batch results: pop_peak median 36, tiles 72, combats 312, techs 40 — all healthy. BUT victories 0/3 (stalemate). Seed 1 no longer has resource-placement disadvantage (task #7 bias closed). Remaining gap is combat balance (tasks #10). 4 PASS additions to checklist from batch 5→batch balanced_retry. (map-balance-dev)
|
||||
2026-04-16 14:05 INFRA: scripts/apricot/run_ap3.sh had UNSCOPED pkill (kills all Godot) causing sibling batch kills. Fixed in-repo to scoped pkill matching AUTO_PLAY_DIR. Deployed to apricot ~/bin/run_ap3.sh. Future run_ap3.sh invocations won't kill siblings. Enables parallel agent smokes without collision. (team-lead from dataloader-dev catch)
|
||||
2026-04-16 14:05 INFRA: scripts/autoplay/run_ap3.sh had UNSCOPED pkill (kills all Godot) causing sibling batch kills. Fixed in-repo to scoped pkill matching AUTO_PLAY_DIR. Deployed to apricot ~/bin/run_ap3.sh. Future run_ap3.sh invocations won't kill siblings. Enables parallel agent smokes without collision. (team-lead from dataloader-dev catch)
|
||||
2026-04-16 14:13 Task #9 DATALOADER DETERMINISM complete (T29→T49 byte-identical, 20-turn improvement): Commits e63088100 (data_loader.gd sorted DirAccess), 0e43a3182 (lens_unlock_manager.gd sorted enum), d2062cbd1 (pathfinder.gd A*/Dijkstra tiebreakers + atmosphere_anomalies.gd sorted keys). 104 lines total across 4 files (over ≤50 budget due to expanded surface). Remaining T50 gap is in mc-combat tactical_memory or Rust tile borders — minor, not in checklist. (dataloader-dev)
|
||||
2026-04-16 14:29 Task #10 COMBAT BALANCE DIAL-BACK (no-op verdict): tuned wall_penalty 0.70→0.75, melee_fraction 0.50→0.55, HEAL_PER_TURN 20→15 across 3 cumulative batches (option_a, option_ab, option_abc). All 3/3/3 produced 0 captures despite 260-342 combats and p1 10x kill ratio. Combat math NOT the bottleneck. Reverted all 3 to baseline (0.70/0.50/20), 103/103 mc-combat+mc-city tests pass. Handoff to #11 (AI capture commit in simple_heuristic_ai.gd). (balance-dev)
|
||||
2026-04-16 14:36 Task #11 AI CAPTURE COMMIT complete (64403f888): simple_heuristic_ai.gd +41/-3 in _decide_military_action. Three behaviors: (1) Adjacent-city attack fires BEFORE retreat/chase logic; (2) Retreat-on-low-HP suppressed when within 4 hexes of enemy city (commitment); (3) When own_mil ≥ 2×enemy_mil AND enemy city closer than nearest stray, skip chase to press city. Batch: 70/121/114 city attacks per game (was 0), 45/64/43 killed=true attacks. Victories STILL 0/3 because HP resets to 380 every turn (net-zero bug in Rust). AI side done. (capture-ai-dev)
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@
|
|||
- `tools/autoplay-report.py` — CSV + summary + assertions
|
||||
- `tools/autoplay-validate.py` — schema validator (per-file + JSONL)
|
||||
- `tools/schemas/autoplay/{meta,turn-stats-line,events-line,save}.json` — 4 JSON schemas
|
||||
- `scripts/apricot/run_ap3.sh` + `run_seeded.sh` — persistent headless runners
|
||||
- `scripts/autoplay/run_ap3.sh` + `run_seeded.sh` — persistent headless runners
|
||||
|
||||
## Known Debt / Future Work
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ evidence:
|
|||
- src/game/engine/tests/unit/test_save_manager.gd
|
||||
- src/game/engine/tests/integration/test_save_load_round_trip.gd
|
||||
- src/game/engine/scenes/tests/auto_play.gd
|
||||
- scripts/apricot/test_save_resume.sh
|
||||
- scripts/autoplay/test_save_resume.sh
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
|
@ -25,7 +25,7 @@ Save/load UI, autosave-on-quit hook, multi-slot naming, schema version with reje
|
|||
|
||||
- ✓ Save/load from main menu + in-game pause — `scenes/menus/load_game.gd` calls `list_saves()` + `load_named_slot()`; `scenes/ui/ingame_menu.gd` calls `save_to_named_slot()`.
|
||||
- ✓ Autosave on quit — `scenes/main/main.gd` handles `NOTIFICATION_WM_CLOSE_REQUEST` → `SaveManagerScript.autosave()` before `quit()`.
|
||||
- ✓ Byte-identical T100 turn_stats after save-at-T50 + load-back — `AUTO_PLAY_SAVE_AT=N` writes `mid_run.save` + quits; `AUTO_PLAY_LOAD_AUTOSAVE=<path>` resumes from that save after world loads. Integration test harness: `scripts/apricot/test_save_resume.sh`. `SaveManager.load_from_path()` added for absolute-path loads.
|
||||
- ✓ Byte-identical T100 turn_stats after save-at-T50 + load-back — `AUTO_PLAY_SAVE_AT=N` writes `mid_run.save` + quits; `AUTO_PLAY_LOAD_AUTOSAVE=<path>` resumes from that save after world loads. Integration test harness: `scripts/autoplay/test_save_resume.sh`. `SaveManager.load_from_path()` added for absolute-path loads.
|
||||
- ✓ GUT round-trip test covers new fields — `tests/integration/test_save_load_round_trip.gd` (9 tests incl. traded_luxuries, clan_id, GameState.diplomacy, wonders_built).
|
||||
- ✓ `SCHEMA_VERSION` rejection — `save_manager.gd:SCHEMA_VERSION = 2`; `_read_slot` rejects mismatches via `ERR_FILE_UNRECOGNIZED`. Test: `test_wrong_schema_version_rejected`.
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,14 @@ updated_at: 2026-04-17
|
|||
evidence:
|
||||
- src/simulator/crates/mc-ai/src/abstract_state.rs
|
||||
- src/simulator/crates/mc-ai/src/mcts_tree.rs
|
||||
- src/simulator/crates/mc-ai/src/rollout.rs
|
||||
- src/simulator/crates/mc-ai/src/gpu/inner.rs
|
||||
- src/simulator/crates/mc-ai/src/gpu/rollout.wgsl
|
||||
- src/simulator/crates/mc-ai/src/gpu/cpu_reference.rs
|
||||
- src/simulator/crates/mc-ai/tests/gpu_rollout_parity.rs
|
||||
- src/simulator/crates/mc-turn/src/gpu/mod.rs
|
||||
- src/simulator/crates/mc-ai/src/game_state.rs
|
||||
- scripts/dev-setup/bluefin.sh
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
|
@ -23,6 +29,63 @@ introduces a **GPU-batched abstract rollout** layer so the tree search can
|
|||
evaluate hundreds of candidate futures per leaf at single-digit-millisecond
|
||||
cost.
|
||||
|
||||
### 2026-04-17 update — GPU↔CPU numerical parity ACHIEVED
|
||||
|
||||
Phase C structural work shipped in the earlier team pass but the parity test
|
||||
was silently taking the skip path on headless hosts — the shader had never
|
||||
actually compiled on any adapter. A deep audit + four independent fixes landed
|
||||
this cycle proving real numerical parity:
|
||||
|
||||
1. **WGSL reserved-keyword bug**: `var active: u32 = 0u` at `rollout.wgsl:607`
|
||||
used the `active` reserved word → Naga parse panic → wgpu_core handler → try_init
|
||||
worker thread panic → timeout returned None → skip-path. Renamed to
|
||||
`active_idx`; the shader now actually compiles. Without this, the skip-path
|
||||
was structurally "passing" every test in Phase C without ever exercising the
|
||||
WGSL kernel.
|
||||
2. **Adapter backend restriction**: `wgpu::Backends::all()` picked the NVIDIA
|
||||
OpenGL adapter first on apricot, whose compute support silently fails at
|
||||
`request_device`. Restricted to `VULKAN | METAL | DX12 | BROWSER_WEBGPU`
|
||||
which all have first-class compute paths.
|
||||
3. **Device limits fix**: `Limits::default()` targets a discrete GPU — too
|
||||
large for llvmpipe / lavapipe. Changed to
|
||||
`Limits::downlevel_defaults().using_resolution(adapter.limits())` so software
|
||||
Vulkan backends can satisfy device creation.
|
||||
4. **Action-walk order unified**: the root numerical divergence. CPU
|
||||
`active_actions()` returned actions in insertion order
|
||||
`[Build, Research, Defend, Idle, Attack, ...]`; WGSL iterated k=0..9 in
|
||||
`ActionKind::ALL` numerical order `[Build, Attack, Settle, Research, ...]`.
|
||||
Identical probabilities, identical RNG draw → different action picked at
|
||||
every cumulative-sum boundary. Rewrote `active_actions()` to iterate
|
||||
`ActionKind::ALL` in canonical order (with explicit docstring warning not
|
||||
to reorder for readability).
|
||||
|
||||
**Parity verification on apricot (headless bluefin + lavapipe software
|
||||
Vulkan)**: with `MC_AI_GPU_DEBUG=1 VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json`
|
||||
driving the tests on real llvmpipe dispatch, not skip-path:
|
||||
|
||||
```
|
||||
[parity small_batch backend=Vulkan] n=16 agree=16/16 (1.000) max_drift=0.000000
|
||||
[parity partial_workgroup backend=Vulkan] n=65 agree=65/65 (1.000) max_drift=0.000000
|
||||
[parity multi_workgroup backend=Vulkan] n=128 agree=128/128 (1.000) max_drift=0.000000
|
||||
buckets: <1e-6=all others=0 across all three tests
|
||||
```
|
||||
|
||||
Not 98% (the stated tolerance) — **100% agreement, bit-identical** on all 3
|
||||
quantitative parity tests (209 inputs total). Pre-fixes: 3–6% agreement with
|
||||
max_drift 0.025–0.043 (action-boundary flips). Post-fix: integer fields
|
||||
byte-equal, scalar fields byte-equal. WGSL kernel is now a provable,
|
||||
byte-for-byte port of `rollout::walk`.
|
||||
|
||||
### 2026-04-17 update — host-side infrastructure
|
||||
|
||||
- `scripts/dev-setup/bluefin.sh` + `./run setup:bluefin` — idempotent installer
|
||||
for `weston`, `vulkan-tools`, `mesa-vulkan-drivers` on bootc/Bluefin systems
|
||||
via `rpm-ostree install --apply-live`. `--check` mode for CI.
|
||||
Delegates EDIT→RUN via `$AUTOPLAY_HOST` when invoked from EDIT.
|
||||
- `~/Code/bootc-bluefin/containerfiles/Containerfile.desktop-core` updated on
|
||||
apricot with `vulkan-tools` + `mesa-vulkan-drivers` added alongside `weston`.
|
||||
Rebooted bootc images now include these without needing the transient script.
|
||||
|
||||
## Design outline
|
||||
|
||||
- `AbstractRolloutState` — a ~256 byte `#[repr(C)]` `Pod + Zeroable` compression of
|
||||
|
|
|
|||
105
run
105
run
|
|
@ -110,91 +110,58 @@ _dispatch_install() {
|
|||
COMMAND="${1:-}"
|
||||
shift 2>/dev/null || true
|
||||
|
||||
# Special cases (subcommand requires alias, multi-arg dispatch, help).
|
||||
# Everything else resolves to `cmd_<command-with-[-:]-replaced-by-_>`.
|
||||
case "$COMMAND" in
|
||||
# ── Development ──────────────────────────────────────────────────
|
||||
play) cmd_play "$@" ;;
|
||||
editor) cmd_editor "$@" ;;
|
||||
guide) cmd_guide "$@" ;;
|
||||
lint) cmd_lint "$@" ;;
|
||||
lint:gd) cmd_lint_gd "$@" ;;
|
||||
lint:rust) cmd_lint_rust "$@" ;;
|
||||
lint:ts) cmd_lint_ts "$@" ;;
|
||||
format) cmd_format "$@" ;;
|
||||
format:gd) cmd_format_gd "$@" ;;
|
||||
format:rust) cmd_format_rust "$@" ;;
|
||||
format:ts) cmd_format_ts "$@" ;;
|
||||
typecheck) cmd_typecheck "$@" ;;
|
||||
validate) cmd_validate "$@" ;;
|
||||
test) cmd_test "$@" ;;
|
||||
test:golden) cmd_test_golden "$@" ;;
|
||||
coverage) cmd_coverage "$@" ;;
|
||||
verify) cmd_verify "$@" ;;
|
||||
screenshot) cmd_screenshot "$@" ;;
|
||||
autoplay) cmd_autoplay "$@" ;;
|
||||
autoplay-batch) cmd_autoplay_batch "$@" ;;
|
||||
help|--help|-h|"") usage; exit 0 ;;
|
||||
|
||||
# ── Build ────────────────────────────────────────────────────────
|
||||
build) cmd_build "$@" ;;
|
||||
build:wasm) cmd_build_wasm "$@" ;;
|
||||
build:gdext) cmd_build_gdext "$@" ;;
|
||||
# Export single-target takes a positional `target` arg.
|
||||
export:windows|export:macos|export:linux|export:android|export:ios)
|
||||
cmd_export_single "${COMMAND#export:}" "$@"; exit $? ;;
|
||||
|
||||
# ── Export ───────────────────────────────────────────────────────
|
||||
export) cmd_export "$@" ;;
|
||||
export:windows) cmd_export_single windows "$@" ;;
|
||||
export:macos) cmd_export_single macos "$@" ;;
|
||||
export:linux) cmd_export_single linux "$@" ;;
|
||||
export:android) cmd_export_single android "$@" ;;
|
||||
export:ios) cmd_export_single ios "$@" ;;
|
||||
# Install dispatches through a shared args parser.
|
||||
install:osx|install:macos) _dispatch_install osx "$@"; exit $? ;;
|
||||
install:linux) _dispatch_install linux "$@"; exit $? ;;
|
||||
install:iphone|install:ios) _dispatch_install iphone "$@"; exit $? ;;
|
||||
install:sim) _dispatch_install sim "$@"; exit $? ;;
|
||||
install:android) _dispatch_install android "$@"; exit $? ;;
|
||||
|
||||
# ── Install (colon form — canonical) ────────────────────────────
|
||||
install:osx|install:macos) _dispatch_install osx "$@" ;;
|
||||
install:linux) _dispatch_install linux "$@" ;;
|
||||
install:iphone|install:ios) _dispatch_install iphone "$@" ;;
|
||||
install:sim) _dispatch_install sim "$@" ;;
|
||||
install:android) _dispatch_install android "$@" ;;
|
||||
# Aliases so `install:macos` → `install:osx` (etc.) dispatch to the canonical fn.
|
||||
start:macos) cmd_start_osx "$@"; exit $? ;;
|
||||
stop:macos) cmd_stop_osx "$@"; exit $? ;;
|
||||
smoke:macos) cmd_smoke_osx "$@"; exit $? ;;
|
||||
start:iphone) cmd_start_ios "$@"; exit $? ;;
|
||||
|
||||
# ── Start / Stop / Smoke (colon form — canonical) ───────────────
|
||||
start:osx|start:macos) cmd_start_osx "$@" ;;
|
||||
start:linux) cmd_start_linux "$@" ;;
|
||||
start:ios|start:iphone) cmd_start_ios "$@" ;;
|
||||
stop:osx|stop:macos) cmd_stop_osx "$@" ;;
|
||||
stop:linux) cmd_stop_linux "$@" ;;
|
||||
smoke:osx|smoke:macos) cmd_smoke_osx "$@" ;;
|
||||
smoke:linux) cmd_smoke_linux "$@" ;;
|
||||
|
||||
# ── Tools (colon form — canonical) ──────────────────────────────
|
||||
tools:spritegen) cmd_tools_spritegen "$@" ;;
|
||||
|
||||
# ── Legacy space-form aliases (for muscle memory — undocumented)
|
||||
# install|start|stop|smoke|tools all accept a positional subtarget.
|
||||
# Legacy space-form aliases: `install osx --dev` → `install:osx --dev`
|
||||
install)
|
||||
TARGET=""; POS=()
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dev) POS+=("$arg") ;;
|
||||
osx|macos|linux|iphone|ios|sim|android) TARGET="$arg" ;;
|
||||
*) POS+=("$arg") ;;
|
||||
*) POS+=("$arg") ;;
|
||||
esac
|
||||
done
|
||||
[ -n "$TARGET" ] || { echo -e "${RED}install requires a target (use install:<target>)${NC}"; exit 1; }
|
||||
_dispatch_install "${TARGET/macos/osx}" "${POS[@]+"${POS[@]}"}"
|
||||
exit $?
|
||||
;;
|
||||
start|stop|smoke)
|
||||
VERB="$COMMAND"
|
||||
TARGET="${1:-}"; shift 2>/dev/null || true
|
||||
start|stop|smoke|tools)
|
||||
VERB="$COMMAND"; TARGET="${1:-}"; shift 2>/dev/null || true
|
||||
[ -n "$TARGET" ] || { echo -e "${RED}$VERB requires a target (use $VERB:<target>)${NC}"; exit 1; }
|
||||
# Recurse using canonical colon form
|
||||
exec "$0" "$VERB:$TARGET" "$@"
|
||||
;;
|
||||
tools)
|
||||
SUB="${1:-}"; shift 2>/dev/null || true
|
||||
[ -n "$SUB" ] || { echo -e "${RED}tools requires a subcommand (use tools:<sub>)${NC}"; exit 1; }
|
||||
exec "$0" "tools:$SUB" "$@"
|
||||
;;
|
||||
|
||||
# ── Misc ─────────────────────────────────────────────────────────
|
||||
setup) cmd_setup "$@" ;;
|
||||
setup:bluefin) cmd_setup_bluefin "$@" ;;
|
||||
help|--help|-h|"") usage ;;
|
||||
*) echo -e "${RED}Unknown command: $COMMAND${NC}"; echo ""; usage; exit 1 ;;
|
||||
esac
|
||||
|
||||
# Data-driven dispatch: `<verb>:<target>` → `cmd_<verb>_<target>`, `<verb>` → `cmd_<verb>`.
|
||||
# New subcommands don't require editing this file — just define `cmd_<name>` in
|
||||
# any `scripts/run/*.sh` (or subcommand alias in `scripts/dev-setup/`).
|
||||
FUNC="cmd_${COMMAND//[:-]/_}"
|
||||
if declare -F "$FUNC" >/dev/null; then
|
||||
"$FUNC" "$@"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
echo -e "${RED}Unknown command: $COMMAND${NC}"
|
||||
echo ""
|
||||
usage
|
||||
exit 1
|
||||
|
|
|
|||
45
scripts/README.md
Normal file
45
scripts/README.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# `scripts/` — repo automation
|
||||
|
||||
Everything in this directory is either sourced by `./run` or invoked
|
||||
directly over SSH on a remote host (apricot, plum). Every `*.sh` is
|
||||
idempotent and safe to re-run.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
scripts/
|
||||
run/ — ./run <cmd> dispatch modules; one file per concern
|
||||
common.sh — colors, dotenv loader, $GAME_DIR/$SIMULATOR_DIR/$GUIDE_DIR
|
||||
build.sh — cmd_build, cmd_build_wasm, cmd_build_gdext, cmd_build_info
|
||||
build-info.sh — git-state → src/game/build_info.json generator
|
||||
dev.sh — lint/format/test/verify/autoplay subcommands
|
||||
export.sh — Godot export one-target dispatch
|
||||
remote.sh — install:/start:/stop:/smoke: over SSH
|
||||
tools.sh — setup + spritegen + misc one-offs
|
||||
dev-setup/ — one-shot env bootstrap scripts, per-OS
|
||||
osx.sh — macOS (Homebrew + Godot + Rust + --with-runner)
|
||||
linux.sh — generic Linux (dnf/apt + Rust + --with-runner)
|
||||
bluefin.sh — rpm-ostree Bluefin layer (weston, vulkan-tools)
|
||||
lib/ — helpers shared across the per-OS scripts
|
||||
runner.sh — forgejo-runner install/register/verify
|
||||
autoplay/ — runs ON a linux host (apricot) for headless batches
|
||||
run_ap3.sh — weston-headless flatpak Godot invocation
|
||||
run_seeded.sh — single-seed AUTO_PLAY wrapper
|
||||
test_save_resume.sh — save-at-T50 → resume → compare test harness
|
||||
```
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Functions prefixed with `_`** are private helpers scoped to one
|
||||
file. Functions without the prefix are callable from other modules.
|
||||
- **`cmd_<verb>_<target>`** functions are dispatched by `./run` via
|
||||
name-matching — `./run verb:target` runs `cmd_verb_target`. No need
|
||||
to edit the top-level `run` case block to add a new subcommand.
|
||||
- **Direct execution works too** — every `scripts/run/*.sh` and
|
||||
`scripts/dev-setup/*.sh` has a working shebang and `if [[ "${BASH_SOURCE[0]}" == "$0" ]]` guard where relevant.
|
||||
- **Remote scripts** under `autoplay/` are *meant* to be SSH-invoked
|
||||
from the EDIT host (typically plum) running on the RUN host
|
||||
(typically apricot). Never invoke them on the EDIT host directly.
|
||||
- **Env files** — `.env` (tracked base) → `.env.local` (user secrets,
|
||||
gitignored) → `.env.<mode>` → `.env.<mode>.local`. Loaded automatically
|
||||
by `common.sh` at source time. See `.env.example` for documented keys.
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
# Fail: any game crashes, save missing, or turn_stats differ
|
||||
#
|
||||
# Usage (from repo root, apricot must be SSH-reachable):
|
||||
# AUTOPLAY_HOST=lilith@apricot.local bash scripts/apricot/test_save_resume.sh [seed]
|
||||
# AUTOPLAY_HOST=lilith@apricot.local bash scripts/autoplay/test_save_resume.sh [seed]
|
||||
#
|
||||
# Without AUTOPLAY_HOST, runs locally via flatpak (Linux only).
|
||||
set -euo pipefail
|
||||
|
|
@ -1,24 +1,40 @@
|
|||
#!/usr/bin/env bash
|
||||
# Regenerates src/game/build_info.json from the current git state.
|
||||
# Invoke before an export so the About screen shows the correct commit.
|
||||
# Usage: ./scripts/run/build-info.sh
|
||||
#
|
||||
# Available two ways:
|
||||
# * Directly: `./scripts/run/build-info.sh`
|
||||
# * Via runner: `./run build:info` (calls cmd_build_info below)
|
||||
#
|
||||
# Previously this file was top-level executable code — sourcing it via
|
||||
# the runner's `for _script in scripts/run/*.sh; source "$_script"` loop
|
||||
# regenerated build_info.json on EVERY `./run <anything>` invocation,
|
||||
# including quick ones like `./run lint:gd`. Wrapping in a function makes
|
||||
# the write lazy: only runs when explicitly called by a build/export cmd.
|
||||
|
||||
set -euo pipefail
|
||||
# Regenerate src/game/build_info.json. Idempotent, side-effect-free at source time.
|
||||
cmd_build_info() {
|
||||
local repo_root="${REPO_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
||||
local commit build_date version_base version
|
||||
commit="$(git -C "$repo_root" rev-parse --short=12 HEAD 2>/dev/null || echo unknown)"
|
||||
build_date="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
version_base="$(awk -F'"' '/^config\/version=/ {print $2}' "$repo_root/src/game/project.godot" 2>/dev/null || echo 0.0.0)"
|
||||
version="${version_base}-ea"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
COMMIT="$(git -C "$REPO_ROOT" rev-parse --short=12 HEAD 2>/dev/null || echo unknown)"
|
||||
BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
VERSION_BASE="$(awk -F'"' '/^config\/version=/ {print $2}' "$REPO_ROOT/src/game/project.godot" 2>/dev/null || echo 0.0.0)"
|
||||
VERSION="${VERSION_BASE}-ea"
|
||||
|
||||
cat > "$REPO_ROOT/src/game/build_info.json" <<EOF
|
||||
cat > "$repo_root/src/game/build_info.json" <<EOF
|
||||
{
|
||||
"version": "$VERSION",
|
||||
"commit": "$COMMIT",
|
||||
"build_date": "$BUILD_DATE",
|
||||
"version": "$version",
|
||||
"commit": "$commit",
|
||||
"build_date": "$build_date",
|
||||
"godot_rust": "0.2"
|
||||
}
|
||||
EOF
|
||||
echo "Wrote build_info.json: $VERSION @ $COMMIT"
|
||||
echo "Wrote build_info.json: $version @ $commit"
|
||||
}
|
||||
|
||||
# Keep the direct-invocation path working (`./scripts/run/build-info.sh`).
|
||||
# BASH_SOURCE[0] == $0 exactly when the file is executed, not sourced.
|
||||
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
set -euo pipefail
|
||||
cmd_build_info
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ cmd_build_gdext() {
|
|||
|
||||
cmd_build() {
|
||||
local exit_code=0
|
||||
cmd_build_info || exit_code=$?
|
||||
echo ""
|
||||
cmd_build_wasm || exit_code=$?
|
||||
echo ""
|
||||
cmd_build_gdext || exit_code=$?
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
# Variables already set in the shell ALWAYS win (never clobber). Lines
|
||||
# are plain `KEY=VALUE`; inline comments (`#`) are stripped; surrounding
|
||||
# single/double quotes on values are stripped.
|
||||
load_envfile() {
|
||||
_load_envfile() {
|
||||
local f="$1"
|
||||
[[ -r "$f" ]] || return 0
|
||||
local line key val
|
||||
|
|
@ -36,12 +36,12 @@ load_envfile() {
|
|||
done < "$f"
|
||||
}
|
||||
|
||||
load_env_cascade() {
|
||||
_load_env_cascade() {
|
||||
local mode="${NODE_ENV:-development}"
|
||||
load_envfile "$REPO_ROOT/.env"
|
||||
load_envfile "$REPO_ROOT/.env.local"
|
||||
load_envfile "$REPO_ROOT/.env.${mode}"
|
||||
load_envfile "$REPO_ROOT/.env.${mode}.local"
|
||||
_load_envfile "$REPO_ROOT/.env"
|
||||
_load_envfile "$REPO_ROOT/.env.local"
|
||||
_load_envfile "$REPO_ROOT/.env.${mode}"
|
||||
_load_envfile "$REPO_ROOT/.env.${mode}.local"
|
||||
}
|
||||
|
||||
# Require one or more env vars; fail the current command with a helpful
|
||||
|
|
@ -60,7 +60,7 @@ require_env() {
|
|||
}
|
||||
|
||||
# Run cascade at source time so every subcommand has vars available.
|
||||
load_env_cascade
|
||||
_load_env_cascade
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"version": "0.1.0-ea",
|
||||
"commit": "79e7351e4084",
|
||||
"build_date": "2026-04-17T19:13:46Z",
|
||||
"commit": "f5ec1c0101b6",
|
||||
"build_date": "2026-04-17T19:22:13Z",
|
||||
"godot_rust": "0.2"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue