Start Menu Carousel — design
Start Menu Carousel — design
Section titled “Start Menu Carousel — design”Restyle the start menu’s mode picker as a carousel (reusing the shop’s established
hologram-carousel visual/interaction language), remove the inline ship-picker row from
the start menu to declutter it, and turn ShipConfigPanel’s single-ship display into a
carousel you can page left/right through to choose a ship, when opened pre-run.
Context
Section titled “Context”The start menu (ui/start_menu.gd) currently shows a vertical VBoxContainer list of
mode cards (collapsed to a single “SURVIVAL” card while V01_CRYSTALS_ONLY = true),
followed by a HBoxContainer row of 4 static ship-picker tiles (_make_ship_button),
then footer buttons including “Ship” which opens ui/ship_config_panel.gd —
ShipConfigPanel today only ever displays meta.selected_ship (the ship already picked
on the row above it); there’s no way to switch ships from inside that panel.
ui/meta_shop_panel.gd already has a shipped, validated carousel (design:
docs/superpowers/specs/2026-07-02-shop-carousel-hologram-design.md) — hologram-bracket
cards, one centered/full-scale item with dimmed/scaled-down neighbors, live-follow
drag + tween-on-release, MenuNav.is_left/right D-pad stepping. That carousel is not a
shared/extracted component (confirmed by reading the file) — it’s private state and
methods on MetaShopPanel itself, ported there from a tools/shop_lab/ prototype rather
than pulled out into a reusable class. This design follows the same precedent: port the
pattern into start_menu.gd and ship_config_panel.gd independently, don’t extract a
shared carousel class. Three carousels with copy-adapted logic is consistent with this
codebase’s existing bias toward one-off ports over speculative abstraction, and avoids
inventing a generic data-source/delegate interface no other caller needs yet.
Non-goals
Section titled “Non-goals”- No change to
ShopCategories,MetaState,ShipBonuses, or any upgrade/loadout data model — this is a rendering/navigation change to two screens, not a data change. - No change to
meta_shop_panel.gditself — it’s the reference pattern, not something this work touches. - No ship-switching from the pause-menu path into
ShipConfigPanel(sim != null). Switching hulls mid-run isn’t a mechanic this game has (ShipBonuses.apply_base()only runs at_new_run()), so the carousel/left-right paging is gated to the pre-run (sim == null) case only. Mid-run, the panel keeps today’s exact behavior: a single static ship + live weapon/drone loadout, no paging, no left/right input handled. - No change to the mode-picker’s actual mode list,
V01_CRYSTALS_ONLYgating, or which modes exist — purely how the (currently 1, later up to 4) mode cards are laid out and navigated. - Not reintroducing anything from the reverted “glassy ambient”/Gemini-glass-texture shop restyles mentioned in the shop-carousel design — hologram brackets only.
Design
Section titled “Design”1. Mode picker → carousel (ui/start_menu.gd)
Section titled “1. Mode picker → carousel (ui/start_menu.gd)”Replace the VBoxContainer of full-width mode cards with a hologram-bracket carousel,
following the shop’s pattern: one centered mode card at CENTER_SCALE, immediate
neighbors dimmed/scaled at SIDE_SCALE/SIDE_ALPHA, positions computed fresh each frame
from get_viewport().get_visible_rect().size() (not hardcoded — this is the exact bug
the shop carousel had to fix for real device aspect ratios). With V01_CRYSTALS_ONLY
still true there’s only one card, so it always renders center-only (mirrors the shop
carousel’s existing 1-item fallback) — no special-casing needed for “only one mode”
beyond what the ported pattern already handles. Card content (title, description, accent
color, leading _AccentDot) is unchanged; only the container/positioning/nav changes.
MenuNav.is_left/right steps the carousel (debounced, NAV_DEBOUNCE_MS = 200, same as
today); touch drag and tap-a-side-card behave as in the shop carousel; confirm
(ui_accept/JOY_BUTTON_A/tap-center) calls _choose(mode) exactly as today — activation
logic is unchanged, only how the centered item is determined changes (carousel index,
not _sel grid focus).
2. Remove the inline ship-picker row (ui/start_menu.gd)
Section titled “2. Remove the inline ship-picker row (ui/start_menu.gd)”Delete _make_ship_button, the ship_row/ship_label construction, and _pick_ship()
from start_menu.gd entirely — ship selection moves to ShipConfigPanel (below). The
“Ship” footer button (ship_config_requested signal) is unchanged; it’s now the only
way to reach ship selection, from either the start menu or the pause menu.
3. ShipConfigPanel becomes carousel-capable, pre-run only
Section titled “3. ShipConfigPanel becomes carousel-capable, pre-run only”open_config(meta, sim) already receives sim (null pre-run, live mid-run) — gate the
new paging behavior on sim == null:
- Pre-run (
sim == null): the centered ship becomes pageable. Left/right (MenuNav.is_left/right, touch drag, tap-a-side-ship) cycles throughShipBonuses.ORDERthe same way the start menu’s old row did, updating_ship_thumb,_title_lbl,_bonus_lbl, the weapon-slot ring (_weapon_cards, still built fromShipBonuses.weapon_slots_for(ship_id)with all-empty-but-Blade views since there’s no live sim), and locked/unlocked state (dimmed + “LOCKED” tag, ported from the old_make_ship_button’s locked treatment) — all per-ship content already exists today, this just re-triggers building it for whichevership_idis centered instead of only evermeta.selected_ship. Confirming the centered ship (ui_accept/tap) calls the existing_pick_ship-equivalent persistence path (meta.selected_ship = ship_id; MetaStore.save_state(meta), ported from the deletedstart_menu.gdmethod) and stays open on that ship (browsing further is still possible; “Back” is the only way to leave). - Mid-run (
sim != null): unchanged from today — one ship (whichever is already equipped), live weapon/drone data, no left/right handling, no locked-ship dimming/paging.
Given this panel isn’t a multi-item swarm of small tiles (it’s one large hero ship with a
ring of weapon cards around it), the carousel treatment here means paging the centered
hero display itself — not shrinking to a 3-up hologram-bracket layout like the shop or
mode picker. Side neighbors are NOT rendered as visible dimmed ships (there’s no room
once the weapon ring is laid out around the center); the paging animation is a
cross-fade/slide of the whole hero+ring composition between ship A and ship B, reusing
the shop carousel’s ANIM_DURATION = 0.3/Tween.TRANS_CUBIC timing for consistency, not
its 3-up slot layout.
4. Navigation summary
Section titled “4. Navigation summary”| Screen | Confirm | Left/Right | Back |
|---|---|---|---|
| Mode carousel (start menu) | _choose(mode) (unchanged) |
steps carousel | n/a (footer buttons only) |
Ship carousel (pre-run ShipConfigPanel) |
persist meta.selected_ship, stay open |
pages to next/prev ship | close() (unchanged) |
Ship detail (mid-run ShipConfigPanel) |
n/a | no-op (not handled) | close() (unchanged) |
Testing
Section titled “Testing”Start menu — kept: test_shop_entry_emits_shop_requested (footer button behavior,
untouched by the carousel change). Removed/rewritten: any assertion that walks
_cards/box.get_children() expecting a flat vertical list of mode cards followed by a
ship-tile row (none currently exist beyond the one kept test, per tests/test_start_menu.gd
being only 12 lines today — this file grows, it isn’t gutting an existing suite). New:
carousel center/side scale+alpha for the mode list (mirroring the shop carousel’s own
carousel tests), MenuNav.is_left/right steps it, confirm activates the centered mode,
1-item case renders center-only without erroring, no ship-picker nodes exist anywhere in
the built tree.
ShipConfigPanel — kept exactly as-is (all assert real behavior this design doesn’t
touch): test_open_pre_run_shows_default_hull_weapon_slots_only_blade_owned,
test_config_panel_card_count_follows_hull_weapon_slots,
test_open_mid_run_shows_live_owned_weapons, test_close_emits_closed_and_hides,
test_shows_the_selected_ships_name_and_bonus, test_drone_row_shows_all_eventual_slots,
test_open_config_shows_the_current_level. New: pre-run left/right pages to the next/prev
ShipBonuses.ORDER entry and rebuilds thumb/title/bonus/weapon-ring/drone-row for it;
paging wraps at both ends; confirming a centered locked ship does NOT persist
meta.selected_ship (mirrors the old _pick_ship guard); confirming an owned ship DOES
persist it and calls MetaStore.save_state; mid-run (sim != null) left/right input is a
no-op — _weapon_cards/_ship_thumb don’t change and no ship switches.
Rollout
Section titled “Rollout”Render/UI-only change across two files (ui/start_menu.gd, ui/ship_config_panel.gd),
no /sim files touched — the pinned determinism baseline should hold by construction,
verify per the standard bh-dev-chunk ritual anyway. Given both screens are used
identically across iPhone, iPad, Apple TV, and Mac, and the shop carousel needed a real
on-device pass to catch its centering bug, include a real-device check (Mac export at
minimum, bh-deploy to the Apple TV if this session’s cadence continues) before calling
this done — not just a headless test pass.