Skip to content

feat: move positional status to channels - #669

Merged
tariknz merged 6 commits into
mainfrom
feat/positional-status-channels
Aug 8, 2026
Merged

feat: move positional status to channels#669
tariknz merged 6 commits into
mainfrom
feat/positional-status-channels

Conversation

@tariknz

@tariknz tariknz commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Description

Phase 4 of the channel-based bridge migration. Adds a demand-driven 25 Hz track-state.snapshot processor for positional, pit-state, and warning data, with reusable projection buffers and live/tape plus demo/mock runtime wiring.

Pitlane Helper, Track Map, Flat Track Map, Battle, Blind Spot Monitor, Rejoin Indicator, Faster Cars From Behind, and Slow Car Ahead now consume typed channel snapshots and opt out of the legacy renderer telemetry firehose. Session and pit-lane providers are activated independently from raw telemetry so channel-only widgets retain the session data and persisted pit entry/exit data they need.

This PR also includes the remaining SessionBar follow-up changes: InformationBar now seeds and applies session-timing.snapshot, the class-rank and manufacturer-position stories use per-story session-bar.snapshot fixtures instead of obsolete telemetry seeders, and class rank is hidden for unclassified drivers or invalid class sizes.

Input and Tachometer are also completed as channel-only consumers: their indirect driving-state dependency now declares track-state.snapshot, while their session visibility and Tachometer car metadata retain SessionProvider through explicit session-data runtime metadata. An Input/Tachometer-only renderer therefore keeps session state without restoring raw telemetry delivery.

Track-state values are normalized at the processor boundary: pit-road 0/1 flags become typed booleans and lap-distance percentages are rounded to three decimals before publication. Pitlane tests now mock typed channel snapshots directly rather than reconstructing them through legacy telemetry hooks.

The deterministic curated replay now validates 13 probes over all 36,000 frames. This was not tested against a live iRacing session.

Architecture checklist:

  • N1 — no new synchronous filesystem I/O
  • N2/N3 — no frontend-to-app or new cross-widget imports
  • N4/R4.1 — no new IPC handlers or hand-rolled bridges
  • R4.4/R4.5 — typed minimal named snapshot channel
  • R5.2/R14.1 — processor and runtime unit coverage plus recorded replay validation
  • R13.1/R13.2 — performance metrics included and per-frame arrays reused

Validation:

  • npm run lint -- --no-fix
  • npm run test -- --no-coverage — 1,210 passed, 1 skipped
  • npm run test:replay:curated — 36,000 frames, 70 session revisions, 13 probes

Screenshots

No visual changes expected; this replaces the data transport and preserves existing widget output.

Before

Affected renderers receive the legacy telemetry firehose.

After

Affected renderers subscribe only to their typed channel 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)

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds the track-state.snapshot channel, processes and publishes versioned telemetry snapshots on demand, integrates the runtime with SDK bridges, migrates frontend consumers, updates provider wiring, and adds replay validation plus Storybook fixtures.

Changes

Track-state snapshot pipeline

Layer / File(s) Summary
Snapshot contract and processing
src/types/channels/channel.ts, src/app/processors/*, tools/telemetry-replay/*, test-data/telemetry/*
Defines TrackStateSnapshot, processes telemetry with buffer reuse and versioning, publishes snapshots on demand, and validates replay output.
Bridge lifecycle integration
src/app/bridge/iracingSdk/*
Forwards session and telemetry events to TrackStateRuntime and disposes it during shutdown.
Renderer channel wiring
src/frontend/context/ChannelStore/*, src/frontend/widgetRuntime.tsx, src/frontend/components/RendererDataProviders/*
Adds useTrackStateSnapshot and separates session, pit-lane, and legacy telemetry provider requirements.
Widget and shared-hook migration
src/frontend/components/{Battle,BlindSpotMonitor,PitlaneHelper,RejoinIndicator,SlowCarAhead,TrackMap}/*, src/frontend/context/*
Moves consumers from individual telemetry hooks to snapshot channels and updates widget runtime definitions and tests.
Storybook snapshot fixtures
.storybook/*, src/frontend/components/**/*.stories.tsx
Adds reusable track-state fixtures and channel snapshot decorators.
Migration status documentation
docs/IMPLEMENTATION_PLAN.md
Records the Phase 4 migration and activity-log updates.

Estimated code review effort: 4 (Complex) | ~60 minutes

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 describes the primary migration of positional status data from legacy telemetry to typed channels.
Description check ✅ Passed The description follows the template and clearly documents scope, testing, screenshots, change types, and checklist status.
✨ 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 feat/positional-status-channels

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 8, 2026 23:08

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

🧹 Nitpick comments (3)
src/frontend/components/PitlaneHelper/PitlaneHelper.spec.tsx (1)

7-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test the typed snapshot boundary directly.

These mocks derive typed snapshots from useTelemetryValue. This keeps the
legacy telemetry interface in the test setup and can hide a regression back to
direct telemetry reads.

  • src/frontend/components/PitlaneHelper/PitlaneHelper.spec.tsx#L7-L23: Return
    independent track-state and driver-controls fixture objects.
  • src/frontend/components/PitlaneHelper/components/PitExitInputs.spec.tsx#L7-L16:
    Return a direct driver-controls fixture and replace telemetry-call assertions
    with snapshot-hook assertions.
  • src/frontend/components/PitlaneHelper/hooks/usePitLimiterWarning.spec.tsx#L7-L19:
    Return a direct track-state fixture for each hook state transition.
🤖 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/PitlaneHelper/PitlaneHelper.spec.tsx` around lines 7
- 23, Update the test mocks to exercise typed snapshot hooks directly: in
src/frontend/components/PitlaneHelper/PitlaneHelper.spec.tsx lines 7-23, return
independent track-state and driver-controls fixtures; in
src/frontend/components/PitlaneHelper/components/PitExitInputs.spec.tsx lines
7-16, return a direct driver-controls fixture and assert snapshot-hook calls
instead of telemetry calls; in
src/frontend/components/PitlaneHelper/hooks/usePitLimiterWarning.spec.tsx lines
7-19, return direct track-state fixtures for each hook state transition. Remove
reliance on useTelemetryValue in these setup paths so regressions to direct
telemetry reads are exposed.
src/frontend/components/Standings/components/SessionBar/components/ClassRankItem/ClassRankItem.tsx (1)

11-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Set a display name for ClassRankItem.

Add ClassRankItem.displayName = 'ClassRankItem'; after the memoized component declaration.

As per coding guidelines, memoized components require a displayName.

Proposed fix
 export const ClassRankItem = memo(({ standalone }: SessionBarItemProps) => {
   // ...
 });
+
+ClassRankItem.displayName = 'ClassRankItem';
🤖 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/Standings/components/SessionBar/components/ClassRankItem/ClassRankItem.tsx`
at line 11, Set the display name on the memoized ClassRankItem component by
adding ClassRankItem.displayName = 'ClassRankItem' immediately after its
declaration.

Source: Coding guidelines

src/frontend/context/shared/useFocusCarIdx.ts (1)

2-2: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the configured context path alias.

Replace the relative ChannelStore import with the @irdashies/context alias.

As per coding guidelines, use TypeScript path aliases for context imports.

Proposed fix
-import { useTrackStateSnapshot } from '../ChannelStore';
+import { useTrackStateSnapshot } from '`@irdashies/context`';
🤖 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/context/shared/useFocusCarIdx.ts` at line 2, Update the import
in useFocusCarIdx.ts to replace the relative ChannelStore path with the
configured `@irdashies/context` alias, while preserving the existing
useTrackStateSnapshot import.

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.

Inline comments:
In @.storybook/trackStateSnapshot.ts:
- Around line 3-27: Update the trackStateStorySnapshot fixture so focusCarIdx is
consistent with the empty carIdx* arrays: set focusCarIdx to null rather than 0,
preserving the existing empty-array fixture.

In `@docs/IMPLEMENTATION_PLAN.md`:
- Line 475: Correct the date on the PR `#669` activity-log entry to 2026-08-08 if
it documents the current PR opening; otherwise, move the entry after the
existing August 9, 2026 activity.

In `@src/app/processors/TrackStateProcessor.ts`:
- Around line 97-114: Run Prettier on the changed code: format the setter calls
in TrackStateProcessor.ts (lines 97-114), lifecycle callback registration in
trackStateRuntime.ts (lines 29-33), and test setup in
TrackStateProcessor.spec.ts (lines 45-48), preserving single quotes,
80-character lines, and ES5 trailing commas.
- Around line 82-86: Update the CarIdxOnPitRoad handling in TrackStateProcessor
to convert each SDK numeric flag to a boolean while copying into
latest.carIdxOnPitRoad, rather than relying on arrayValue<boolean>’s type
assertion. Add coverage verifying input [0, 1] is published as [false, true].

In `@src/frontend/components/PitlaneHelper/hooks/usePitboxPosition.tsx`:
- Line 27: Include useTrackStateSnapshot()?.version in the useMemo dependency
arrays for usePitboxPosition in
src/frontend/components/PitlaneHelper/hooks/usePitboxPosition.tsx:27-27 and
usePitlaneTraffic in
src/frontend/components/PitlaneHelper/hooks/usePitlaneTraffic.tsx:15-17, so
memoized pit calculations refresh for each track-state update even when the
reused arrays retain equal values.

In `@src/frontend/components/TrackMap/hooks/useDriverProgress.tsx`:
- Around line 17-18: Update the track-state flow used by useDriverProgress and
driversTrackData so carIdxLapDistPct is copied immutably and rounded to 3
decimals via useTelemetryValuesRounded. Ensure position-only updates produce a
new rounded driversLapDist reference, either by changing TrackStateProcessor or
by exposing a rounded selector that participates in snapshot memoization.

In `@src/frontend/context/PitLaneStore/usePitLaneDetection.tsx`:
- Around line 17-30: Update the pit transition detection flow in
usePitLaneDetection to trigger detectPitTransitions once for each new
trackState.version when carIdxOnPitRoad, carIdxTrackSurface, and
carIdxLapDistPct are all present. Remove the previous array-reference comparison
gate and track the last processed snapshot version instead, so reusable
projection buffers still produce detection updates.

---

Nitpick comments:
In `@src/frontend/components/PitlaneHelper/PitlaneHelper.spec.tsx`:
- Around line 7-23: Update the test mocks to exercise typed snapshot hooks
directly: in src/frontend/components/PitlaneHelper/PitlaneHelper.spec.tsx lines
7-23, return independent track-state and driver-controls fixtures; in
src/frontend/components/PitlaneHelper/components/PitExitInputs.spec.tsx lines
7-16, return a direct driver-controls fixture and assert snapshot-hook calls
instead of telemetry calls; in
src/frontend/components/PitlaneHelper/hooks/usePitLimiterWarning.spec.tsx lines
7-19, return direct track-state fixtures for each hook state transition. Remove
reliance on useTelemetryValue in these setup paths so regressions to direct
telemetry reads are exposed.

In
`@src/frontend/components/Standings/components/SessionBar/components/ClassRankItem/ClassRankItem.tsx`:
- Line 11: Set the display name on the memoized ClassRankItem component by
adding ClassRankItem.displayName = 'ClassRankItem' immediately after its
declaration.

In `@src/frontend/context/shared/useFocusCarIdx.ts`:
- Line 2: Update the import in useFocusCarIdx.ts to replace the relative
ChannelStore path with the configured `@irdashies/context` alias, while preserving
the existing useTrackStateSnapshot import.
🪄 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: a15c0c2f-a2be-424b-9f22-9195ebbc952c

📥 Commits

Reviewing files that changed from the base of the PR and between fa37ec4 and 79a559e.

📒 Files selected for processing (71)
  • .storybook/index.ts
  • .storybook/trackStateSnapshot.ts
  • docs/IMPLEMENTATION_PLAN.md
  • src/app/bridge/iracingSdk/iracingSdkBridge.ts
  • src/app/bridge/iracingSdk/mock-data/mockSdkBridge.ts
  • src/app/processors/TrackStateProcessor.spec.ts
  • src/app/processors/TrackStateProcessor.ts
  • src/app/processors/trackStateRuntime.spec.ts
  • src/app/processors/trackStateRuntime.ts
  • src/frontend/components/Battle/Battle.stories.tsx
  • src/frontend/components/Battle/Battle.tsx
  • src/frontend/components/Battle/widgetRuntimeDefinition.ts
  • src/frontend/components/BlindSpotMonitor/BlindSpotMonitor.tsx
  • src/frontend/components/BlindSpotMonitor/hooks/useBlindSpotMonitor.tsx
  • src/frontend/components/BlindSpotMonitor/widgetRuntimeDefinition.ts
  • src/frontend/components/FasterCarsFromBehind/FasterCarsFromBehind.stories.tsx
  • src/frontend/components/FasterCarsFromBehind/widgetRuntimeDefinition.ts
  • src/frontend/components/FlatTrackMap/widgetRuntimeDefinition.ts
  • src/frontend/components/InformationBar/InformationBar.stories.tsx
  • src/frontend/components/Input/Input.stories.tsx
  • src/frontend/components/Input/widgetRuntimeDefinition.ts
  • src/frontend/components/PitlaneHelper/PitlaneHelper.spec.tsx
  • src/frontend/components/PitlaneHelper/PitlaneHelper.tsx
  • src/frontend/components/PitlaneHelper/components/PitExitInputs.spec.tsx
  • src/frontend/components/PitlaneHelper/components/PitExitInputs.tsx
  • src/frontend/components/PitlaneHelper/hooks/usePitLimiterWarning.spec.tsx
  • src/frontend/components/PitlaneHelper/hooks/usePitLimiterWarning.tsx
  • src/frontend/components/PitlaneHelper/hooks/usePitSpeed.tsx
  • src/frontend/components/PitlaneHelper/hooks/usePitboxPosition.tsx
  • src/frontend/components/PitlaneHelper/hooks/usePitlaneTraffic.tsx
  • src/frontend/components/PitlaneHelper/hooks/usePitlaneVisibility.tsx
  • src/frontend/components/PitlaneHelper/widgetRuntimeDefinition.ts
  • src/frontend/components/RejoinIndicator/RejoinIndicator.tsx
  • src/frontend/components/RejoinIndicator/widgetRuntimeDefinition.ts
  • src/frontend/components/RendererDataProviders/RendererDataProviders.spec.tsx
  • src/frontend/components/RendererDataProviders/RendererDataProviders.tsx
  • src/frontend/components/SlowCarAhead/hooks/useSlowCarAhead.tsx
  • src/frontend/components/SlowCarAhead/widgetRuntimeDefinition.ts
  • src/frontend/components/Standings/components/SessionBar/SessionBarClassRank.stories.tsx
  • src/frontend/components/Standings/components/SessionBar/SessionBarManufacturerPosition.stories.tsx
  • src/frontend/components/Standings/components/SessionBar/components/ClassRankItem/ClassRankItem.spec.tsx
  • src/frontend/components/Standings/components/SessionBar/components/ClassRankItem/ClassRankItem.tsx
  • src/frontend/components/Tachometer/Tachometer.stories.tsx
  • src/frontend/components/Tachometer/widgetRuntimeDefinition.ts
  • src/frontend/components/TrackMap/FlatTrackMap.spec.tsx
  • src/frontend/components/TrackMap/FlatTrackMap.stories.tsx
  • src/frontend/components/TrackMap/FlatTrackMap.tsx
  • src/frontend/components/TrackMap/FlatTrackMapCanvas.tsx
  • src/frontend/components/TrackMap/TrackCanvas.stories.tsx
  • src/frontend/components/TrackMap/TrackCanvas.tsx
  • src/frontend/components/TrackMap/TrackMap.spec.tsx
  • src/frontend/components/TrackMap/TrackMap.stories.tsx
  • src/frontend/components/TrackMap/TrackMap.tsx
  • src/frontend/components/TrackMap/hooks/useDriverProgress.spec.tsx
  • src/frontend/components/TrackMap/hooks/useDriverProgress.tsx
  • src/frontend/components/TrackMap/trackDrawingUtils.ts
  • src/frontend/components/TrackMap/widgetRuntimeDefinition.ts
  • src/frontend/context/ChannelStore/index.ts
  • src/frontend/context/ChannelStore/useTrackStateSnapshot.ts
  • src/frontend/context/PitLaneStore/PitLaneStore.ts
  • src/frontend/context/PitLaneStore/usePitLaneDetection.tsx
  • src/frontend/context/shared/useCarIdxOffTrack.tsx
  • src/frontend/context/shared/useDrivingState.spec.ts
  • src/frontend/context/shared/useDrivingState.ts
  • src/frontend/context/shared/useFocusCarIdx.ts
  • src/frontend/widgetRuntime.spec.tsx
  • src/frontend/widgetRuntime.tsx
  • src/types/channels/channel.ts
  • test-data/telemetry/ai-race-10min.golden.json
  • tools/telemetry-replay/run-curated-validation.ts
  • tools/telemetry-replay/track-state-probe.ts

Comment thread .storybook/trackStateSnapshot.ts

## 6. Activity log

- **2026-08-09** — PR #668 merged. Opened PR #669 for the second final Phase 4 slice: a reusable-buffer, demand-driven 25 Hz `track-state.snapshot` for positional, pit-state, and warning data; migrated Pitlane Helper, Track Map, Flat Track Map, Battle, Blind Spot, Rejoin, Faster Cars From Behind, and Slow Car Ahead off the legacy renderer firehose; decoupled session and pit-lane providers from raw telemetry; added processor/runtime tests, Storybook snapshots, and a thirteenth curated replay probe — `feat/positional-status-channels` — in review

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the future activity-log date.

This entry is dated 2026-08-09, but the current date is 2026-08-08. If this entry documents the current PR opening, use 2026-08-08. Otherwise, add the entry after August 9, 2026.

🤖 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 `@docs/IMPLEMENTATION_PLAN.md` at line 475, Correct the date on the PR `#669`
activity-log entry to 2026-08-08 if it documents the current PR opening;
otherwise, move the entry after the existing August 9, 2026 activity.

Comment thread src/app/processors/TrackStateProcessor.ts
Comment thread src/app/processors/TrackStateProcessor.ts Outdated
const session = useSessionStore((state) => state.session);
const focusCarIdx = useFocusCarIdx();
const carIdxLapDistPct = useTelemetryValuesRounded('CarIdxLapDistPct', 3);
const carIdxLapDistPct = useTrackStateSnapshot()?.carIdxLapDistPct;

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline src/app/processors/TrackStateProcessor.ts --items all
rg -n -C 5 \
  'carIdxLapDistPct|carIdxOnPitRoad|projection|buffer|version|snapshot' \
  src/app/processors/TrackStateProcessor.ts \
  src/types/channels/channel.ts

rg -n -C 5 \
  'useMemo|carIdxLapDistPct|carIdxOnPitRoad|version' \
  src/frontend/components/PitlaneHelper/hooks/usePitboxPosition.tsx \
  src/frontend/components/PitlaneHelper/hooks/usePitlaneTraffic.tsx

Repository: tariknz/irdashies

Length of output: 28021


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- TrackStateProcessor relevant sections ---\n'
sed -n '1,170p' src/app/processors/TrackStateProcessor.ts

printf '\n--- usePitboxPosition relevant sections ---\n'
sed -n '1,170p' src/frontend/components/PitlaneHelper/hooks/usePitboxPosition.tsx

printf '\n--- usePitlaneTraffic relevant sections ---\n'
sed -n '1,75p' src/frontend/components/PitlaneHelper/hooks/usePitlaneTraffic.tsx

printf '\n--- behavioral probe for copyArray identity and useMemo key semantics ---\n'
python3 - <<'PY'
class Processor:
    def __init__(self):
        self.latest = {
            'carIdxLapDistPct': [],
            'carIdxOnPitRoad': [],
            'version': 0,
        }

    def copyArray(self, target, source):
        if len(target) != len(source):
            target[:] = [0] * len(source)
        changed = False
        for i, value in enumerate(source):
            if target[i] != value:
                target[i] = value
                changed = True
        return changed

    def onFrame(self, carIdxLapDistPct_source=None, carIdxOnPitRoad_source=None):
        changed = False
        if carIdxLapDistPct_source is not None:
            changed = self.copyArray(self.latest['carIdxLapDistPct'], carIdxLapDistPct_source) or changed
        if carIdxOnPitRoad_source is not None:
            changed = self.copyArray(self.latest['carIdxOnPitRoad'], carIdxOnPitRoad_source) or changed
        if changed:
            self.latest['version'] += 1

processor = Processor()
carIdxLapDistPct = [0.1]
carIdxOnPitRoad = [True]
processor.onFrame(carIdxOnPitRoad=carIdxOnPitRoad)
first_pit = id(processor.latest['carIdxOnPitRoad'])
processor.onFrame(carIdxOnPitRoad=carIdxOnPitRoad)
second_pit = id(processor.latest['carIdxOnPitRoad'])
print(first_pit, second_pit, first_pit == second_pit)
print('version', processor.latest['version'])
print('snapshot object identity', id(processor.latest))
print('snapshot carIdxOnPitRoad identity same', id(processor.latest['carIdxOnPitRoad']))
print('value after no change', processor.latest['carIdxOnPitRoad'])
PY

Repository: tariknz/irdashies

Length of output: 246


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- TrackStateProcessor relevant sections ---'
sed -n '1,170p' src/app/processors/TrackStateProcessor.ts

printf '%s\n' ''
printf '%s\n' '--- usePitboxPosition relevant sections ---'
sed -n '1,170p' src/frontend/components/PitlaneHelper/hooks/usePitboxPosition.tsx

printf '%s\n' ''
printf '%s\n' '--- usePitlaneTraffic relevant sections ---'
sed -n '1,75p' src/frontend/components/PitlaneHelper/hooks/usePitlaneTraffic.tsx

printf '%s\n' ''
printf '%s\n' '--- behavioral probe for copyArray identity and useMemo key semantics ---'
python3 - <<'PY'
class Processor:
    def __init__(self):
        self.latest = {
            'carIdxLapDistPct': [],
            'carIdxOnPitRoad': [],
            'version': 0,
        }

    def copyArray(self, target, source):
        if len(target) != len(source):
            target[:] = [0] * len(source)
        changed = False
        for i, value in enumerate(source):
            if target[i] != value:
                target[i] = value
                changed = True
        return changed

    def onFrame(self, carIdxLapDistPct_source=None, carIdxOnPitRoad_source=None):
        changed = False
        if carIdxLapDistPct_source is not None:
            changed = self.copyArray(self.latest['carIdxLapDistPct'], carIdxLapDistPct_source) or changed
        if carIdxOnPitRoad_source is not None:
            changed = self.copyArray(self.latest['carIdxOnPitRoad'], carIdxOnPitRoad_source) or changed
        if changed:
            self.latest['version'] += 1

processor = Processor()
carIdxLapDistPct = [0.1]
carIdxOnPitRoad = [True]
processor.onFrame(carIdxOnPitRoad=carIdxOnPitRoad)
first_pit = id(processor.latest['carIdxOnPitRoad'])
processor.onFrame(carIdxOnPitRoad=carIdxOnPitRoad)
second_pit = id(processor.latest['carIdxOnPitRoad'])
print(first_pit, second_pit, first_pit == second_pit)
print('version', processor.latest['version'])
print('snapshot object identity', id(processor.latest))
print('snapshot carIdxOnPitRoad identity same', id(processor.latest['carIdxOnPitRoad']))
print('value after no change', processor.latest['carIdxOnPitRoad'])
PY

Repository: tariknz/irdashies

Length of output: 13653


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
class Processor:
    def __init__(self):
        self.latest = {
            'carIdxLapDistPct': [],
            'carIdxOnPitRoad': [],
            'version': 0,
        }

    def copyArray(self, target, source):
        changed = len(target) != len(source)
        if len(target) != len(source):
            target[:] = [0] * len(source)
        for index in range(len(source)):
            if target[index] != source[index]:
                target[index] = source[index]
                changed = True
        return changed

    def onFrame(self, *, carIdxLapDistPct_source=None, carIdxOnPitRoad_source=None):
        changed = False
        if carIdxLapDistPct_source is not None:
            changed = self.copyArray(self.latest['carIdxLapDistPct'], carIdxLapDistPct_source) or changed
        if carIdxOnPitRoad_source is not None:
            changed = self.copyArray(self.latest['carIdxOnPitRoad'], carIdxOnPitRoad_source) or changed
        if changed:
            self.latest['version'] += 1

processor = Processor()
array_identity = lambda key: id(processor.latest[key])
memo_key_equal = lambda keys, new_values: all(v == keys[idx] for idx, v in enumerate(new_values))

array = [0.1]
processor.onFrame(carIdxLapDistPct_source=array)
first = array_identity('carIdxLapDistPct')
processor.onFrame(carIdxLapDistPct_source=array)
second = array_identity('carIdxLapDistPct')
print('same_array_identity', first == second)
print('current_version', processor.latest['version'])
print('useMemo_dependents_equal', memo_key_equal(
    [first, 'same-session'],
    [second, 'same-session'],
))
print('snapshot_reused_by_reference', id(processor.latest) == id(processor.snapshot()))
PY

Repository: tariknz/irdashies

Length of output: 365


Include the track-state version in the pitlane memo keys.

TrackStateProcessor reuses carIdxLapDistPct and carIdxOnPitRoad by mutating existing arrays. If the values in both frames stay the same, the memo key stays the same and the pit calculations can reuse the previous result. Include useTrackStateSnapshot()?.version in the useMemo dependency arrays for usePitboxPosition and usePitlaneTraffic.

📍 Affects 2 files
  • src/frontend/components/PitlaneHelper/hooks/usePitboxPosition.tsx#L27-L27 (this comment)
  • src/frontend/components/PitlaneHelper/hooks/usePitlaneTraffic.tsx#L15-L17
🤖 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/PitlaneHelper/hooks/usePitboxPosition.tsx` at line
27, Include useTrackStateSnapshot()?.version in the useMemo dependency arrays
for usePitboxPosition in
src/frontend/components/PitlaneHelper/hooks/usePitboxPosition.tsx:27-27 and
usePitlaneTraffic in
src/frontend/components/PitlaneHelper/hooks/usePitlaneTraffic.tsx:15-17, so
memoized pit calculations refresh for each track-state update even when the
reused arrays retain equal values.

Comment on lines +17 to +18
const trackState = useTrackStateSnapshot();
const driversLapDist = trackState?.carIdxLapDistPct ?? EMPTY_POSITIONS;

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline src/app/processors/TrackStateProcessor.ts --items all

rg -n -C 4 \
  'carIdxLapDistPct|version|projection|round' \
  src/app/processors/TrackStateProcessor.ts \
  src/frontend/context/ChannelStore/useTrackStateSnapshot.ts \
  src/frontend/components/TrackMap/hooks/useDriverProgress.tsx \
  src/frontend/components/SlowCarAhead/hooks/useSlowCarAhead.tsx \
  src/frontend/context/PitLaneStore/usePitLaneDetection.tsx

Repository: tariknz/irdashies

Length of output: 11768


🏁 Script executed:

#!/bin/bash
set -euo pipefail

wc -l src/app/processors/TrackStateProcessor.ts src/frontend/components/TrackMap/hooks/useDriverProgress.tsx src/frontend/theme/theme.css src/frontend/utils/*.ts src/frontend/utils/*.tsx 2>/dev/null || true
rg -n -C 6 'function copyArray|copyArray=|copyArray\(' src app frontend 2>/dev/null || true

sed -n '1,170p' src/app/processors/TrackStateProcessor.ts
sed -n '1,140p' src/frontend/components/TrackMap/hooks/useDriverProgress.tsx

python3 - <<'PY'
import ast, pathlib
files = [pathlib.Path('src/app/processors/TrackStateProcessor.ts'), pathlib.Path('src/frontend/components/TrackMap/hooks/useDriverProgress.tsx')]
for path in files:
    if not path.exists():
        continue
    text = path.read_text()
    try:
        tree = ast.parse(text, filename=str(path))
    except SyntaxError as exc:
        print(f"\n[PARSE_ERROR] {path}: {exc}")
        continue
    print(f"\n[AST_EXPORTS_AND_USE_DRIVER_PROGRESS] {path}")
    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef) and node.name == 'useDriverProgress':
            print(ast.dump(node, indent=2)[:1200])
    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef) and node.name == 'copyArray':
            print(ast.dump(node, indent=2)[:1200])
PY

Repository: tariknz/irdashies

Length of output: 13598


Emit a rounded immutable car idx lap-dist array for track-map position.

TrackStateProcessor mutates latest.carIdxLapDistPct in place and exposes the same array from snapshot(), so driversTrackData can keep the same driversLapDist identity when only positions change. In addition, map positions must use useTelemetryValuesRounded with 3 decimals. Move the rounding into TrackStateProcessor or expose a rounded selector that drives the snapshot memo.

🤖 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/TrackMap/hooks/useDriverProgress.tsx` around lines 17
- 18, Update the track-state flow used by useDriverProgress and driversTrackData
so carIdxLapDistPct is copied immutably and rounded to 3 decimals via
useTelemetryValuesRounded. Ensure position-only updates produce a new rounded
driversLapDist reference, either by changing TrackStateProcessor or by exposing
a rounded selector that participates in snapshot memoization.

Source: Coding guidelines

Comment on lines +17 to +30
const trackState = useTrackStateSnapshot();
const carIdxOnPitRoad = trackState?.carIdxOnPitRoad;
const carIdxTrackSurface = trackState?.carIdxTrackSurface;
const carIdxLapDistPct = trackState?.carIdxLapDistPct;

const { currentTrackId, pitEntryPct, pitExitPct, setCurrentTrack, reset } =
usePitLaneStore();

// Use refs to track previous values and only call detectPitTransitions when data actually changes
// This prevents running expensive operations at 60 FPS when nothing has changed
const prevTelemetryRef = useRef<{
carIdxOnPitRoad?: boolean[];
carIdxTrackSurface?: number[];
carIdxLapDistPct?: number[];
carIdxOnPitRoad?: readonly boolean[];
carIdxTrackSurface?: readonly number[];
carIdxLapDistPct?: readonly number[];

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use the snapshot version to trigger pit transition detection.

The identity checks at Lines 83-87 do not detect element changes in reusable
projection buffers. Pit entry and exit detection can stop after the first
snapshot.

Trigger detectPitTransitions for each new trackState.version while all
three arrays are present. Remove the array-reference gate.

Based on PR objectives, the processor uses reusable projection buffers.

Proposed fix
   const trackState = useTrackStateSnapshot();
+  const trackStateVersion = trackState?.version;
   const carIdxOnPitRoad = trackState?.carIdxOnPitRoad;
   const carIdxTrackSurface = trackState?.carIdxTrackSurface;
   const carIdxLapDistPct = trackState?.carIdxLapDistPct;
...
-    const prev = prevTelemetryRef.current;
-    if (
-      prev.carIdxOnPitRoad !== carIdxOnPitRoad ||
-      prev.carIdxTrackSurface !== carIdxTrackSurface ||
-      prev.carIdxLapDistPct !== carIdxLapDistPct
-    ) {
-      detectPitTransitions(
-        carIdxOnPitRoad,
-        carIdxTrackSurface,
-        carIdxLapDistPct
-      );
-      prevTelemetryRef.current = {
-        carIdxOnPitRoad,
-        carIdxTrackSurface,
-        carIdxLapDistPct,
-      };
-    }
-  }, [carIdxOnPitRoad, carIdxTrackSurface, carIdxLapDistPct, trackId]);
+    detectPitTransitions(
+      carIdxOnPitRoad,
+      carIdxTrackSurface,
+      carIdxLapDistPct
+    );
+  }, [
+    carIdxOnPitRoad,
+    carIdxTrackSurface,
+    carIdxLapDistPct,
+    trackId,
+    trackStateVersion,
+  ]);
🤖 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/context/PitLaneStore/usePitLaneDetection.tsx` around lines 17 -
30, Update the pit transition detection flow in usePitLaneDetection to trigger
detectPitTransitions once for each new trackState.version when carIdxOnPitRoad,
carIdxTrackSurface, and carIdxLapDistPct are all present. Remove the previous
array-reference comparison gate and track the last processed snapshot version
instead, so reusable projection buffers still produce detection updates.

@tariknz
tariknz merged commit cc4dbe3 into main Aug 8, 2026
7 checks passed
@tariknz
tariknz deleted the feat/positional-status-channels branch August 8, 2026 23:58
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