Skip to content

Smooth nuke point-light position per frame in ambient mode - #4311

Merged
evanpelle merged 1 commit into
mainfrom
fix-ambient-light-nuke-smoothing
Jun 16, 2026
Merged

Smooth nuke point-light position per frame in ambient mode#4311
evanpelle merged 1 commit into
mainfrom
fix-ambient-light-nuke-smoothing

Conversation

@evanpelle

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #4255. That PR made nuke sprites glide per render frame — UnitPass.drawMissiles lerps each nuke's lastPos→pos by wall-clock progress through the current tick. But in ambient/night mode the glow behind a nuke comes from a separate pass, PointLightPass, whose instance buffer is packed once per tick in updateLights() from the raw unit.pos. Its per-frame draw() (run every frame via LightmapPass) only set uniforms and issued the instanced draw — it never repositioned the lights. So the sprite moved at 60fps while its light jumped once per 100ms tick.

Fix

Mirror UnitPass's smoothing in PointLightPass:

  • updateLights() records a smoothSegs tuple (lightIdx, lastX, lastY, x, y) for each SMOOTHED_NUKE_TYPES unit whose lastPos !== pos, and stamps lastUnitsUpdateMs.
  • A new applySmoothing(), called at the top of draw(), lerps those lights by wall-clock tick progress ((now - lastUnitsUpdateMs) / tickIntervalMs, clamped to 1) and re-uploads only the affected instances. Unlike UnitPass (which re-uploads its tiny missile buffer wholesale), the light buffer can hold thousands of static structure lights, so a full per-frame re-upload would be wasteful.
  • tickIntervalMs comes from a new config constructor param, wired through in Renderer.ts (the same config already passed to UnitPass).

The light now uses the exact same lastPos→pos endpoints and alpha as the sprite, so the two track together.

Test plan

  • npx tsc --noEmit, eslint, and prettier all clean.
  • npx vitest tests/client/render --run — 40 passed.

🤖 Generated with Claude Code

Follow-up to #4255: nuke sprites glide per render frame (UnitPass lerps
lastPos→pos), but the glow behind them comes from PointLightPass, whose
instance buffer is packed once per tick from raw unit.pos. Its per-frame
draw() never repositioned the lights, so in ambient/night mode the light
jumped once per tick while the sprite moved at 60fps.

Mirror UnitPass's smoothing in PointLightPass: record (lightIdx, lastX,
lastY, x, y) segments for SMOOTHED_NUKE_TYPES in updateLights(), then lerp
them by wall-clock tick progress in draw() and re-upload only the affected
instances (the light buffer holds thousands of static structure lights, so
a full per-frame re-upload would be wasteful).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

A new NukeSmoother utility class extracts position interpolation logic for reuse across rendering passes. Both UnitPass and PointLightPass are refactored to replace local smoothing state with this utility. UnitPass removes tuple-array buffering and timing state; PointLightPass adds Config dependency to initialize the smoother. Tests are updated to drive smoothing through the new NukeSmoother interface.

Changes

Nuke and light position smoothing refactoring

Layer / File(s) Summary
NukeSmoother utility class
src/client/render/gl/utils/NukeSmoothing.ts
Introduces NukeSmoother to record per-instance position segments (instanceIdx, lastX, lastY, x, y) during a tick and interpolate them on render using wall-clock elapsed time. Exposes reset(), record(), isEmpty, and apply(set) for segment management and interpolation callback invocation.
UnitPass migration to NukeSmoother
src/client/render/gl/passes/UnitPass.ts
Removes local SMOOTH_SEG_STRIDE constant and smoothing state fields (smoothSegs, lastUnitsUpdateMs, tickIntervalMs), replacing them with a single nukeSmoother field. Constructor initializes via config.msPerTick(), updateUnits() resets per tick, unit iteration records segments, and applyMissileSmoothing() applies interpolated positions via this.nukeSmoother.apply().
PointLightPass integration with NukeSmoother
src/client/render/gl/passes/PointLightPass.ts, src/client/render/gl/Renderer.ts
Adds Config import and constructor parameter to PointLightPass, initializing nukeSmoother from config.msPerTick(). updateLights() resets the smoother and records segments for SMOOTHED_NUKE_TYPES units. New applySmoothing() method interpolates positions and re-uploads affected light instances via gl.bufferSubData; draw() calls it before the instanced draw. GPURenderer passes config at the PointLightPass call site.
Test harness updates
tests/client/render/gl/UnitPassSmoothing.test.ts
makeSmoothingHarness imports NukeSmoother, stubs performance.now to control wall-clock progress, creates and resets a NukeSmoother, records segment tuples directly, and injects nukeSmoother plus missileCount into the UnitPass instance, replacing legacy field injection.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • openfrontio/OpenFrontIO#4255: This PR refactors the missile position smoothing originally introduced in #4255 by extracting the flat tuple-based logic into a dedicated NukeSmoother utility class and applying the same pattern to PointLightPass light interpolation.

Poem

🚀 Smoothing logic breaks free from the pass,
A NukeSmoother handles the mass.
Both Unit and PointLight now delegate,
Position segments interpolate. ✨
Wall-clock progress drives the blend,
One utility, two passes—refactored end!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: adding frame-by-frame smoothing for nuke point-light positions in ambient/night mode, which is the core objective of this PR.
Description check ✅ Passed The description provides comprehensive context about the problem (desynchronization between sprite and light movement) and the solution (mirroring UnitPass smoothing in PointLightPass), directly relating to all changes in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

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

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 16, 2026

@coderabbitai coderabbitai Bot left a comment

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.

Caution

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

⚠️ Outside diff range comments (1)
tests/client/render/gl/UnitPassSmoothing.test.ts (1)

34-74: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Use setup() helper instead of manual UnitPass prototype wiring in tests.

This harness directly constructs UnitPass via Object.create(...) and custom field injection, which bypasses the required test setup path for tests/**/*.test.ts. Please migrate this harness to use tests/util/Setup.ts and drive smoothing through the simulation-oriented setup flow.

As per coding guidelines: “tests/**/*.test.ts: Use the setup() helper from tests/util/Setup.ts to create test game instances and exercise core simulation directly.”

🤖 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 `@tests/client/render/gl/UnitPassSmoothing.test.ts` around lines 34 - 74, The
makeSmoothingHarness function manually constructs a UnitPass instance using
Object.create(UnitPass.prototype) and Object.assign to inject properties, which
bypasses the required test setup path. Replace this manual prototype wiring with
a call to the setup() helper from tests/util/Setup.ts to create a properly
initialized test game instance. Update makeSmoothingHarness to use the
simulation-oriented setup flow, which will ensure the UnitPass is correctly
initialized through the standard testing infrastructure rather than bypassing it
with direct property injection.

Source: Coding guidelines

🤖 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.

Outside diff comments:
In `@tests/client/render/gl/UnitPassSmoothing.test.ts`:
- Around line 34-74: The makeSmoothingHarness function manually constructs a
UnitPass instance using Object.create(UnitPass.prototype) and Object.assign to
inject properties, which bypasses the required test setup path. Replace this
manual prototype wiring with a call to the setup() helper from
tests/util/Setup.ts to create a properly initialized test game instance. Update
makeSmoothingHarness to use the simulation-oriented setup flow, which will
ensure the UnitPass is correctly initialized through the standard testing
infrastructure rather than bypassing it with direct property injection.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ab1f08d2-033f-417b-993d-ba26b0444a01

📥 Commits

Reviewing files that changed from the base of the PR and between af930dc and ebb1b08.

📒 Files selected for processing (4)
  • src/client/render/gl/passes/PointLightPass.ts
  • src/client/render/gl/passes/UnitPass.ts
  • src/client/render/gl/utils/NukeSmoothing.ts
  • tests/client/render/gl/UnitPassSmoothing.test.ts

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 16, 2026
@evanpelle
evanpelle force-pushed the fix-ambient-light-nuke-smoothing branch from ebb1b08 to af930dc Compare June 16, 2026 17:59
@evanpelle evanpelle added this to the v32 milestone Jun 16, 2026
@evanpelle
evanpelle merged commit 6c84919 into main Jun 16, 2026
21 of 26 checks passed
@github-project-automation github-project-automation Bot moved this from Triage to Complete in OpenFront Release Management Jun 16, 2026
@evanpelle
evanpelle deleted the fix-ambient-light-nuke-smoothing branch June 16, 2026 23:54
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