unit price (#3989)

## Description:

# Ghost structure cost label

Renders the gold cost of the currently-selected build under the ghost
structure cursor, with color-coded affordability/placement state. Honors
the
existing `cursorCostLabel` user setting (legacy name `ghostPricePill`,
already
shipping ON by default).

## Behavior

| State | Color |
|---|---|
| Can afford + valid placement | white |
| Can afford + can't place here (port on land, overlap, …) | gray |
| Can't afford | red |

The number is formatted via `renderNumber` (project-wide convention —
`1.5K`,
`1.23M`, etc.) and rendered as MSDF text at a fixed world-space scale,
centered
under the ghost icon.

## Implementation

The cost was already plumbed end-to-end on
[`GhostPreviewData.cost`](src/client/render/types/Renderer.ts) but never
visualized. This PR:

- Extends [`GhostPreviewData`](src/client/render/types/Renderer.ts) with
`showCost` (from setting) and `canAfford` (gold
vs. cost check, computed in
[BuildPreviewController](src/client/controllers/BuildPreviewController.ts)).
- Adds a `setGhostCostLabel(...)` channel to the MSDF text pass — one
persistent,
non-animated text instance alongside the existing ephemeral popups. No
new
  pass, no new shader.
- Wires
[`Renderer.updateGhostPreview`](src/client/render/gl/Renderer.ts) to
push the label whenever a ghost is active.
- Renames `ConquestPopupPass` →
[`WorldTextPass`](src/client/render/gl/passes/WorldTextPass.ts) (and its
shader dir
`conquest-popup/` → `world-text/`) since it now handles conquest popups,
bonus popups, and the ghost cost label. Done with `git mv` so history is
  preserved.




https://github.com/user-attachments/assets/c5b21bf3-f440-4c28-9b94-843df9bf6a37





## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

evan
This commit is contained in:
Evan
2026-05-22 17:30:15 +01:00
committed by GitHub
parent 458d04e278
commit ee04a19d3c
7 changed files with 143 additions and 22 deletions
@@ -16,6 +16,7 @@ import {
} from "../../core/game/Game";
import { TileRef } from "../../core/game/GameMap";
import { GameView } from "../../core/game/GameView";
import { UserSettings } from "../../core/game/UserSettings";
import { Controller } from "../Controller";
import {
ConfirmGhostStructureEvent,
@@ -57,6 +58,7 @@ export class BuildPreviewController implements Controller {
public uiState: UIState,
private transformHandler: TransformHandler,
private view: WebGLGameView,
private userSettings: UserSettings,
) {}
init() {
@@ -335,13 +337,16 @@ export class BuildPreviewController implements Controller {
break;
}
const cost = u.cost;
return {
ghostType: u.type,
tileX: this.game.x(tileRef),
tileY: this.game.y(tileRef),
canBuild: u.canBuild !== false,
canUpgrade: u.canUpgrade !== false,
cost: Number(u.cost),
cost: Number(cost),
showCost: this.userSettings.cursorCostLabel(),
canAfford: myPlayer.gold() >= cost,
ghostRailPaths: u.ghostRailPaths,
overlappingRailroads: u.overlappingRailroads,
ownerID: myPlayer.smallID(),