Skip to content

Move attack troop overlay to WebGL#3996

Merged
evanpelle merged 1 commit into
mainfrom
attack-overlay
May 24, 2026
Merged

Move attack troop overlay to WebGL#3996
evanpelle merged 1 commit into
mainfrom
attack-overlay

Conversation

@evanpelle
Copy link
Copy Markdown
Collaborator

@evanpelle evanpelle commented May 23, 2026

Description:

Replaces the DOM-based AttackingTroopsOverlay with AttackingTroopsController, rendering attack troop counts through WorldTextPass instead of a separate fixed-position DOM container.

Summary

  • New AttackingTroopsController polls attackClusteredPositions() every 200ms and pushes labels to the WebGL view each frame, lerping cluster positions over 250ms for smooth front-line movement (replaces the old CSS transform 0.25s transition).
  • WorldTextPass gains setAttackTroopLabels() and renders them at a fixed on-screen size (zoom-independent) using screenScale / zoom.
  • World text now draws on top of NamePass so attack callouts aren't hidden behind centered player names.
  • Fragment shader adds a soft quadratic dark halo around every world-text label; extent uses the remaining SDF range after the hard outline so it fades smoothly to zero (no rectangular clipping).
  • Deletes AttackingTroopsOverlay.ts; existing unit tests repointed to the controller's exported alignClusterOrder.
Screenshot 2026-05-24 at 4 43 51 PM

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 23, 2026

Review Change Stack

Walkthrough

Migrates attacking-troop labels from a DOM overlay to WebGL. Adds AttackingTroopsController that polls clustered attack positions, interpolates Slot animations, and emits per-frame AttackTroopLabel arrays to WorldTextPass; GameView/Renderer/WorldTextPass are extended to accept and render these labels.

Changes

Attack Troop Labels Migration to WebGL

Layer / File(s) Summary
Slot-based position animation model
src/client/controllers/AttackingTroopsController.ts
Introduces Slot for interpolated positions, per-attack AttackEntry, and alignClusterOrder to minimize label movement for two-cell clusters.
AttackingTroopsController core implementation
src/client/controllers/AttackingTroopsController.ts
Controller subscribes to AlternateViewEvent, polls myPlayer.attackClusteredPositions() every 200ms, reconciles positions into Slot animations (snap/animate), tracks incoming/outgoing attacks, and pushes per-frame labels via view.setAttackTroopLabels.
WebGL rendering infrastructure for attack labels
src/client/render/gl/GameView.ts, src/client/render/gl/Renderer.ts, src/client/render/gl/passes/WorldTextPass.ts
Adds setAttackTroopLabels() plumbing; WorldTextPass gains AttackTroopLabel type, accepts zoom in tick(), and rebuilds GPU instances with zoom-compensated scale for persistent attack labels.
GameRenderer layer registration
src/client/hud/GameRenderer.ts
Replaces the attacking-troops HUD layer from AttackingTroopsOverlay to AttackingTroopsController with appropriate dependencies.
Test migration to new Slot model
tests/client/graphics/layers/AttackingTroopsOverlay.test.ts
Updates imports to use Slot and alignClusterOrder from the controller, adds helper to build Slot test fixtures, removes computeLabelScale tests, and revalidates cluster-order behavior.

Sequence Diagram(s)

sequenceDiagram
  participant RAF as RequestAnimationFrame
  participant Controller as AttackingTroopsController
  participant Worker as myPlayer
  participant WebGL as WebGLGameView
  participant Pass as WorldTextPass

  Controller->>Controller: tick() every 200ms
  Controller->>Worker: attackClusteredPositions()
  Worker-->>Controller: ClusteredCell[] response
  Controller->>Controller: reconcileSlots() interpolate
  Note over Controller: Slot-based smooth animation
  RAF->>Controller: pushLabels() every frame
  Controller->>WebGL: setAttackTroopLabels(AttackTroopLabel[])
  WebGL->>Pass: setAttackTroopLabels()
  Note over Pass: Generate GPU instances with zoom-scaled size
  Pass-->>RAF: Render to screen
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

Suggested Labels

UI/UX, Feature

Poem

Troops that once were DOM-bound sprites,
Now glide in GPU-lit nights.
Slots interpolate each hearty cheer,
Labels crisp and steady near.
WebGL hums — the frontlines bright.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ Passed checks (4 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.
Title check ✅ Passed The title 'Move attack troop overlay to WebGL' clearly and concisely describes the main architectural change in the changeset: migrating the attack troop label rendering from DOM-based AttackingTroopsOverlay to WebGL-based AttackingTroopsController.
Description check ✅ Passed The pull request description clearly explains the changes: replacing DOM-based AttackingTroopsOverlay with WebGL-based AttackingTroopsController, with details about polling, lerping, rendering, and shader updates.

✏️ 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

🤖 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/WorldTextPass.ts`:
- Around line 47-54: Move the hardcoded attack-label tuning values
(ATTACK_LABEL_SCREEN_SCALE, ATTACK_LABEL_OUTLINE_WIDTH and the other constants
used in WorldTextPass methods) into RenderSettings and read them from the
existing RenderSettings instance instead of using literals in WorldTextPass; add
corresponding keys and sane defaults to render-settings.json and the
RenderSettings type/interface, then replace uses of ATTACK_LABEL_SCREEN_SCALE,
ATTACK_LABEL_OUTLINE_WIDTH and the other pass constants inside the WorldTextPass
class (and any methods referencing them) with lookups like
renderSettings.attackLabelScreenScale / attackLabelOutlineWidth so the pass uses
configuration-driven values.
🪄 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: a4d82013-e76d-4597-a84d-383c95f34691

📥 Commits

Reviewing files that changed from the base of the PR and between 14f2e36 and 43bd112.

⛔ Files ignored due to path filters (1)
  • src/client/render/gl/shaders/world-text/world-text.frag.glsl is excluded by !**/*.glsl
📒 Files selected for processing (7)
  • src/client/controllers/AttackingTroopsController.ts
  • src/client/hud/GameRenderer.ts
  • src/client/hud/layers/AttackingTroopsOverlay.ts
  • src/client/render/gl/GameView.ts
  • src/client/render/gl/Renderer.ts
  • src/client/render/gl/passes/WorldTextPass.ts
  • tests/client/graphics/layers/AttackingTroopsOverlay.test.ts
💤 Files with no reviewable changes (1)
  • src/client/hud/layers/AttackingTroopsOverlay.ts

Comment thread src/client/render/gl/passes/WorldTextPass.ts
@github-project-automation github-project-automation Bot moved this from Triage to Development in OpenFront Release Management May 23, 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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/client/controllers/AttackingTroopsController.ts (1)

236-259: ⚡ Quick win

Reuse labelBuf instead of allocating a new array every frame.

This method runs on every animation frame (60+ fps). Creating const out: AttackTroopLabel[] = [] each time adds GC pressure. The class already has a labelBuf field meant for reuse—clear it and push into it directly.

♻️ Proposed fix
   private pushLabels(): void {
     if (this.alternateView || this.attacks.size === 0) {
       if (this.labelBuf.length > 0) {
-        this.labelBuf = [];
+        this.labelBuf.length = 0;
         this.view.setAttackTroopLabels(this.labelBuf);
       }
       return;
     }

     const now = performance.now();
-    const out: AttackTroopLabel[] = [];
+    this.labelBuf.length = 0;

     for (const entry of this.attacks.values()) {
       const r = entry.isIncoming ? INCOMING_R : OUTGOING_R;
       const g = entry.isIncoming ? INCOMING_G : OUTGOING_G;
       const b = entry.isIncoming ? INCOMING_B : OUTGOING_B;
       for (const slot of entry.slots) {
         const t = Math.min(1, (now - slot.startMs) / ANIM_MS);
         slot.curX = slot.srcX + (slot.dstX - slot.srcX) * t;
         slot.curY = slot.srcY + (slot.dstY - slot.srcY) * t;
-        out.push({
+        this.labelBuf.push({
           x: slot.curX,
           y: slot.curY,
           text: entry.text,
           colorR: r,
           colorG: g,
           colorB: b,
         });
       }
     }

-    this.labelBuf = out;
-    this.view.setAttackTroopLabels(out);
+    this.view.setAttackTroopLabels(this.labelBuf);
   }
🤖 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/controllers/AttackingTroopsController.ts` around lines 236 - 259,
The loop currently allocates a fresh array with "const out: AttackTroopLabel[] =
[]" every frame; instead reuse the existing this.labelBuf: clear it (e.g.,
this.labelBuf.length = 0) before the loops and push each computed
AttackTroopLabel into this.labelBuf while computing positions with
performance.now(), ANIM_MS, and this.attacks; remove the local out and after the
loops call this.view.setAttackTroopLabels(this.labelBuf) so no new array is
created each frame.
🤖 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/controllers/AttackingTroopsController.ts`:
- Around line 81-91: The init() method starts a perpetual requestAnimationFrame
loop (drive) and registers an AlternateViewEvent listener but never cleans them
up; add a dispose() method that (1) removes the AlternateViewEvent listener
registered via this.eventBus.on(...) (use the same handler reference or store it
as e.g. this._onAlternateView) and (2) cancels the RAF loop by storing the ID
returned from requestAnimationFrame when starting drive (e.g. this._rafId) and
calling cancelAnimationFrame(this._rafId) in dispose(); ensure dispose() also
nulls any references used by pushLabels()/drive to allow GC.
- Around line 161-162: AttackingTroopsController currently calls
renderTroops(troops) which delegates to renderNumber and produces hardcoded
"K"/"M" suffixes; change renderNumber (and/or renderTroops) to stop emitting
hardcoded suffixes and instead call translateText() with new keys (e.g.
"number.k", "number.m" or "troops.format") defined in resources/lang/en.json so
localization can control "K"/"M" and any surrounding formatting; update
renderTroops/renderNumber to: compute scaled value and pass the scale key to
translateText along with the formatted numeric part, and add the corresponding
keys and example values to resources/lang/en.json; ensure
AttackingTroopsController.ensureEntry still uses renderTroops(…) unchanged from
the caller perspective.

---

Nitpick comments:
In `@src/client/controllers/AttackingTroopsController.ts`:
- Around line 236-259: The loop currently allocates a fresh array with "const
out: AttackTroopLabel[] = []" every frame; instead reuse the existing
this.labelBuf: clear it (e.g., this.labelBuf.length = 0) before the loops and
push each computed AttackTroopLabel into this.labelBuf while computing positions
with performance.now(), ANIM_MS, and this.attacks; remove the local out and
after the loops call this.view.setAttackTroopLabels(this.labelBuf) so no new
array is created each frame.
🪄 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: a384bd5c-4c72-4649-9c30-78fe7ffdb511

📥 Commits

Reviewing files that changed from the base of the PR and between 43bd112 and d167912.

⛔ Files ignored due to path filters (1)
  • src/client/render/gl/shaders/world-text/world-text.frag.glsl is excluded by !**/*.glsl
📒 Files selected for processing (7)
  • src/client/controllers/AttackingTroopsController.ts
  • src/client/hud/GameRenderer.ts
  • src/client/hud/layers/AttackingTroopsOverlay.ts
  • src/client/render/gl/GameView.ts
  • src/client/render/gl/Renderer.ts
  • src/client/render/gl/passes/WorldTextPass.ts
  • tests/client/graphics/layers/AttackingTroopsOverlay.test.ts
💤 Files with no reviewable changes (1)
  • src/client/hud/layers/AttackingTroopsOverlay.ts

Comment thread src/client/controllers/AttackingTroopsController.ts
Comment thread src/client/controllers/AttackingTroopsController.ts
@evanpelle evanpelle changed the title attack overlay Move attack troop overlay to WebGL May 24, 2026
@evanpelle evanpelle marked this pull request as ready for review May 24, 2026 15:44
@evanpelle evanpelle requested a review from a team as a code owner May 24, 2026 15:44
@evanpelle evanpelle added this to the v32 milestone May 24, 2026
@evanpelle evanpelle merged commit b4a14f9 into main May 24, 2026
17 of 32 checks passed
@evanpelle evanpelle deleted the attack-overlay branch May 24, 2026 15:47
@github-project-automation github-project-automation Bot moved this from Development to Complete in OpenFront Release Management May 24, 2026
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