Battle UI contract + 4 sub-issues: SUPPORT/LOG below fold, portrait fallback, snap-pause selection, RMB hint #22
Labels
No labels
blocked:needs-attention
blocked:needs-decision
blocked:needs-decompose
blocked:needs-design
blocked:needs-triage
blocked:unclassified
bug
documentation
enhancement
gsd-spec
in-progress
needs-decompose
priority:high
priority:low
priority:medium
quickfix
test
triage-approved
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Mark/cybertactics#22
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
The in-mission battle UI is described piecemeal across
README.md§Controls, theDOM in
web/index.html, and the renderer inweb/js/ui.js+web/css/style.css.The screenshot at
docs/screenshot.pngis the only end-to-end reference, and itreveals the surface is real but the layout contract is never written down.
This issue captures the canonical battle-UI contract derived from the docs +
screenshot, then enumerates the gaps the contract surfaces. Each gap lands as a
sub-issue (below) so kilo-gsd can pick them up individually.
The contract is web-first; the Godot port has its own
godot/scenes/main.tscnthat ships an empty
UICanvasLayer (separate gap, filed in #23 alongside theexisting Godot-side stubs).
Design contract — battle UI
The in-mission screen has four regions in a fixed layout:
Region definitions
#topbar(html:9)README.md§Controls lines 164–175G.speed === 0. Phase pill recolors perphase-exec/phase-plan/phase-event(css:19–21).#game(html:21, 728×504)README.md§Systems lines 183–204TILE = 28.#side(html:22, w=330px, max-h=504)README.md+ screenshotmax-height: 504px(css:31) so the bottom two scroll off on a 504-tall canvas.#hint(html:32)README.md§ControlsPanel contracts
Each sidebar panel has a hard role derived from
web/js/ui.jsupdateUI()(lines 595–780):
#squadselectUnit(m); click team header →selectTeam(tid)#selG.selectedportrait + stats (ui.js:636–650); or team stats whenG.selTeamset#stancesSTANCESkeys (ui.js:652–668)setStance(k); buttonactivewhen target/team matches#queueu.queue(ui.js:670–713) — list withqitemrows;xper-row when pausedu.queue.splice(i, 1); click clear →clearQueue(u)#playbooksPLAYBOOKS(ui.js:715–744); research-locked entries show🔒 via <node>applyPlaybook(k)(only enabled when not locked)#supportair+dropalways;smokeonly whenghostcellresearch owned (ui.js:746–760)beginTargeting(k); second click anywhere cancels#logG.logring of 60 (ui.js:762–763)Controls contract
selectUnit(u)selectUnit(null))issueContext(tx, ty)→ MOVE / formation spreadtogglePause()setSpeed(1/2)cycleUnit()next aliveselectTeam("alpha"/"bravo"/"all")clearQueue(G.selected)G.targetingPhase contract
G.phase ∈ {EXECUTION, PLANNING};G.pausedBy ∈ {null, "manual", "planning", "event"}.Order queue rows are editable only when
G.speed === 0 || G.phase === "PLANNING"(ui.js:687, 705). Command phase fires every
BEAT_LEN = 9sof execution(game.js:6) via
enterPlanning()which resetsorders_runfor all squad unitsand pauses;
resumeExec()resumes atprevSpeed.Identified gaps — concrete fixes the contract surfaces
These are real bugs / missing affordances, ordered by how much the user is
likely to hit them in a 2-minute playthrough. Each becomes a sub-issue.
Sub-issue A — SUPPORT and LOG scroll below the fold (real bug)
web/css/style.css:31pins#side { max-height: 504px; }to match the canvas.With 5 visible panels (Squad/Selected/Stance/Queue/Playbooks) the user never
sees Support or Log on a default 1280×800 viewport. Confirmed in screenshot —
docs/screenshot.pngshows only the first 5 panels.Fix: make
#sideflex-grow within#wrap(or use100vh - topbar - hint)and let the LOG section be the only one that scrolls internally. Other
panels stay compact. Acceptance: Support + Log visible on 1280×800 without
scrolling the whole sidebar;
make cipasses.Sub-issue B — selected-panel portrait is missing for non-squad (real bug)
web/js/ui.jsupdateUI()line 644 selectsPORTRAIT[u.sprite]only when thesprite is in
PORTRAIT. For non-squad enemies inspected via LMB, the rendererfalls back to
assets/portraits/<vex|ganger>.png— wrong portrait for theinspected unit (SABLE shows VEX's face). The fallback string is on the same line.
Acceptance: LMB on a visible hostile shows that hostile's portrait (or a
neutral silhouette); no squad portrait leaks into the enemy inspect path.
Sub-issue C — no visible "Phalanx locked" affordance on the Playbook button
(tooltips-OK, button-state stub)
This is the same gap that landed as #17 for Phalanx. The fix is in place
(ui.js:729–741 wires
🔒 via <research-name>tooltip + sub-label), but thebutton itself remains enabled-looking — the
disabledattribute is set(ui.js:741) but no visual state tells the user "this will not work, you need
research X." Currently the only signal is opacity reduction via
button:disabled(css:27).
Acceptance: research-locked playbooks render with a distinct visual state
(amber border + sub-label already wired) and a one-line README/wiki note pointing
to the Strategic View where research lives.
Sub-issue D — selected unit speed across modes (UX gap)
Speed buttons are 1× / 2×; the README §Controls line 172 only mentions those.
There is no
0.5×(slow-mo for tactical review) or3×. README matches thebuild, but the topbar cluster renders the buttons at fixed width and looks
empty when nothing is happening in the planning phase.
Acceptance decision: keep current 1× / 2× — README is accurate, no scope
creep. Mark as out of scope for this issue, file separately if desired.
Sub-issue E — keyboard hint bottom-bar is the only command surface
(documentation gap)
#hint(html:32) is a fixed string. There's no in-game tooltip on RMB that says"MOVE on ground, ATTACK on enemy, INTERACT on object" — the user has to read
the hint bar or experiment. Same for the F1/F2/F3 strike-team keybindings —
they aren't shown anywhere in the panels themselves; only
titleattribute onteam rows (ui.js:623).
Acceptance: RMB hover on map shows a small contextual hint at the cursor
("MOVE", "ATTACK ", "INTERACT ") matching the queued order label.
Sub-issue F — focus-snap event doesn't show which unit the event is about
(UX gap)
snapPause()(game.js:545) setsG.focusand writes a log line but doesn'tselect the involved unit. After the snap-pause the player sees a circle pulse
but the SELECTED panel still shows whoever was selected before the event.
For "squad wounded" / "unit down" / "order failed" events, the involved unit
should become
G.selectedautomatically so the SELECTED panel updates.Acceptance: any snap-pause caused by a squad unit's state changes
selects that unit; hostile-contact snaps select the spotted enemy.
Sub-issue decomposition
web/css/style.css, possiblyweb/js/ui.jsweb/js/ui.jsline 644web/js/game.jssnapPause()callersweb/js/ui.jsrender()+web/js/main.jsmousedownSub-issue 22-1 should be its own quickfix lane (
make ciis the gate).Sub-issues 22-2 and 22-3 are surgical code changes. Sub-issue 22-4 is a
small renderer addition.
Acceptance
triage-approvedlabel.make cigreen on each landed sub-issue.docs/screenshot.pngreflects the new layout (Support +Log visible without scrolling on 1280×800).
Out of scope
(
godot/project.godotline 21 setspointing/emulate_touch_from_mouse=truefor Godot; the web build has no touch handler).
web/with a bundler —AGENTS.mdline 53 rules it out.Refs
README.mdlines 160–204 (Controls + Systems)web/index.htmllines 9–35 (DOM contract)web/css/style.csslines 12–53 (region styling)web/js/ui.jsupdateUI()lines 595–780 (panel contracts)web/js/main.jslines 12–43 (input handler)web/js/game.jssnapPause()line 545 +enterPlanning()line 555#15 (mission unlock on loss)
Decomposed into 4 sub-issues, each with
triage-approvedso kilo-gsd can pick them up:quickfix)bug)enhancement)enhancement)Each child's body opens with
**Parent-Issue:** #22and carries the full acceptance criteria + file pointers from the table in this issue's body. Sub-issues C (Phalanx affordance visual state), D (speed 0.5×/3×), and E (F1/F2/F3 in-panel keybinding hints) are not in scope for this decomposition per the issue body; they remain open for follow-up if desired.Closing this parent — the contract and the gap enumeration are captured; the work itself lives on the children.