Skip to content

Missing UI — Implementation Plan

Screens designed (prefabs exist) but not yet wired: FullMapView, WinScreenView, LoseScreenView, SettingsView. Read docs/ui/UNITY_UI_WORKFLOW.md first — all the rules there (hard-linked components, LoadPrefabContents editing, UIMount.FillParent, driven root state, adaptive spine, MVC) apply to everything below. Web proto = game.js.

Status snapshot — ✅ ALL IMPLEMENTED (2026-07-03)

Screen View script Controller Notes
FullMapView Map/FullMapView (+ LegendItemView) Map/MapController (extended) framed surface mount + on-spawn legend + crisp circle markers (both maps)
WinScreenView Result/WinScreenView Result/ResultController (shared) biome-unlock block; sequential loot pop-in
LoseScreenView Result/LoseScreenView Result/ResultController (shared) "Slain by X"; hide-empty panels
SettingsView Settings/SettingsView Settings/SettingsController VolumeSliderView×2 + UIToggle; lobby btn_settings entry

Leaf components built: Common/UIToggle, Settings/VolumeSliderView, Map/LegendItemView (+ the Common/GameSettings audio/haptics seam). All prefabs wired 0 nulls; every screen compiles clean. Data added for the result screens: RaidState.Kills (player-attributed) + RaidState.DeathCause. Remaining validation: a play-test pass (open each screen in-game; the UIWiring tooling can still re-verify). The sections below are the as-built reference.


A. Leaf components to build first

A1. VolumeSliderView (the flagged "fillable progress bar") — Settings Music/Sound

Prefab sub-tree (panel_music/panel_soundprogbar):

progbar
  img_back    (track background — full width)
  img_fill    (the filled portion; NOT a Filled-type Image — resize its width)
  img_knob    (draggable handle)
Build a MonoBehaviour on progbar: - Serialized refs: RectTransform track (img_back), RectTransform fill (img_fill), RectTransform knob (img_knob). - public float Value {get; } in 0..1; event Action<float> Changed; - SetValue(float v): clamp 0..1; set fill width = track.width * v (anchor fill left, drive sizeDelta.x or a left/right anchor), and knob.anchoredPosition.x = track.width * v (minus half-knob). Do NOT use Image.type = Filled — the art is a separate fill sprite. - Drag input: implement IDragHandler/IPointerDownHandler (or a small drag on the track); map pointer-x within the track rect → value → SetValue → raise Changed. - No Slider component, no runtime Find — pure serialized refs. Reused twice (Music, Sound).

A2. SettingsToggleView — Settings Vibration

Prefab sub-tree (panel_vibrationbtn_toggle):

btn_toggle
  img_bar_body_3   (track)
  img_toggle_on    (on-state art / knob-right)
  img_toggle_off   (off-state art / knob-left)
- Serialized refs: GameObject onArt (img_toggle_on), GameObject offArt (img_toggle_off); authored UIButton on btn_toggle (see workflow §5 button authoring). - public bool On {get;}; event Action<bool> Toggled; - SetOn(bool): onArt.SetActive(on), offArt.SetActive(!on). - On UIButton.Clicked: flip On, apply, raise Toggled.

A3. LegendItemView — FullMapView legend row (MapLegendItem.prefab)

  • One legend entry = a colour swatch Image + a label TMP_Text.
  • Serialized refs: Image swatch, TMP_Text label; Set(Color c, string text).
  • Used as a typed template cloned per legend entry (see the web renderLegend list).

A4. Reused (already exist)

  • CurrencyView — win/lose currency chips (blueprints + gold). Prefabs already contain CurrencyView_Blueprints / CurrencyView_Currency — just wire + Set(icon,value).
  • InventoryItem — win/lose loot grid cells (obtained / lost). Clone into grid_items and RenderItem(icon,rarity,level,…) per item (like InventoryView.RenderStash).

B. FullMapView (minimap tap → full tactical map)

Web proto: tapping the minimap calls openBigMap() (game.js:4693) → drawBigMap() + renderLegend(); handleMapTap handles taps; closeBigMap() on tap/back. A one-time "🗺 tap to open" hint sits on the minimap until save.mapEverOpened.

Unity today: Map/MapView.cs already renders BOTH the minimap and a full-screen tactical map (a RawImage of the MapTextureBaker texture + circle markers + player dot; it builds its surface procedurally). Map/MapController.cs owns the minimap (mounted into InGameView's MinimapSlot). FullMapView.prefab is the authored Figma frame + legend; MapLegendItem.prefab is the legend row.

To implement: 1. Decide integration (recommend): FullMapView.prefab provides the frame art + a map_surface RectTransform slot + a legend container. At open, the controller mounts a MapView map-surface (or reuses the existing tactical MapView) inside map_surface, feeds it the same baked texture + markers the minimap uses, and populates legend by cloning LegendItemView for each marker category (You / Rival / Threat / Guardian / Vault / Rare / Epic / Chest / Safe Exit / Descent Portal — from the imported IngameMap legend + web renderLegend). 2. FullMapView.cs (pure view): serialized map_surface, legend container + LegendItemView template, an authored close UIButton (tap-to-close, like the settings scrim). Events: CloseClicked; a MapTapped(Vector2 viewportPos) if tactical taps do anything. 3. Wire into MapController: expose the minimap as tappable (author a UIButton on the minimap root or its frame) → OpenFullMap(); show FullMapView (mount under UIRoot via a controller + UIMount.FillParent), freeze/keep the raid running per web (mode==="playing"), set save.mapEverOpened. Close returns to the HUD. 4. Keep the "tap to open" minimap hint until first open (web drawMapHint/mapEverOpened).

Data source: MapTextureBaker (baked map texture), the live marker list the minimap already builds (MapController marker feed), player world position.


C. WinScreenView / LoseScreenView (raid result)

Web proto: endRun(win, msg) → result overlay: resultTitle = "✦ Extracted! ✦" (win) / "✗ You Fell" (lose); a loot list (chips) or "Extracted empty-handed."; a flavour line ("You descend deeper, haul in hand." on a progress extract). Death holds on a beat first.

Unity today: Tester/MetaHud.cs owns the placeholder Result card. Flow: RaidController.EndRaid banks the run → MetaHud.OnRaidEnded(bool extracted, bool descended)AdvanceLadderShowResult. Death waits player.deathDelay (DeathBeat) then shows; extract is instant. Replace the placeholder card with the two Figma views.

Prefab structure (both):

popup (adaptive VLG+CSF spine)
  panel_header   → txt_header  ("Extracted!" / "Extraction Failed!")
  panel_stats    → content: txt_description ("You descend deeper" / "You have been slain")
                   + panel_lvl_stat (time / kills / floor — the run summary)
  panel_biome    (WIN ONLY, toggleable) → "New Biome Unlocked" + txt_biomename
  panel_loot     → txt_header ("Obtained Items:" / "Lost Loot:")
                   [Lose also: txt_description "Your gear is safe, …"]
                   + grid_items (InventoryItem clones) + row_currency (CurrencyView ×2)
  btn_continue   → "Continue"

To implement: 1. WinScreenView.cs + LoseScreenView.cs (or ONE ResultScreenView with a bool win — they share ~90%; a shared view with the biome block toggled off for lose is DRY-est). Pure view: serialized header/stats/biome/loot(grid template + currency)/continue + a Render(ResultVM). 2. Adaptive spine on popup (biome block toggles off for lose / when no new biome; loot grid + currency populate dynamically) — same pattern as DebriefingWin. 3. ResultVM: title/body strings, run stats (time, kills, floor reached), biome-unlock name (or none), obtained/lost loot list ({icon,rarity,level}), currency deltas (blueprints, gold). Fill it in the controller from the raid-end state (RaidState, ExtractionService/RaidController result, save deltas). DebriefingWin.prefab is the win-design reference already imported. 4. Wire into MetaHud (or a new ResultController): on OnRaidEnded, instantiate the right view under UIRoot (UIMount.FillParent), Render(vm), btn_continue.Clicked → return to lobby (ReturnToLobby) / next. Reuse the DeathBeat delay for lose. 5. Reuse CurrencyView (wire CurrencyView_Blueprints/_Currency) + InventoryItem (grid).


D. SettingsView (Unity/Figma-only)

No web counterpart. New meta screen. Opened from… (decide: a gear button on the lobby TopPanel/NavBar — none authored yet; may need a settings button). Closed via BackgroundTapToClose.

Prefab structure:

Content
  BackgroundTapToClose (authored UIButton) → txt_close "Tap To Close"
  popup
    panel_header → "Settings"
    panel_sound → panel_settings → panel_music / panel_sound / panel_vibration
        panel_music     → panel_header(icon+"Music")   + progbar  → VolumeSliderView (A1)
        panel_sound     → panel_header(icon+"Sound")   + progbar  → VolumeSliderView (A1)
        panel_vibration → panel_header(icon+"Vibration") + btn_toggle → SettingsToggleView (A2)
    panel_buttons → grid_buttons: btn_tos / btn_pp / btn_support / btn_delete  (author UIButtons)
    panel_info → panel_version (txt "Version" + txt_version_number)
                 layout_user_id (txt "User ID" + txt_id + btn_copy)

To implement: 1. Build leaf components A1 (VolumeSliderView) + A2 (SettingsToggleView). 2. SettingsView.cs (pure view): serialized musicSlider/soundSlider (VolumeSliderView), vibrationToggle (SettingsToggleView), 4 button UIButtons, version/id TMP_Text, copy UIButton, close UIButton. Render(SettingsVM) + events (MusicChanged, SoundChanged, VibrationToggled, TosClicked, PpClicked, SupportClicked, DeleteClicked, CopyIdClicked, CloseClicked). 3. SettingsController: reads/writes new save fieldsmusicVol/soundVol (0..1), vibration (bool) — on SaveData; pushes them into an audio/haptics seam (mirror the existing UiFeedback no-op seam in Common/UIButton.cs: AudioSettings.Impl etc. — real service later). Button actions: TOS/PP/Support → open URL (Application.OpenURL) or no-op stub; Delete → confirm + clear save; Copy → GUIUtility.systemCopyBuffer = userId. Version = Application.version; User ID = a save/device id. 4. Add a settings entry point (a gear UIButton somewhere on the lobby) — flag this: the authored lobby has no settings button yet; needs Figma art or a placeholder.


E. Suggested order

  1. Leaf components A1 VolumeSliderView, A2 SettingsToggleView, A3 LegendItemView (small, testable in isolation).
  2. SettingsView (self-contained; exercises A1/A2 + save fields + the audio seam).
  3. WinScreenView / LoseScreenView (shared ResultScreenView; replaces MetaHud.ShowResult; reuses CurrencyView + InventoryItem + the adaptive spine).
  4. FullMapView (most integration — reconcile the procedural MapView surface with the authored frame + legend, and add the minimap-tap entry point).

Each screen: author any missing Button+UIButtons, wire every ref with the UIWire tooling, verify UIWireVerify.AssertAllRefs == 0, edit prefabs via LoadPrefabContents only, and play-test. Follow the per-view checklist in the workflow doc.

F. Open questions to resolve with the designer/owner

(Resolved — see §G.)

G. Owner decisions (2026-07-03) — build to these

Layering: Settings mounts on top of the Lobby; Results + FullMap mount on top of the in-raid HUD. (Nested under UIRoot with a sort order above the host screen.)

Settings: - Slider (VolumeSliderView): interactive drag. When value == 0 → enable the icon's img_disabled mark (the disabled overlay on the row's img_music/img_sound/img_vibration) and tint the img_knob red; otherwise hide the mark + normal knob colour. Wire Music AND Sound identically; both are exposed + functional as values, but there is no audio system to drive yet (store the value; no-op seam). - Toggle → make it a SHARED widget Common/UIToggle (sibling of UIButton, reusable later). Both on/off visuals are authored (img_toggle_on/img_toggle_off) — swap them. Vibration uses it. - 4 buttons (TOS/PP/Support/Delete): authored UIButtons with NO logic for now — leave inert. - User ID: leave the value empty for now (wire later). Still serialize the ref. - Version: read Application.version (current build version). - Entry point: a settings button at the TOP-RIGHT of the Lobby (add to LobbyView; needs an authored UIButton — may need placeholder art / a Figma gear icon).

Win/Lose (separate views + prefabs; shared CONTROLLER only): - Display all data carried; disable/hide empty panels: no obtained/lost items → hide the item grid; no currencies → disable the currency bars. - Text — web-faithful: win → "You descend deeper…" (progress extract) / extract-banked wording; lose → "slain by X" (the killer/cause). Match game.js endRun messages. - Animate loot appearance: reveal collected/lost items one-by-one (fast sequential pop-in), skippable on tap (tap → instantly show all). - Do NOT combine the two views/prefabs; a shared controller (ResultController) drives both.

FullMap: - Legend behaves like the web: when an item/marker is present on the stage, show its legend row — computed on spawn (not realtime). - The map is framed inside the frame sprite (same as the minimap). - Circle sprite for map markers — and replace the minimap markers with the same circle (the current default markers are hard to read/distinguish because they're gradient).

Build order: leaf components (UIToggle shared, VolumeSliderView, LegendItemView) → Settings → Win/Lose (shared controller) → FullMap (+ circle markers on both maps).