Wire targetingAlly through GhostPreviewData.rangeWarning so RangeCirclePass
can color the blast-radius circle red instead of white when the ghosted nuke
would trigger a traitor penalty. Restores behavior lost in the canvas→WebGL
migration.
## 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
## Description:
Show factory and defence post radius for ghost structure when placing
structures from build bar (unitdisplay).
Show when city/port is placed directly over existing railroad, by
highlighting the railroad green. The railroad is not highlighted when
instead a city/port nearby the ghost structure will be upgraded instead
of placing it on the railroad. This works with the existing code in
buildableUnits in PlayerImpl: it would already return an empty array []
for overlappingRailroads and for ghostRailPaths when canUpgrade is
false. So the old checks for uiState for Canvas2D in
BuildPreviewController weren't even needed per se, they followed the
same logic as buildableUnits in PlayerImpl already did.
Both changes emulate how it worked before the move to WebGL.
- OverlappingRailroads now returns TileRefs instead of a railroad ID,
and it does so with less allocations than the previous code. It's a
determistic outcome, sorted and deduplicated. In doubt about this a bit,
because it's better also in case we ever do desync checks using this
data, but for the rendering it isn't needed per se and could be more
performant without allocations.
- Also: Cleanup obsolete Canvas2D rail highlighting state (UIState) that
was superseded by GhostPreviewData.
## 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:
tryout33
The render/ tree was the only place in the client still using kebab-case
filenames. Brings ~80 files in line with the rest of src/client/
(BuildPreviewController, TransformHandler, etc.). Directories kept as
they were (name-pass/, fx-pass/, passes/, utils/, debug/) since the
codebase already mixes those.
Two collisions surfaced and got resolved: render/types/ is a directory,
not a file, so its imports kept the lowercase form; and the sed pass
incidentally normalized core/pathfinding imports, which had to be
reverted since that file is actually lowercase on disk despite some
imports having referenced it as ./Types under macOS case-insensitive
resolution.
NukeTrajectoryPass and the rangeRadius pipe existed but had no caller —
trajectory arc and outer-blast circle never appeared during build mode.
BuildPreviewController now picks the closest active player silo as the
launch source, collects non-allied SAMs as threats, and pushes a
NukeTrajectoryData each preview tick. rangeRadius is set to
nukeMagnitudes(type).outer for AtomBomb / HydrogenBomb so the existing
RangeCirclePass renders the blast radius at the target.
Bypass the snap-to-tile in TransformHandler by adding
screenToWorldCoordinatesFloat. Each render frame, BuildPreviewController
re-emits the ghost preview at the cursor's exact world position
(adjusted by -0.5 to cancel the shader's tile-center offset). Buildable
validation still runs on the snapped tile at the 50ms throttle, but the
icon now follows the cursor 1:1 instead of stepping tile-to-tile.
graphics/ was a canvas2D-era directory name — TransformHandler, UIState,
and the Controller interface aren't graphics, they're cross-cutting
client state. Hoist them to src/client/ so the path matches what they
are. GhostStructureChangedEvent had three emitters and zero listeners;
removed.
BuildPreviewController and WarshipSelectionController now take the WebGL
view in their constructor and call view.updateGhostPreview /
view.setSelectedUnits themselves instead of emitting bus events that
ClientGameRunner forwarded. Splits the old mountWebGLDebugRenderer in
two — createWebGLView builds the view up front so the renderer can wire
controllers to it, mountWebGLDebugRenderer does the per-frame plumbing
after the transformHandler exists. GhostPreviewUpdatedEvent had no
remaining consumers and is removed.
UILayer → WarshipSelectionController and StructureIconsLayer →
BuildPreviewController. These are the two real Controller implementations
(state + click handling, no rendering) — the new names + location reflect
what they actually do now that all rendering lives in WebGL passes.