Skip to content

fix: smooth blind spot monitor - #685

Merged
tariknz merged 6 commits into
mainfrom
fix/smooth-positional-widgets
Aug 9, 2026
Merged

fix: smooth blind spot monitor #685
tariknz merged 6 commits into
mainfrom
fix/smooth-positional-widgets

Conversation

@tariknz

@tariknz tariknz commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Description

Fixes two positional-widget regressions observed after the Phase 4 performance profile:

  • Adds a minimal dedicated 60 Hz blind-spot snapshot containing CarLeftRight, full-precision CarIdxLapDistPct, and IsOnTrack from the same telemetry frame. This restores prompt overlap detection and smooth movement without raising the general track-state channel rate.
  • Stores previous blind-spot percentages in a ref to prevent the active monitor from entering a render/effect feedback loop and repeatedly remounting.
  • Interpolates track-map driver progress between the existing 25 Hz positional snapshots, including shortest-path interpolation across the start/finish boundary.
  • Adds processor, hook, runtime-definition, and boundary-replay regression coverage.

This follows the Phase 4 channel-snapshot topology while restoring the presentation smoothness lost during the channel migration.

Tested live in iRacing by reproducing the original flashing blind-spot monitor and confirming the dedicated high-frequency path substantially improves its behavior.

Screenshots

Before

Blind-spot indicators flashed/repeatedly remounted while a car overlapped, and track-map markers moved in visible 25 Hz steps.

After

Blind-spot overlap and distance telemetry update coherently at 60 Hz, and track-map markers interpolate smoothly between positional snapshots.

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Performance improvement
  • Refactoring (no functional changes)
  • Documentation update
  • Dependency update

Checklist

  • I have discussed this change in the discord server
  • I have tested this in iRacing (either in an online session or with AI)
  • All tests pass locally via npm test
  • I have added tests that prove my fix is effective or that my feature works
  • I have run npm run lint and fixed any issues
  • I have performed a self-review of my own code
  • I have added/updated Storybook stories for visual changes
  • I have updated the README.md (if applicable)
  • I have updated defaultDashboard.ts if introducing new widgets or configurations (if applicable)

Summary by CodeRabbit

  • New Features

    • Added a dedicated blind-spot telemetry channel updating at 25 Hz.
    • Improved blind-spot monitoring with accurate nearby-car positions and on-track status.
    • Added state handling for cars entering, leaving, or disconnecting from a session.
  • Bug Fixes

    • Reduced unnecessary monitor updates and improved position-change handling.
    • Preserved blind-spot visibility behavior across demo mode, session visibility, and on-track settings.
  • Tests

    • Added coverage for telemetry processing, widget subscriptions, state updates, and reset behavior.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The PR adds a versioned blind-spot.snapshot telemetry channel. A new processor publishes blind-spot state at 25 Hz. The frontend monitor consumes this channel, tracks transition percentages with a ref, and validates runtime subscriptions and lifecycle behavior.

Blind-spot snapshot flow

Layer / File(s) Summary
Snapshot contract and processor
src/types/channels/channel.ts, src/app/processors/BlindSpotProcessor.ts, src/app/processors/processorRegistry.ts, src/app/perfMetrics.ts, src/app/processors/BlindSpotProcessor.spec.ts
Defines the snapshot payload and rate limits. Processes telemetry and lifecycle resets. Registers processor metrics and tests state changes.
Frontend blind-spot channel integration
src/frontend/context/ChannelStore/*, src/frontend/components/BlindSpotMonitor/*
Moves the monitor to blind-spot.snapshot. Uses ref-backed transition tracking and reads isOnTrack from the selected snapshot.
Runtime channel wiring and coverage
src/frontend/components/BlindSpotMonitor/widgetRuntimeDefinition.ts, src/frontend/widgetRuntime.spec.tsx, src/runtimeBoundaryReplay.spec.ts
Subscribes the widget to the new channel at 25 Hz and extends runtime coverage.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Telemetry
  participant BlindSpotProcessor
  participant SnapshotChannel
  participant BlindSpotMonitor
  Telemetry->>BlindSpotProcessor: telemetry frame
  BlindSpotProcessor->>SnapshotChannel: publish blind-spot.snapshot at 25 Hz
  SnapshotChannel->>BlindSpotMonitor: provide selected snapshot
  BlindSpotMonitor->>BlindSpotMonitor: calculate blind-spot percentages
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly identifies the primary fix to the blind-spot monitor and is concise.
Description check ✅ Passed The description explains the changes, testing, screenshots, change types, and checklist status in the required structure.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/smooth-positional-widgets

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.

@tariknz
tariknz marked this pull request as ready for review August 9, 2026 07:31
@tariknz

tariknz commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8f5b39751c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


export class BlindSpotProcessor implements TelemetryProcessor<BlindSpotSnapshot> {
readonly channel = 'blind-spot.snapshot';
readonly tickRateHz = 60;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep the positional blind-spot processor at 25 Hz

When the blind-spot widget is enabled, every full-precision CarIdxLapDistPct change increments the snapshot version, so this setting serializes and delivers the complete position array at 60 Hz. This is a positional channel rather than an input-style channel, and the repository's hard architecture rules cap positional processors at 25 Hz; use the existing 40 ms indicator transition to smooth those samples instead of doubling the processor and IPC workload.

AGENTS.md reference: AGENTS.md:L12-L18

Useful? React with 👍 / 👎.

1,
(now - startedAt) / POSITION_UPDATE_INTERVAL_MS
);
const nextDrivers = driversTrackData.map((entry) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid allocating driver snapshots inside each animation frame

Whenever map positions update, each animation frame maps the entire field into a new array and clones every existing driver entry, then stores that array in React state; both map variants consequently rerender and recalculate/redraw their canvases multiple times per 25 Hz telemetry tick. This violates the repository's hard prohibition on unbenchmarked per-frame allocations in hot paths and scales poorly with driver count or multiple map widgets; keep interpolation state in reusable refs/buffers and drive only the canvas draw instead.

AGENTS.md reference: AGENTS.md:L12-L18

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2734d93ba1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

});

displayedDriversRef.current = nextDrivers;
setSmoothedDrivers(nextDrivers);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Memoize the qualifying fallback before animation

When QualifyResultsInfo.Results is empty but QualifyPositions exists (the heat-race fallback), its inline .map() creates a new qualifyingResults array on every render. Each animation frame calls setSmoothedDrivers here, causing another render, which recreates driverIdentities and driversTrackData, restarts this effect, and schedules another frame even after the position sample has settled. Consequently both track-map canvases redraw indefinitely at display refresh rate; memoize the fallback so driversTrackData changes only when its actual inputs change.

AGENTS.md reference: AGENTS.md:L153-L157

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ad9f666332

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}: ProcessorRegistryOptions): readonly AnyProcessorDefinition[] => [
defineProcessor({
channel: 'blind-spot.snapshot',
metricsPrefix: 'blindSpot',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add blind-spot to processor execution metrics

When performance metrics are enabled for this 60 Hz processor, ProcessorHost records the blindSpotProcessing section, but perfMetrics.ts does not map that label in PROCESSOR_CHANNELS. As a result, generated channelMetrics.processorExecutions reports no executions for blind-spot.snapshot, so the performance analyzer shows a zero processor rate and cannot accurately evaluate this new hot path; add the corresponding section-to-channel mapping.

AGENTS.md reference: AGENTS.md:L12-L18

Useful? React with 👍 / 👎.

export const useBlindSpotMonitor = (): BlindSpotMonitorState => {
const [carLeftRight, lapDistPcts, isOnTrack] =
useTrackStateSelector(selectBlindSpotTelemetry, {
const [carLeftRight, lapDistPcts, isOnTrack] = useBlindSpotSelector(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Stop retaining the track-state subscription

When Blind Spot Monitor is the only positional widget, adding this selector activates the new blind-spot processor while BlindSpotMonitor.tsx still calls useTrackStateSelector(trackStateSelectors.isOnTrack) for its visibility check. Runtime channel metadata does not suppress hook subscriptions, so both the 60 Hz blind-spot processor and the 25 Hz track-state processor remain active and isOnTrack is delivered twice, negating the intended replacement of the general positional channel; use the dedicated snapshot's on-track value for that visibility decision as well.

Useful? React with 👍 / 👎.

@tariknz

tariknz commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@codex rereview

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aad9047a37

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

channels: ['track-state.snapshot'],
channels: ['blind-spot.snapshot'],
ratePreset: 'driverFocused',
channelRates: { 'blind-spot.snapshot': 60 },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Align the requested rate with the 25 Hz source

In the live iRacing path, publishIRacingSDKEvents sleeps for 1000 / 25 after every telemetry frame (src/app/bridge/iracingSdk/iracingSdkBridge.ts:295-297), so BlindSpotProcessor.onFrame cannot receive 60 frames per second and this request cannot provide the claimed 60 Hz overlap or movement updates. Fresh evidence beyond the earlier processor-rate comment is this fixed upstream 40 ms throttle: advertising 60 Hz here creates a false runtime/performance contract without increasing data cadence; keep the positional channel at 25 Hz and smooth those samples in presentation instead.

AGENTS.md reference: AGENTS.md:L12-L18

Useful? React with 👍 / 👎.

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

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/frontend/components/BlindSpotMonitor/widgetRuntimeDefinition.ts`:
- Line 8: Update the blind-spot.snapshot channel definition’s maxRateHz in the
channel configuration to 60 before changing the related processor, runtime
definition, or test values; then align those consumers with the updated maximum
while preserving the existing channel-rate behavior.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3202bca8-4195-434c-ad16-7d6123e9cfe5

📥 Commits

Reviewing files that changed from the base of the PR and between 8f5b397 and a3ffdce.

📒 Files selected for processing (9)
  • src/app/perfMetrics.ts
  • src/app/processors/BlindSpotProcessor.spec.ts
  • src/app/processors/BlindSpotProcessor.ts
  • src/frontend/components/BlindSpotMonitor/BlindSpotMonitor.tsx
  • src/frontend/components/BlindSpotMonitor/hooks/useBlindSpotMonitor.spec.tsx
  • src/frontend/components/BlindSpotMonitor/hooks/useBlindSpotMonitor.tsx
  • src/frontend/components/BlindSpotMonitor/widgetRuntimeDefinition.ts
  • src/frontend/widgetRuntime.spec.tsx
  • src/types/channels/channel.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/types/channels/channel.ts
  • src/app/processors/BlindSpotProcessor.ts
  • src/frontend/components/BlindSpotMonitor/hooks/useBlindSpotMonitor.spec.tsx
  • src/frontend/components/BlindSpotMonitor/hooks/useBlindSpotMonitor.tsx

channels: ['track-state.snapshot'],
channels: ['blind-spot.snapshot'],
ratePreset: 'driverFocused',
channelRates: { 'blind-spot.snapshot': 25 },

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.

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 2 'blind-spot\.snapshot|tickRateHz|channelRates' src

Repository: tariknz/irdashies

Length of output: 18133


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- BlindSpotProcessor ---\n'
sed -n '1,80p' src/app/processors/BlindSpotProcessor.ts

printf '\n--- BlindSpotMonitor runtime definition ---\n'
sed -n '1,40p' src/frontend/components/BlindSpotMonitor/widgetRuntimeDefinition.ts

printf '\n--- Widget runtime handling ---\n'
sed -n '90,120p' src/frontend/widgetRuntime.tsx
sed -n '155,175p' src/frontend/widgetRuntime.tsx

printf '\n--- Channel default rate ---\n'
sed -n '295,310p' src/types/channels/channel.ts

Repository: tariknz/irdashies

Length of output: 246


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- BlindSpotProcessor ---'
sed -n '1,80p' src/app/processors/BlindSpotProcessor.ts

printf '%s\n' ''
printf '%s\n' '--- BlindSpotMonitor runtime definition ---'
sed -n '1,40p' src/frontend/components/BlindSpotMonitor/widgetRuntimeDefinition.ts

printf '%s\n' ''
printf '%s\n' '--- Widget runtime handling ---'
sed -n '90,120p' src/frontend/widgetRuntime.tsx
sed -n '155,175p' src/frontend/widgetRuntime.tsx

printf '%s\n' ''
printf '%s\n' '--- Channel default rate ---'
sed -n '295,310p' src/types/channels/channel.ts

Repository: tariknz/irdashies

Length of output: 3958


Align the blind-spot rate with the max channel rate.

src/types/channels/channel.ts sets maxRateHz for blind-spot.snapshot to 25 Hz. If 60 Hz is required, increase maxRateHz in the channel definition before changing the processor, runtime definition, and test.

🤖 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/frontend/components/BlindSpotMonitor/widgetRuntimeDefinition.ts` at line
8, Update the blind-spot.snapshot channel definition’s maxRateHz in the channel
configuration to 60 before changing the related processor, runtime definition,
or test values; then align those consumers with the updated maximum while
preserving the existing channel-rate behavior.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a3ffdce295

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +14 to +15
const processor = new BlindSpotProcessor();
processor.onFrame(frame(2, [0.123456, 0.123789]));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Drive the processor test through the required lifecycle

The new processor coverage constructs BlindSpotProcessor and calls onFrame directly with a hand-built object; none of these tests calls init(session) or drives a recorded frame sequence. This leaves activation/session initialization and compatibility with recorded telemetry shapes untested, contrary to the mandatory fixture-backed init → onFrame* → snapshot sequence in docs/ARCHITECTURE_RULES.md R14.1; add coverage that exercises the complete lifecycle.

AGENTS.md reference: AGENTS.md:L12-L18

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot mentioned this pull request Aug 9, 2026
30 tasks
@tariknz tariknz changed the title fix: smooth blind spot monitor and track map fix: smooth blind spot monitor Aug 9, 2026
@tariknz
tariknz merged commit ee04588 into main Aug 9, 2026
7 checks passed
@tariknz
tariknz deleted the fix/smooth-positional-widgets branch August 9, 2026 09:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant