Say what a move would do before the player commits to it #40

Merged
icub3d merged 1 commit from 10-mission-hud into main 2026-09-13 22:28:43 +00:00
Owner

Pillar 4: every number the player needs in order to decide is visible before they decide.
Until now none of them were, and #8's device run showed the cost — I picked the wrong unit
three times in a row, because the map answers a press by drawing a route whether or not
that unit can act, and then nothing happens and nothing says why.

The action bar says why

A control reads either what committing would do or the reason it will not be allowed:

Move: 3 tiles, 1 AP what it would cost
Attack: 4 damage, lethal, 1 AP what it would do
Attack: 5 away, reaches 2 why not, with the numbers
Move: needs 1 AP, has 0 why not
Move: not this unit's turn the one that cost me three attempts

Greying a control out tells a player they cannot. The pillar is about telling them
why.

It is also the visible confirm-and-cancel affordance ADR 0003 asked for and #23 deferred
to this issue — pressing the map a second time works, but it is neither labelled nor
discoverable.

Where the numbers come from

combat::Prospect, published by combat rather than worked out in the HUD, because working
one out means building a Battle and ADR 0008 keeps exactly one place doing that. The HUD
derives nothing. That also gave take_actions and the forecast a shared snapshot, so a
preview and the act it previews cannot be looking at different worlds.

The turn order comes from the scheduler's own upcoming, so the timeline cannot disagree
with the turns that follow it.

Attacking, two-stage like moving

Pressing somebody on the other side chooses them as a target; pressing again confirms.
Which side somebody is on is an identity and the map may know it — whether the shot
reaches, costs, or is allowed is a rule and stays in combat, so world still does not
depend on it.

The overlay grows a fifth shape, a cross: a target and a destination are different
answers and are often one tile apart.

Two things the device found that the source hid

  • Every label is ASCII now. An em dash and a pair of diamond pips looked fine in the
    source and shipped as tofu boxes, because assets/fonts/ is empty and the built-in font
    draws little else (#27). A test asserts it rather than trusting the habit.
  • A press on a control no longer also presses the map behind it. Picking casts a ray
    whatever is drawn on top, so Wait was pressing a tile too.

Acceptance criteria

  • No colour, size, or spacing literal outside src/theme/ — and I swept the
    pre-existing ones out of story.rs, settings.rs and widget.rs while here.
    Val::Px no longer appears outside src/theme/.
  • Every colour cue paired with a glyph or a word — role glyphs in the timeline, a
    ring on the acting entry, HP 10/10 beside the bar, AP 2/2 ## beside the pips.
  • Damage previewed before commitment — via choose-then-confirm rather than hover,
    per ADR 0003: a touchscreen has none, and the criterion's "hovering" is superseded.
  • Reads at each UI scale — everything is Px under the global UiScale the
    settings screen drives, so the HUD scales with the text rather than drifting from it.

Not here, and not invented

There are no shields to draw — units have Health and nothing else — and no hit
chance
to show, because damage is a fixed number, which is what "no hidden dice the
player cannot reason about" reads as. Both are in the issue's scope list; neither exists
in the model, and inventing a stat to fill a HUD would be the wrong way round.

The timeline also sits under the status bar on the tablet — that is #26, already filed.

Verification

cargo fmt --check, cargo clippy --all-targets -- -D warnings, cargo test —
180 passing, up from 173.

Ran on a Pixel Tablet through four builds: the tofu, the fall-through press, and the
"not this unit's turn" gap were each found by looking at it.

Closes #10

🤖 Generated with Claude Code

https://claude.ai/code/session_01TA4hJHkRSU3XBxZYtMKXdh

Pillar 4: every number the player needs in order to decide is visible before they decide. Until now none of them were, and #8's device run showed the cost — I picked the wrong unit three times in a row, because the map answers a press by drawing a route whether or not that unit can act, and then nothing happens and nothing says why. ## The action bar says why A control reads either what committing would do or the reason it will not be allowed: | | | | --- | --- | | `Move: 3 tiles, 1 AP` | what it would cost | | `Attack: 4 damage, lethal, 1 AP` | what it would do | | `Attack: 5 away, reaches 2` | why not, with the numbers | | `Move: needs 1 AP, has 0` | why not | | `Move: not this unit's turn` | the one that cost me three attempts | Greying a control out tells a player they *cannot*. The pillar is about telling them *why*. It is also the visible confirm-and-cancel affordance ADR 0003 asked for and #23 deferred to this issue — pressing the map a second time works, but it is neither labelled nor discoverable. ## Where the numbers come from `combat::Prospect`, published by combat rather than worked out in the HUD, because working one out means building a `Battle` and ADR 0008 keeps exactly one place doing that. The HUD derives nothing. That also gave `take_actions` and the forecast a shared `snapshot`, so a preview and the act it previews cannot be looking at different worlds. The turn order comes from the scheduler's own `upcoming`, so the timeline cannot disagree with the turns that follow it. ## Attacking, two-stage like moving Pressing somebody on the other side chooses them as a target; pressing again confirms. Which side somebody is on is an identity and the map may know it — whether the shot reaches, costs, or is allowed is a rule and stays in `combat`, so `world` still does not depend on it. The overlay grows a fifth **shape**, a cross: a target and a destination are different answers and are often one tile apart. ## Two things the device found that the source hid - **Every label is ASCII now.** An em dash and a pair of diamond pips looked fine in the source and shipped as tofu boxes, because `assets/fonts/` is empty and the built-in font draws little else (#27). A test asserts it rather than trusting the habit. - **A press on a control no longer also presses the map behind it.** Picking casts a ray whatever is drawn on top, so `Wait` was pressing a tile too. ## Acceptance criteria - [x] **No colour, size, or spacing literal outside `src/theme/`** — and I swept the pre-existing ones out of `story.rs`, `settings.rs` and `widget.rs` while here. `Val::Px` no longer appears outside `src/theme/`. - [x] **Every colour cue paired with a glyph or a word** — role glyphs in the timeline, a ring on the acting entry, `HP 10/10` beside the bar, `AP 2/2 ##` beside the pips. - [x] **Damage previewed before commitment** — via choose-then-confirm rather than hover, per ADR 0003: a touchscreen has none, and the criterion's "hovering" is superseded. - [x] **Reads at each UI scale** — everything is `Px` under the global `UiScale` the settings screen drives, so the HUD scales with the text rather than drifting from it. ## Not here, and not invented There are **no shields** to draw — units have `Health` and nothing else — and **no hit chance** to show, because damage is a fixed number, which is what "no hidden dice the player cannot reason about" reads as. Both are in the issue's scope list; neither exists in the model, and inventing a stat to fill a HUD would be the wrong way round. The timeline also sits under the status bar on the tablet — that is #26, already filed. ## Verification `cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`, `cargo test` — **180 passing**, up from 173. Ran on a Pixel Tablet through four builds: the tofu, the fall-through press, and the "not this unit's turn" gap were each found by looking at it. Closes #10 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01TA4hJHkRSU3XBxZYtMKXdh
Pillar 4: every number the player needs in order to decide is visible before
they decide. Until now none of them were, and #8's device run showed what that
costs — I picked the wrong unit three times in a row, because the map answers a
press by drawing a route whether or not that unit can act, and then nothing
happens and nothing says why.

So the action bar says why. A control reads either what committing would do —
`Move: 3 tiles, 1 AP`, `Attack: 4 damage, lethal, 1 AP` — or the reason it will
not be allowed: `5 away, reaches 2`, `needs 1 AP, has 0`, `not this unit's turn`.
Greying a control out tells a player they cannot; the pillar is about telling
them why.

The numbers come from `combat::Prospect`, published by combat rather than worked
out here, because working one out means building a `Battle` and ADR 0008 keeps
exactly one place doing that. The HUD derives nothing; it reads and draws. That
also gave `take_actions` and the forecast a shared `snapshot`, so a preview and
the act it previews cannot be looking at different worlds.

The turn order comes from the scheduler's own `upcoming`, so the timeline cannot
disagree with the turns that follow it — the reason `upcoming` runs the real
`advance` on a copy rather than predicting.

Attacking gets the same two-stage commit as moving. Pressing somebody on the
other side chooses them as a target; pressing again confirms. Which side
somebody is on is an identity and the map may know it, but whether the shot
reaches, costs, or is allowed is a rule and stays in `combat` — so `world` still
does not depend on it. The overlay grows a fifth shape, a cross, because a
target and a destination are different answers and are often one tile apart.

Two things the device found that the source hid. Every label is ASCII now: an em
dash and a pair of diamond pips looked fine here and shipped as tofu boxes,
because `assets/fonts/` is empty and the built-in font draws little else (#27).
A test asserts it rather than trusting the habit. And a press that lands on a
control no longer also presses the map behind it — picking casts a ray whatever
is drawn on top, so `Wait` was pressing a tile too.

Also swept the last size literals out of `src/ui/`: the story columns, the
settings panel, the value controls and the caret all had bare `Val::Px` where a
named token belonged. `Val::Px` no longer appears outside `src/theme/`.

Not here, and not invented: there are no shields to draw, and no hit chance to
show — damage is a fixed number, which is what "no hidden dice" reads as.

Closes #10

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TA4hJHkRSU3XBxZYtMKXdh
icub3d merged commit d3371304a7 into main 2026-09-13 22:28:43 +00:00
icub3d deleted branch 10-mission-hud 2026-09-13 22:28:43 +00:00
Sign in to join this conversation.
No description provided.