Skip to content

unit price#3989

Merged
evanpelle merged 1 commit into
mainfrom
unit-price
May 22, 2026
Merged

unit price#3989
evanpelle merged 1 commit into
mainfrom
unit-price

Conversation

@evanpelle
Copy link
Copy Markdown
Collaborator

@evanpelle evanpelle commented May 22, 2026

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 but never
visualized. This PR:

  • Extends GhostPreviewData with showCost (from setting) and canAfford (gold
    vs. cost check, computed in BuildPreviewController).
  • 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 to push the label whenever a ghost is active.
  • Renames ConquestPopupPassWorldTextPass (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.
Screen.Recording.2026-05-22.at.5.21.43.PM.mov

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • 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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

Review Change Stack

Walkthrough

Build preview now displays an optional cursor cost label beneath the build site. The feature adds showCost and canAfford fields to GhostPreviewData, computes them in BuildPreviewController using user settings, passes them through the rendering system, and renders the label via a renamed WorldTextPass class with affordability-based coloring.

Changes

Ghost cost labeling

Layer / File(s) Summary
GhostPreviewData contract extension
src/client/render/types/Renderer.ts
GhostPreviewData interface adds showCost and canAfford boolean fields to control cost label visibility and color.
BuildPreviewController cost computation
src/client/controllers/BuildPreviewController.ts
BuildPreviewController injects UserSettings, computes showCost from userSettings.cursorCostLabel(), and computes canAfford from build affordability, returning both flags in buildGhostPreviewData().
WorldTextPass ghost-cost rendering
src/client/render/gl/passes/WorldTextPass.ts
ConquestPopupPass is renamed to WorldTextPass and extended with persistent ghost-cost label storage. Shader sources switch to world-text; a new public setGhostCostLabel() method sets/clears the label and computes its RGB color based on affordability and placeability. The tick() early-return logic now proceeds when either active popups exist or the ghost-cost label is present. Instance building emits both conquest/bonus popup glyphs and persistent ghost-cost glyphs into the same buffer.
Renderer pipeline integration
src/client/render/gl/Renderer.ts
GPURenderer replaces ConquestPopupPass with WorldTextPass across constructor wiring, conquest/bonus event routing, ghost preview updates, FX lifecycle, and disposal. updateGhostPreview() now calls worldTextPass.setGhostCostLabel(...) when showCost is true and cost > 0, passing cost, affordability, and placeability data; otherwise it clears the label by passing null.
GameRenderer dependency wiring
src/client/hud/GameRenderer.ts
GameRenderer passes userSettings to BuildPreviewController constructor, enabling cost label visibility control from user settings.

Sequence Diagram

sequenceDiagram
  participant BuildPreview as BuildPreviewController
  participant Renderer as GPURenderer
  participant WorldText as WorldTextPass
  participant Screen as Screen
  
  BuildPreview->>BuildPreview: compute cost, canAfford
  BuildPreview->>Renderer: return GhostPreviewData with showCost, canAfford
  Renderer->>Renderer: check showCost && cost > 0
  Renderer->>WorldText: setGhostCostLabel({ tileX, tileY, cost, canAfford, canPlace })
  WorldText->>WorldText: compute color (red/gray/white)
  WorldText->>WorldText: tick() - layout cost text
  WorldText->>WorldText: build glyph instances
  Renderer->>WorldText: draw()
  WorldText->>Screen: render cost label with affordability color
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

UI/UX

Poem

Beneath the cursor, numbers glow,
Red when broke, white when to go,
Ghost text dancing as we plan,
Cost made clear across the land. ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'unit price' is vague and generic, using non-descriptive terms that don't clearly convey what the changeset implements. Use a more specific title that describes the main change, such as 'Add ghost structure cost label display' or 'Show building cost under cursor with affordability feedback'.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description clearly describes the feature being implemented: rendering ghost structure cost labels with color-coded affordability states, explaining the implementation approach, and detailing which files were modified.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/client/render/gl/passes/ConquestPopupPass.ts (1)

495-498: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

clear() should also clear persistent ghost label state.

Line 495 clears popup entries, but ghostCostLabel remains set, so stale label state can survive clear() calls.

Proposed fix
  clear(): void {
    this.active.length = 0;
+   this.ghostCostLabel = null;
    this.instanceCount = 0;
  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/render/gl/passes/ConquestPopupPass.ts` around lines 495 - 498, The
clear() method currently zeroes active entries and instanceCount but leaves the
persistent ghost label state (ghostCostLabel) intact; update
ConquestPopupPass.clear() to also reset ghostCostLabel (e.g., set ghostCostLabel
to undefined/null or its initial default) so stale ghost label state cannot
persist after clear() is called.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/client/render/gl/passes/ConquestPopupPass.ts`:
- Around line 40-45: Move the per-pass tuning constants GHOST_COST_Y_OFFSET,
GHOST_COST_SCALE, and GHOST_COST_OUTLINE_WIDTH out of ConquestPopupPass and into
render-settings.json as new keys (e.g., ghostCostYOff, ghostCostScale,
ghostCostOutlineWidth), then access them through the RenderSettings accessor
used elsewhere (e.g., RenderSettings.<property> or the existing getter pattern)
inside ConquestPopupPass instead of the hardcoded values so the pass reads
RenderSettings.ghostCostYOff, RenderSettings.ghostCostScale and
RenderSettings.ghostCostOutlineWidth at runtime.

---

Outside diff comments:
In `@src/client/render/gl/passes/ConquestPopupPass.ts`:
- Around line 495-498: The clear() method currently zeroes active entries and
instanceCount but leaves the persistent ghost label state (ghostCostLabel)
intact; update ConquestPopupPass.clear() to also reset ghostCostLabel (e.g., set
ghostCostLabel to undefined/null or its initial default) so stale ghost label
state cannot persist after clear() is called.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 849ca81b-c1c4-4244-b03e-d7ac7b76a3be

📥 Commits

Reviewing files that changed from the base of the PR and between 458d04e and e6e8ead.

📒 Files selected for processing (5)
  • src/client/controllers/BuildPreviewController.ts
  • src/client/hud/GameRenderer.ts
  • src/client/render/gl/Renderer.ts
  • src/client/render/gl/passes/ConquestPopupPass.ts
  • src/client/render/types/Renderer.ts

Comment on lines +40 to +45
/** Tiles below the ghost icon center for the cost label. */
const GHOST_COST_Y_OFFSET = 3;
/** World-space font size — smaller than popups so it sits unobtrusively under the icon. */
const GHOST_COST_SCALE = 4;
/** Matches player-name outline width for a consistent UI look. */
const GHOST_COST_OUTLINE_WIDTH = 1.4;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Move ghost-cost tuning values into RenderSettings.

The new constants on Line 41, Line 43, and Line 45 are per-pass tuning values but are hardcoded in the pass.

As per coding guidelines All per-pass tuning constants must be defined in render-settings.json and accessed through RenderSettings, not hardcoded in pass classes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/render/gl/passes/ConquestPopupPass.ts` around lines 40 - 45, Move
the per-pass tuning constants GHOST_COST_Y_OFFSET, GHOST_COST_SCALE, and
GHOST_COST_OUTLINE_WIDTH out of ConquestPopupPass and into render-settings.json
as new keys (e.g., ghostCostYOff, ghostCostScale, ghostCostOutlineWidth), then
access them through the RenderSettings accessor used elsewhere (e.g.,
RenderSettings.<property> or the existing getter pattern) inside
ConquestPopupPass instead of the hardcoded values so the pass reads
RenderSettings.ghostCostYOff, RenderSettings.ghostCostScale and
RenderSettings.ghostCostOutlineWidth at runtime.

@github-project-automation github-project-automation Bot moved this from Triage to Development in OpenFront Release Management May 22, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/client/render/gl/passes/WorldTextPass.ts (1)

41-46: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Move ghost-cost tuning values to RenderSettings.

Line 42, Line 44, and Line 46 hardcode per-pass tuning values, and those same constants are consumed at Line 331 and Line 431-Line 432. Please source them from render-settings.json through RenderSettings instead.

Suggested direction
-const GHOST_COST_Y_OFFSET = 3;
-const GHOST_COST_SCALE = 4;
-const GHOST_COST_OUTLINE_WIDTH = 1.4;
+// from RenderSettings.worldText.ghostCost*
-      y: label.tileY + GHOST_COST_Y_OFFSET,
+      y: label.tileY + this.settings.worldText.ghostCostYOffset,
-        this.instanceData[off + 8] = GHOST_COST_SCALE;
-        this.instanceData[off + 9] = GHOST_COST_OUTLINE_WIDTH;
+        this.instanceData[off + 8] = this.settings.worldText.ghostCostScale;
+        this.instanceData[off + 9] =
+          this.settings.worldText.ghostCostOutlineWidth;

As per coding guidelines All per-pass tuning constants must be defined in render-settings.json and accessed through RenderSettings, not hardcoded in pass classes.

Also applies to: 331-332, 431-432

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/render/gl/passes/WorldTextPass.ts` around lines 41 - 46, The three
hardcoded tuning constants GHOST_COST_Y_OFFSET, GHOST_COST_SCALE, and
GHOST_COST_OUTLINE_WIDTH in WorldTextPass must be removed and sourced from
RenderSettings/read from render-settings.json; add corresponding keys (e.g.
ghostCostYOffset, ghostCostScale, ghostCostOutlineWidth) to
render-settings.json, expose them on the RenderSettings API, and replace all
uses of the local constants (declaration site and the usages inside
WorldTextPass where the constants are referenced) with lookups on RenderSettings
(e.g. RenderSettings.ghostCostYOffset / RenderSettings.get('ghostCostYOffset'))
so the pass no longer contains hardcoded tuning values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/client/render/gl/passes/WorldTextPass.ts`:
- Around line 41-46: The three hardcoded tuning constants GHOST_COST_Y_OFFSET,
GHOST_COST_SCALE, and GHOST_COST_OUTLINE_WIDTH in WorldTextPass must be removed
and sourced from RenderSettings/read from render-settings.json; add
corresponding keys (e.g. ghostCostYOffset, ghostCostScale,
ghostCostOutlineWidth) to render-settings.json, expose them on the
RenderSettings API, and replace all uses of the local constants (declaration
site and the usages inside WorldTextPass where the constants are referenced)
with lookups on RenderSettings (e.g. RenderSettings.ghostCostYOffset /
RenderSettings.get('ghostCostYOffset')) so the pass no longer contains hardcoded
tuning values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e76a8a43-a7a3-4201-ba50-60b695d2405a

📥 Commits

Reviewing files that changed from the base of the PR and between e6e8ead and 2f5a0e5.

⛔ Files ignored due to path filters (2)
  • src/client/render/gl/shaders/world-text/world-text.frag.glsl is excluded by !**/*.glsl
  • src/client/render/gl/shaders/world-text/world-text.vert.glsl is excluded by !**/*.glsl
📒 Files selected for processing (5)
  • src/client/controllers/BuildPreviewController.ts
  • src/client/hud/GameRenderer.ts
  • src/client/render/gl/Renderer.ts
  • src/client/render/gl/passes/WorldTextPass.ts
  • src/client/render/types/Renderer.ts

@evanpelle evanpelle marked this pull request as ready for review May 22, 2026 16:29
@evanpelle evanpelle requested a review from a team as a code owner May 22, 2026 16:29
@evanpelle evanpelle merged commit ee04a19 into main May 22, 2026
19 of 28 checks passed
@evanpelle evanpelle deleted the unit-price branch May 22, 2026 16:30
@github-project-automation github-project-automation Bot moved this from Development to Complete in OpenFront Release Management May 22, 2026
@coderabbitai coderabbitai Bot mentioned this pull request May 23, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Complete

Development

Successfully merging this pull request may close these issues.

1 participant