Nebula (Ember Reach) galaxy area background + `bh-area-background` skill
Nebula (Ember Reach) galaxy area background + bh-area-background skill
Section titled “Nebula (Ember Reach) galaxy area background + bh-area-background skill”Date: 2026-07-02
Add a third explorable area — display name “Nebula”, internal id ember_reach (chosen to avoid
colliding with the existing internal VARIANT_NEBULA name, which is an unrelated soft-glow-cloud
look already used in the Home random pool) — with a warm gold/amber/orange galaxy backdrop: a
spiral-armed core plus a sparser scattered star field filling the rest of the arena. Package the
process as a reusable local skill so future area backgrounds are faster to add correctly.
Areas are currently gated off for the v0.1 launch (main.V01_LOCK_AREAS = true) — this work adds
the area and its background but does not need to unlock exploration; it ships dark until the flag
flips, same as Aurora did.
Current system (confirmed by reading the code)
Section titled “Current system (confirmed by reading the code)”sim/area_defs.gd(AreaDefs, pure/simdata, no engine APIs): a static dictid → {name, difficulty_mult, reward_mult, background}.backgroundis a plain string — sim never touches rendering, it just names a look.main.gdreadsAreaDefs.get_def(_warp_dest)["background"]and callsArenaBackground.set_variant_by_name(name, seed).render/arena_background.gd(ArenaBackground):set_variant_by_namespecial-cases known area names to a dedicated variant constant; anything unrecognized falls into the random “Home pool” (VARIANT_GRID/VARIANT_STARFIELD/VARIANT_NEBULA, picked by a render-side seeded RNG). Aurora already works this way (VARIANT_AURORA = 3, animated ribbon bands, area-selected only — explicitly excluded from the random pool viaVARIANT_COUNT).- The scatter/placement data (star positions, nebula-cloud positions) is generated once per run in
set_variant()from a render-side seededRandomNumberGenerator, notsim.rng— background choice and appearance never affect the determinism baseline. AreaDefs.other(id)is currently a hard binary toggle (Aurora if id == Home else Home) for the two-way wormhole. With a third area this must become a real list/cycle.
Part 1 — the Nebula area + galaxy background
Section titled “Part 1 — the Nebula area + galaxy background”sim/area_defs.gd
Section titled “sim/area_defs.gd”- Add
const EMBER_REACH := "ember_reach". - Add a
_DEFSentry:{"name": "Nebula", "difficulty_mult": 1.3, "reward_mult": 1.25, "background": "ember_reach"}— deliberately between Home (1.0/1.0) and Aurora (1.6/1.5). - Replace
other(id)with a cycle across all non-Home areas (or a full ordered list) so adding a 4th area later doesn’t require touching the wormhole logic again. Exact shape decided during implementation planning — keep it a pure data function, no behavior change for Home↔Aurora.
render/arena_background.gd
Section titled “render/arena_background.gd”- Add
const VARIANT_GALAXY := 4(area-selected only, excluded fromVARIANT_COUNT’s random pool — same treatment asVARIANT_AURORA). set_variant_by_name: add an"ember_reach"branch →set_variant(VARIANT_GALAXY, seed_val).set_variant(): onVARIANT_GALAXY, generate two star sets from the seeded RNG:- Spiral core — 2–3 arms via a logarithmic-spiral parametric curve
(
r = a * exp(b * theta)), walkingthetaoutward per arm with per-star angle/radius jitter so it reads as a dense star cloud shaped like a spiral, not a printed curve. Reuses the same per-star record shape as the existing starfield (p,r,a,phase,tw) so the existing twinkle animation in_process/_drawworks unchanged. - Scattered field — a sparser uniform scatter across the rest of the arena, generated the
same way as
VARIANT_STARFIELDtoday (lower count than the core). Store both in_stars(or a second array if keeping them visually distinct — e.g. brighter/larger near the core — is easier that way); decide during implementation based on what reads best.
- Spiral core — 2–3 arms via a logarithmic-spiral parametric curve
(
_draw(): add aVARIANT_GALAXYbranch drawing the star sets with a warm palette — core near white-gold fading through amber to deep orange in the outer arms (exact hex values tuned visually during implementation, e.g. coreColor(1.0, 0.95, 0.75), arms ranging towardColor(0.85, 0.35, 0.05)). Keep the same additive blend + twinkle approach asVARIANT_STARFIELD.- Update the
VARIANT_COUNT/wrap comment at the top of the file to note the galaxy variant, same as the existing Aurora note. - No
/simchanges beyondarea_defs.gd; no determinism impact (render-side RNG only, same as every other variant).
Verification
Section titled “Verification”Custom _draw() painting and per-instance colors can’t be read back headless (established gotcha:
headless MultiMesh/canvas readback returns dummy values). Verify visually:
- A temporary windowed check (same pattern as
tools/ship_preview, or a throwaway_shot.gd-style scene) renderingArenaBackgroundalone atVARIANT_GALAXYto confirm the spiral reads as a galaxy and the palette looks right before wiring it intomain.gd. - Then boot-check + full suite + determinism baseline per
bh-dev-chunk, expecting the baseline to be unchanged (render-only).
Part 2 — bh-area-background skill
Section titled “Part 2 — bh-area-background skill”New local skill at .claude/skills/bh-area-background/SKILL.md (gitignored, project-only — matches
the existing bh-dev-chunk / bh-deploy / bh-appstore-release convention). Same terse,
checklist-driven format as bh-dev-chunk.
Checklist it documents, generalized from this exact task:
- Naming gotcha up front: pick an internal variant id that does NOT collide with any existing
VARIANT_*name or area id — the visible in-game name can differ from the internal id (as withember_reach→ “Nebula”). Greprender/arena_background.gdandsim/area_defs.gdfor existing names before choosing one. sim/area_defs.gd: add the id const +_DEFSentry (name, difficulty_mult, reward_mult between existing areas unless told otherwise,backgroundstring). Updateother()/any area-cycling logic if the area count changed. Stays pure/simdata — no engine APIs.render/arena_background.gd: add a newVARIANT_*const, EXCLUDE it from the randomVARIANT_COUNTpool if it’s area-selected-only (the norm — Aurora and Galaxy both are), add theset_variant_by_namebranch, add scatter/placement generation inset_variant()seeded from the render-side RNG (neversim.rng), add the_draw()branch for the new look, reuse the existing per-star record shape + twinkle/pulse animation pattern where the new look is star-based.- Verify visually — headless can’t read back custom
_draw()painting; use a windowed throwaway scene/harness to confirm the look before wiring intomain.gd. - Wire into
main.gdwhere the area is entered (arena_bg.set_variant_by_name(...)) if not already generic (it already is — nomain.gdchange needed unless the entry point logic itself changes). - Full
bh-dev-chunkgate: boot-check, full suite + count guard, determinism baseline (expect no change — render-only), commit. Sync to tvOS perbh-dev-chunk§7 if shipping to device. - Not covered by this skill: enemy rosters, difficulty tuning depth, or unlocking
V01_LOCK_AREAS— this skill is scoped to “area identity + its backdrop” only.
Out of scope
Section titled “Out of scope”- Unlocking areas for players (
V01_LOCK_AREASstaystrue; this ships dark like Aurora did). - New enemies/mechanics specific to Nebula — v1 areas share the same enemy roster per
area_defs.gd’s existing comment (“same enemy roster, no bespoke enemies yet”). - Web demo / site changelog updates (areas are locked off, nothing visible changes publicly).