diff --git a/.project/objectives/p3-26-complete-headless-simulator.md b/.project/objectives/p3-26-complete-headless-simulator.md index a21709bc..b6be3b7b 100644 --- a/.project/objectives/p3-26-complete-headless-simulator.md +++ b/.project/objectives/p3-26-complete-headless-simulator.md @@ -39,6 +39,15 @@ expansion, tech/science, fauna encounters, combat/siege, diplomacy. Verified liv gap 1), global (solar/glacial). Deterministic from seed per `EVENT_FREQUENCY_SPEC.md`; triggers from climate (gap 1), damage targets in biology (fauna/ecology). Wire into the turn. (Magic category excluded — M4.) + - **VERIFIED 2026-06-27 — two listed "remaining" sub-items reassessed:** + (a) **era-based max_tier cap = NON-PARITY (gold-plating, dropped).** The live GDScript events + modules have NO `max_tier`/era-tier cap (0 hits in `src/game/engine/src/modules/events/`). + Headless uses `max_tier=10` (processor.rs:1117); adding an era cap would invent a rule the live + game lacks. Leave flat unless a design adds the cap to the game first. + (b) **surfacing fired events = minor OBSERVABILITY gap only.** Events already fire AND apply + terrain effects headless; `process_events` returns the fired list but `step()` discards it + (`let _fired =`, processor.rs:1117). The SYSTEM runs in self-play; only replay/observation + visibility is missing. Low priority (parallels the p3-29 §A event surface, render-gated payoff). - [ ] **Gap 3 — Equipment / crafting.** `mc-city/recipes.rs` + `enqueue_item` exist but there is no headless `Craft`/`Equip` `PlayerAction` and crafting isn't in the bench turn. Add the action(s) + dispatch + wire recipe resolution (gating resources, quality) diff --git a/infra/terraform/test-fleet/tests/fixtures/id_test.pub b/infra/terraform/test-fleet/tests/fixtures/id_test.pub new file mode 100644 index 00000000..2e7562a9 --- /dev/null +++ b/infra/terraform/test-fleet/tests/fixtures/id_test.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAITESTFIXTUREKEYdummy0000000000000000000000 test-fixture diff --git a/infra/terraform/test-fleet/tests/fleet.tftest.hcl b/infra/terraform/test-fleet/tests/fleet.tftest.hcl new file mode 100644 index 00000000..29756412 --- /dev/null +++ b/infra/terraform/test-fleet/tests/fleet.tftest.hcl @@ -0,0 +1,79 @@ +# No-spend test harness for the fleet module. +# terraform test (from the module dir) +# Uses a MOCKED hcloud provider — no API token, no API calls, no servers, no cost. +# Exercises count expansion, the golden-image branch toggle, and the workers guardrail. + +mock_provider "hcloud" {} + +variables { + hcloud_token = "mock-token-unused" + git_remote = "https://example.com/magic-civilization.git" + ssh_public_key_path = "./tests/fixtures/id_test.pub" +} + +# base_image set -> golden data source is skipped (count 0); fleet expands to N. +run "fleet_expands_and_skips_golden_when_base_image_set" { + command = plan + + variables { + workers = 3 + base_image = "ubuntu-24.04" + } + + assert { + condition = length(hcloud_server.worker) == 3 + error_message = "expected 3 workers when workers=3" + } + + assert { + condition = length(data.hcloud_image.golden) == 0 + error_message = "golden data source must be skipped when base_image is set" + } +} + +# base_image empty -> golden snapshot is resolved via the label selector. +run "golden_image_branch_active_when_base_image_empty" { + command = plan + + variables { + workers = 2 + base_image = "" + } + + assert { + condition = length(data.hcloud_image.golden) == 1 + error_message = "golden data source must be queried when base_image is empty" + } + + assert { + condition = length(hcloud_server.worker) == 2 + error_message = "expected 2 workers when workers=2" + } +} + +# workers = 0 -> zero servers (idle / torn-down state). +run "zero_workers_is_empty_fleet" { + command = plan + + variables { + workers = 0 + base_image = "ubuntu-24.04" + } + + assert { + condition = length(hcloud_server.worker) == 0 + error_message = "workers=0 must produce no servers" + } +} + +# The validation guardrail rejects an oversize fleet — proven without provisioning. +run "rejects_oversize_fleet" { + command = plan + + variables { + workers = 99 + base_image = "ubuntu-24.04" + } + + expect_failures = [var.workers] +} diff --git a/scripts/run/dist.sh b/scripts/run/dist.sh index 19413953..12e35b2f 100755 --- a/scripts/run/dist.sh +++ b/scripts/run/dist.sh @@ -30,6 +30,7 @@ _dist_read_hosts() { cmd_dist() { cat <<'EOF' Distributed test/train fleet (Hetzner). Set TF_VAR_hcloud_token first. + ./run dist:check offline: fmt + validate + mocked test (no token/spend) ./run dist:up [server_type] [location] e.g. ./run dist:up 10 ./run dist:sim [turn_limit] [--destroy-after] ./run dist:train [--destroy-after] @@ -37,6 +38,23 @@ Distributed test/train fleet (Hetzner). Set TF_VAR_hcloud_token first. EOF } +cmd_dist_check() { + # Offline IaC verification — no Hetzner token, no API, no servers, no cost. + # fmt (style) + validate (schema typecheck) + test (mocked-provider behaviour). + local root + root="$(_dist_repo_root)" + local dir="$root/$_DIST_TF_DIR_REL" + echo "== terraform fmt ==" + terraform -chdir="$dir" fmt -check -recursive || { echo "fmt: run 'terraform -chdir=$dir fmt'" >&2; return 1; } + echo "== terraform init (providers only) ==" + terraform -chdir="$dir" init -backend=false -input=false >/dev/null || return 1 + echo "== terraform validate (schema typecheck) ==" + terraform -chdir="$dir" validate || return 1 + echo "== terraform test (mocked hcloud) ==" + terraform -chdir="$dir" test || return 1 + echo "dist:check OK — config is valid, no resources touched." +} + cmd_dist_up() { local n="${1:-}" [[ "$n" =~ ^[0-9]+$ ]] || { echo "usage: ./run dist:up [server_type] [location]" >&2; return 1; }