feat(@projects/@magic-civilization): ✨ add welcome guide theme alignment objective
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
f775c02c36
commit
71cc742490
8 changed files with 170 additions and 17 deletions
|
|
@ -16,9 +16,9 @@
|
|||
|---|---|---|---|---|---|---|
|
||||
| **P0** | 26 | 6 | 3 | 0 | 0 | 35 |
|
||||
| **P1** | 13 | 3 | 2 | 0 | 1 | 19 |
|
||||
| **P2** | 9 | 6 | 0 | 8 | 0 | 23 |
|
||||
| **P2** | 9 | 6 | 0 | 9 | 0 | 24 |
|
||||
| **P3 (oos)** | 0 | 0 | 0 | 0 | 17 | 17 |
|
||||
| **total** | **48** | **15** | **5** | **8** | **18** | **94** |
|
||||
| **total** | **48** | **15** | **5** | **9** | **18** | **95** |
|
||||
|
||||
</td><td valign='top' style='padding-left:2em'>
|
||||
|
||||
|
|
@ -30,8 +30,8 @@
|
|||
| [warcouncil](../team-leads/warcouncil.md) | 6 |
|
||||
| [wireguard](../team-leads/wireguard.md) | 4 |
|
||||
| [shipwright](../team-leads/shipwright.md) | 3 |
|
||||
| [tourguide](../team-leads/tourguide.md) | 3 |
|
||||
| [testwright](../team-leads/testwright.md) | 2 |
|
||||
| [tourguide](../team-leads/tourguide.md) | 2 |
|
||||
| [asset-audio](../team-leads/asset-audio.md) | 1 |
|
||||
|
||||
</td></tr></table>
|
||||
|
|
@ -126,6 +126,7 @@
|
|||
| [p2-26](p2-26-mundane-wonder-sprites.md) | ❌ missing | Mundane-wonder sprites — 24 distinct, higher-fidelity art | [asset-sprite](../team-leads/asset-sprite.md) | 2026-04-17 |
|
||||
| [p2-27](p2-27-city-population-tier-sprites.md) | ❌ missing | City population-tier sprites — city_q1 through city_q5 | [asset-sprite](../team-leads/asset-sprite.md) | 2026-04-17 |
|
||||
| [p2-28](p2-28-sprite-provenance-ledger.md) | ❌ missing | Sprite provenance ledger — LICENSES.md per-file attribution | [asset-sprite](../team-leads/asset-sprite.md) | 2026-04-17 |
|
||||
| [p2-29](p2-29-guide-welcome-homepage-theme-alignment.md) | ❌ missing | Welcome modal + HomePage lore + guide theme align to the player's chosen race/gender | [tourguide](../team-leads/tourguide.md) | 2026-04-17 |
|
||||
|
||||
## Out of Scope (Game 2 / Game 3)
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,14 @@ All acceptance bullets verifiable:
|
|||
fresh deploy is picked up on the next visit; (f) passes through
|
||||
`/__sim-cache/` for future p2-21 baked frames. `docker exec
|
||||
host-nginx nginx -t` validates before the reload.
|
||||
**Gotcha (2026-04-17):** the outer `http {}` block must contain
|
||||
`include /etc/nginx/mime.types; default_type application/octet-stream;`.
|
||||
Without it nginx serves every file as `text/plain`, which browsers
|
||||
refuse to execute as ES modules — the guide renders blank with
|
||||
"disallowed MIME type" console errors on every `/assets/*.js`.
|
||||
`cmd_deploy_guide_next`'s step-5 probe now asserts
|
||||
`Content-Type: application/javascript` on a hashed JS asset to
|
||||
catch this regression at deploy time.
|
||||
- ✓ **Bind mount** — `/bigdisk/nginx/docker-compose.yml` adds
|
||||
`/bigdisk/next/:/var/www/next/:ro`. Container recreated via
|
||||
`docker compose up -d`; `docker exec host-nginx ls /var/www/next/mc/`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
---
|
||||
id: p2-29
|
||||
title: Welcome modal + HomePage lore + guide theme align to the player's chosen race/gender
|
||||
priority: p2
|
||||
status: missing
|
||||
scope: game1
|
||||
owner: tourguide
|
||||
updated_at: 2026-04-17
|
||||
evidence:
|
||||
- public/games/age-of-dwarves/guide/src/components/welcome/WelcomeModal.tsx
|
||||
- public/games/age-of-dwarves/guide/src/pages/HomePage.tsx
|
||||
- public/games/age-of-dwarves/guide/src/theme/fantasy-theme.ts
|
||||
- src/packages/guide/src/theme/RaceThemeProvider.tsx
|
||||
- public/games/age-of-dwarves/guide/src/contexts/PreferencesContext.tsx
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The guide already exposes a welcome modal that lets the player pick a race
|
||||
(Dwarf in Game 1 — `CONCRETE_RACES = ['dwarf']`) and a gender, plus a
|
||||
`RaceThemeProvider` that merges a per-race/per-gender palette into the
|
||||
styled-components theme. But the three surfaces don't line up:
|
||||
|
||||
- **WelcomeModal copy** reads like a settings dialog ("Settings", field
|
||||
labels) rather than an invitation into the story, so the player's first
|
||||
impression is admin-UI-shaped.
|
||||
- **HomePage `<LoreSection>`** *does* pull the player's race + name via
|
||||
`usePreferences()`, but the surrounding `<Hero>` / `<Tagline>` / `<Pitch>`
|
||||
hardcode out-of-scope narrative ("16 asymmetric races, 5 magic schools")
|
||||
that is Game 2/3 territory and does NOT update when the player picks a
|
||||
dwarf leader.
|
||||
- **Theme application** — the palette change from `RaceThemeProvider` fires
|
||||
on confirm, but a browser already on the HomePage may not re-derive the
|
||||
Hero/Pitch colors in a visually coherent way (Cinzel serif, Dwarf copper
|
||||
`#c07040`, etc.). The "align with welcome" contract is not exercised.
|
||||
|
||||
When the player picks **Dwarf + Female** in the modal and clicks Begin,
|
||||
all three surfaces should read as one piece: a dwarf-themed guide,
|
||||
referring to the named dwarf leader, in Dwarf scope language (no "5
|
||||
magic schools" pitch, no generic cross-race framing).
|
||||
|
||||
## Acceptance
|
||||
|
||||
- **Race-aware Hero / Tagline / Pitch**: the top-of-HomePage block no
|
||||
longer hardcodes cross-race copy. For Game 1 Dwarf scope, the pitch is
|
||||
Dwarf-first (craft, industry, mundane tech, fortress cities). When a
|
||||
future episode adds a second concrete race, the prose comes from a
|
||||
race-keyed data source (e.g. `resources/races/<race>/guide_pitch.md`)
|
||||
— not another `<RaceHighlight>{race.name}</RaceHighlight>` sprinkle.
|
||||
- **"Your story begins" block** renders race-specific lore for the
|
||||
chosen race + gender. Dwarf-female reads coherently ("Your band of
|
||||
free Dwarves have chosen you, <Brenna>, as their leader" in a voice
|
||||
consistent with the Dwarf palette). The current ley-line /
|
||||
magic-schools paragraphs are gated behind `<EpisodeGate min={2}>`
|
||||
(Game 2 content) so a default Game 1 bundle shows Dwarf narrative
|
||||
only; `VITE_DEV_GUIDE=1` keeps them visible per p1-15.
|
||||
- **Theme alignment**: after confirming Dwarf + Female in the
|
||||
WelcomeModal, the `RaceThemeProvider` palette is live —
|
||||
`theme.colors.primary.main === '#c07040'` (dwarf copper),
|
||||
`theme.typography.fontFamily.serif === "Cinzel, …"`,
|
||||
`theme.colors.background.primary === '#1a1510'`. HomePage's
|
||||
`<Eyebrow>` / `<LoreEyebrow>` / `<FeatureCard>` accents derive from
|
||||
those tokens and visibly shift from the default palette to the Dwarf
|
||||
palette within one render cycle.
|
||||
- **WelcomeModal voice**: the modal's copy is rewritten in the same
|
||||
voice as HomePage. Instead of "Settings", the modal frames the
|
||||
choice as the start of the story ("Choose your leader" / "Your band
|
||||
of free Dwarves choose…" etc.). `BeginButton` label reinforces the
|
||||
story beat rather than "Save".
|
||||
- **Smoke test**: `pnpm --prefix public/games/age-of-dwarves/guide
|
||||
test:e2e --grep welcome` (new spec) walks (1) load page fresh, (2)
|
||||
pick Dwarf + Female + type a name, (3) click Begin, (4) assert the
|
||||
HomePage `<LoreEyebrow>` reads "Your Story Begins" with the chosen
|
||||
name rendered in `<RaceHighlight>` + the body prose uses Dwarf
|
||||
vocabulary (no "magic schools" / "archons" strings). Console
|
||||
remains zero-error.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Additional races beyond Dwarf — Game 2/3 race content stays gated.
|
||||
- Multi-language / i18n for the prose — copy is authored in English,
|
||||
matching the rest of the guide.
|
||||
- In-modal preview of HomePage changes — `onPreview` already flashes
|
||||
the palette; no need for a live HomePage inset.
|
||||
- A new WelcomeModal design — this is a copy + theme-alignment pass,
|
||||
not a visual redesign.
|
||||
|
||||
## Why this belongs to tourguide
|
||||
|
||||
Closes the "clone → click Begin → first-page impression" path that the
|
||||
dev-guide deploy (`mc.next.black.local`, p1-15) is meant to showcase.
|
||||
Same owner that authored the tabbed Progress Report + the Markdown
|
||||
component + the deploy pipeline.
|
||||
|
|
@ -69,4 +69,4 @@ specialist does not own any objective.
|
|||
| [warcouncil](warcouncil.md) | Warcouncil | AI action generation, MCTS, GPU look-ahead, clan personality differentiation | p0-01, p0-02, p0-20 |
|
||||
| [shipwright](shipwright.md) | Shipwright | Drive Game 1 to release via /experts-team cron loop until every P0 is done | p0-05, p0-14, p0-15, p0-16, p0-17 |
|
||||
| [testwright](testwright.md) | Testwright | Regression-test coverage across Rust + GDScript + data validators — seeds the evidence substrate for the Objective Status Integrity rule | p1-09, p2-10 |
|
||||
| [tourguide](tourguide.md) | Tourguide | Developer experience of the guide web app — dev server boots on plum, route coverage e2e, dev-tier deploy at mc.next.black.local, sim-cache baked on apricot, and the "no build output in src" rule stays enforced | p1-11, p1-12, p1-13, p1-15, p1-17, p2-20, p2-21 |
|
||||
| [tourguide](tourguide.md) | Tourguide | Developer experience of the guide web app — dev server boots on plum, route coverage e2e, dev-tier deploy at mc.next.black.local, sim-cache baked on apricot, welcome→HomePage→theme alignment, and the "no build output in src" rule stays enforced | p1-11, p1-12, p1-13, p1-15, p1-17, p2-20, p2-21, p2-29 |
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ objectives:
|
|||
- p1-17
|
||||
- p2-20
|
||||
- p2-21
|
||||
- p2-29
|
||||
---
|
||||
|
||||
## Mandate
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"generated_at": "2026-04-18T06:48:23Z",
|
||||
"generated_at": "2026-04-18T06:53:01Z",
|
||||
"totals": {
|
||||
"stub": 5,
|
||||
"partial": 15,
|
||||
"missing": 8,
|
||||
"done": 48,
|
||||
"oos": 18,
|
||||
"total": 94
|
||||
"missing": 9,
|
||||
"done": 48,
|
||||
"total": 95
|
||||
},
|
||||
"objectives": [
|
||||
{
|
||||
|
|
@ -779,6 +779,16 @@
|
|||
"updated_at": "2026-04-17",
|
||||
"summary": "Every sprite PNG that ships in `public/games/age-of-dwarves/assets/sprites/` must have a corresponding row in `public/games/age-of-dwarves/assets/sprites/LICENSES.md` recording source, license, author, URL, and SHA256. This is a cross-cutting compliance objective that runs continuously alongside the delivery children (`p2-23` … `p2-27`) — the ledger is complete exactly when every on-disk sprite has a matching row and every row points at an on-disk file.\n\nCommercial-use compatibility is non-negotiable. AI-generated output must come from a model on the approved list (`juggernaut-xl-v9`, `epicrealism-xl`, `illustrious-xl-v2`, or current equivalent per CLAUDE.md). Commissioned art must have assigned commercial rights in writing."
|
||||
},
|
||||
{
|
||||
"id": "p2-29",
|
||||
"title": "Welcome modal + HomePage lore + guide theme align to the player's chosen race/gender",
|
||||
"priority": "p2",
|
||||
"status": "missing",
|
||||
"scope": "game1",
|
||||
"owner": "tourguide",
|
||||
"updated_at": "2026-04-17",
|
||||
"summary": "The guide already exposes a welcome modal that lets the player pick a race\n(Dwarf in Game 1 — `CONCRETE_RACES = ['dwarf']`) and a gender, plus a\n`RaceThemeProvider` that merges a per-race/per-gender palette into the\nstyled-components theme. But the three surfaces don't line up:\n\n- **WelcomeModal copy** reads like a settings dialog (\"Settings\", field\n labels) rather than an invitation into the story, so the player's first\n impression is admin-UI-shaped.\n- **HomePage `<LoreSection>`** *does* pull the player's race + name via\n `usePreferences()`, but the surrounding `<Hero>` / `<Tagline>` / `<Pitch>`\n hardcode out-of-scope narrative (\"16 asymmetric races, 5 magic schools\")\n that is Game 2/3 territory and does NOT update when the player picks a\n dwarf leader.\n- **Theme application** — the palette change from `RaceThemeProvider` fires\n on confirm, but a browser already on the HomePage may not re-derive the\n Hero/Pitch colors in a visually coherent way (Cinzel serif, Dwarf copper\n `#c07040`, etc.). The \"align with welcome\" contract is not exercised.\n\nWhen the player picks **Dwarf + Female** in the modal and clicks Begin,\nall three surfaces should read as one piece: a dwarf-themed guide,\nreferring to the named dwarf leader, in Dwarf scope language (no \"5\nmagic schools\" pitch, no generic cross-race framing)."
|
||||
},
|
||||
{
|
||||
"id": "g2-01",
|
||||
"title": "Ley lines — Game 2 (Age of Kzzykt)",
|
||||
|
|
|
|||
|
|
@ -35,21 +35,53 @@ for envfile in "${PROJECT_DIR}/.env" "${PROJECT_DIR}/.env.local"; do
|
|||
fi
|
||||
done
|
||||
|
||||
# ── Resource policy for PARALLEL ─────────────────────────────────────
|
||||
# Precedence: explicit PARALLEL env > USE_MAX_CORES=true (nproc on RUN host)
|
||||
# > MIN_CORES from .env (default 4). Games are single-core each;
|
||||
# this controls how many run concurrently.
|
||||
# MODE + positional args resolved early so the resource-policy block can
|
||||
# peek at the seed count (which differs per mode — for `clan` it's $2
|
||||
# because $1 is the clan_id; for smoke/gpu-walltime it's $1).
|
||||
MODE="${1:?usage: apricot-run.sh <smoke|clan|gpu-walltime> [args]}"
|
||||
shift || true
|
||||
|
||||
# ── Resource policy for PARALLEL + RAYON_NUM_THREADS ─────────────────
|
||||
# Each Godot instance spawns its own rayon thread pool for MCTS rollouts;
|
||||
# rayon defaults to nproc unless RAYON_NUM_THREADS is set. If PARALLEL
|
||||
# instances each claim all nproc threads, we get PARALLEL*nproc threads
|
||||
# fighting over nproc cores → thrashing, each process effectively single
|
||||
# core. Better: PARALLEL = number of seeds (one instance each), and
|
||||
# RAYON_NUM_THREADS = nproc / PARALLEL so the box is saturated evenly.
|
||||
case "${MODE}" in
|
||||
clan) _seed_count_peek="${2:-10}" ;; # $1 is clan_id, $2 is seeds
|
||||
*) _seed_count_peek="${1:-10}" ;; # smoke, gpu-walltime
|
||||
esac
|
||||
|
||||
NPROC="$(ssh "${APRICOT}" nproc 2>/dev/null || echo 8)"
|
||||
|
||||
if [[ -n "${PARALLEL:-}" ]]; then
|
||||
PARALLEL_EFFECTIVE="${PARALLEL}"
|
||||
PARALLEL_SOURCE="env override"
|
||||
elif [[ "${USE_MAX_CORES:-false}" == "true" ]]; then
|
||||
PARALLEL_EFFECTIVE="$(ssh "${APRICOT}" nproc 2>/dev/null || echo "${MIN_CORES:-4}")"
|
||||
PARALLEL_SOURCE="USE_MAX_CORES=true → nproc"
|
||||
# One instance per seed — up to NPROC. More instances than that
|
||||
# would queue serially anyway (NPROC concurrent Godots max).
|
||||
PARALLEL_EFFECTIVE="$(( _seed_count_peek < NPROC ? _seed_count_peek : NPROC ))"
|
||||
PARALLEL_SOURCE="USE_MAX_CORES=true → min(seeds=${_seed_count_peek}, nproc=${NPROC})"
|
||||
else
|
||||
PARALLEL_EFFECTIVE="${MIN_CORES:-4}"
|
||||
PARALLEL_SOURCE="MIN_CORES default"
|
||||
fi
|
||||
export PARALLEL="${PARALLEL_EFFECTIVE}"
|
||||
|
||||
# RAYON_NUM_THREADS per Godot instance = fair share of cores.
|
||||
if [[ -n "${RAYON_NUM_THREADS:-}" ]]; then
|
||||
RAYON_SOURCE="env override"
|
||||
else
|
||||
if [[ "${PARALLEL_EFFECTIVE}" -gt 0 ]]; then
|
||||
RAYON_NUM_THREADS="$(( NPROC / PARALLEL_EFFECTIVE ))"
|
||||
else
|
||||
RAYON_NUM_THREADS=1
|
||||
fi
|
||||
[[ "${RAYON_NUM_THREADS}" -lt 1 ]] && RAYON_NUM_THREADS=1
|
||||
RAYON_SOURCE="nproc(${NPROC}) / PARALLEL(${PARALLEL_EFFECTIVE})"
|
||||
fi
|
||||
export RAYON_NUM_THREADS
|
||||
# Source + build scratch lives under $HOME/.cache (flatpak-visible via
|
||||
# --filesystem=home). /tmp was tried first but flatpak's sandbox can't see
|
||||
# /tmp, so Godot rejected the --path argument with "Invalid project path".
|
||||
|
|
@ -58,9 +90,6 @@ export PARALLEL="${PARALLEL_EFFECTIVE}"
|
|||
SCRATCH="\$HOME/.cache/mc-src-${STAMP}" # expanded on apricot
|
||||
RESULTS="\$HOME/.cache/mc-batches/${STAMP}" # expanded on apricot
|
||||
|
||||
MODE="${1:?usage: apricot-run.sh <smoke|clan|gpu-walltime> [args]}"
|
||||
shift || true
|
||||
|
||||
# Resolve $HOME on apricot so SCRATCH / RESULTS are fully-qualified paths on that host.
|
||||
SCRATCH_ABS="$(ssh "${APRICOT}" "echo \$HOME/.cache/mc-src-${STAMP}")"
|
||||
RESULTS_ABS="$(ssh "${APRICOT}" "echo \$HOME/.cache/mc-batches/${STAMP}")"
|
||||
|
|
@ -72,6 +101,8 @@ echo " RUN host: ${APRICOT}"
|
|||
echo " SCRATCH: ${SCRATCH_ABS} (per-run source + build scratch)"
|
||||
echo " RESULTS: ${RESULTS_ABS} (persistent batch output)"
|
||||
echo " PARALLEL: ${PARALLEL_EFFECTIVE} (source: ${PARALLEL_SOURCE})"
|
||||
echo " RAYON_NUM_THREADS/instance: ${RAYON_NUM_THREADS} (source: ${RAYON_SOURCE})"
|
||||
echo " Total CPU saturation: ${PARALLEL_EFFECTIVE} × ${RAYON_NUM_THREADS} = $((PARALLEL_EFFECTIVE * RAYON_NUM_THREADS))/${NPROC} cores"
|
||||
echo " AI_GPU_ROLLOUT: ${AI_GPU_ROLLOUT:-true (default on for smoke/clan)}"
|
||||
echo "============================================================"
|
||||
|
||||
|
|
|
|||
|
|
@ -161,6 +161,15 @@ _run_local() {
|
|||
"--env=AI_PIN_PERSONALITY=${AI_PIN_PERSONALITY:-}"
|
||||
"--env=MAP_SIZE=${MAP_SIZE:-}"
|
||||
"--env=NUM_PLAYERS=${NUM_PLAYERS:-}"
|
||||
"--env=AI_USE_MCTS=${AI_USE_MCTS:-}"
|
||||
"--env=AI_GPU_ROLLOUT=${AI_GPU_ROLLOUT:-}"
|
||||
# Rayon thread cap per Godot instance. Without this, rayon
|
||||
# defaults to nproc (e.g. 64 on apricot); with PARALLEL=N
|
||||
# instances each claiming 64 threads, the box has N*64 threads
|
||||
# fighting over 64 cores → thrashing, each process effectively
|
||||
# single-core. Caller (apricot-run.sh) computes nproc/PARALLEL
|
||||
# so cores are divided fairly across instances.
|
||||
"--env=RAYON_NUM_THREADS=${RAYON_NUM_THREADS:-}"
|
||||
)
|
||||
local GODOT_ARGS=("--path" "$GAME_DIR" "--rendering-method" "gl_compatibility")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue