Skip to content

feat(viz): terrain-edit invalidation for the brick-map sensor stack - #74

Merged
WomB0ComB0 merged 2 commits into
mainfrom
feat/webgpu-terrain-invalidation
Apr 28, 2026
Merged

WomB0ComB0 merged 2 commits into
mainfrom
feat/webgpu-terrain-invalidation

Conversation

@WomB0ComB0

@WomB0ComB0 WomB0ComB0 commented Apr 28, 2026 •

Copy link
Copy Markdown
Member

Summary

Closes the highest-risk loose end from PR #70 — setActivePreset and
setHeightmapOverride at runtime previously left the brick map silently
stale, so mesh-link LoS and LiDAR scans would lie about occlusion until
the next page load.

This was specifically called out as a check in the scheduled 2-week
audit (routine trig_01HKh41wYUg5Phrszwkn3vUN) — fixing it now means
that audit can downgrade it from "potential silent bug" to "verified
fixed."

Architecture (event-driven, no terrain↔WebGPU coupling)

  • terrain.ts exports onTerrainChange(listener) → unsubscribe.
    Fires after setActivePreset / setHeightmapOverride mutate the
    active height field. Listener errors are caught + logged so a single
    failing subscriber can't break the others.
  • world.ts adds rebuildWorld(device, heightFn, world):
    re-voxelizes via a shared _voxelize helper into the existing
    voxelBuf, then re-runs buildBrickMap against the existing
    top_grid / brick_pool. Zero new GPU buffer allocation — the
    transient CPU Uint32Array(N³) gets GC'd after device.queue.writeBuffer
    copies it.
  • sensors.ts subscribes after publishing the context; listener
    calls rebuildWorld(device, terrainHeight, world).
  • effects.ts subscribes too — clears _meshLinkOccluded (cached
    occlusion booleans go stale instantly on terrain change) and resets
    the LiDAR Points draw range to 0 (next scan repopulates).

Logger consolidation

Per session feedback, converted all WebGPU sensor stack console.warn /
console.info calls to the project's getLogger pattern (already used
in terrain.ts). Now consistent across webgpu/sensors, effects,
terrain. Structured { error: msg } second-arg style throughout.

Files modified

  • client/terrain.ts — onTerrainChange API + listener registry +
    _fireTerrainChange() calls in the two mutators.
  • client/webgpu/world.ts — extracts _voxelize helper, exports new
    rebuildWorld function.
  • client/webgpu/sensors.ts — subscribes after boot, calls
    rebuildWorld. All console.* converted to logger.
  • client/effects.ts — subscribes in constructor, clears caches.
    Both pre-existing console.warn calls (LiDAR + mesh-link LoS error
    handlers) converted to logger.

Bundle impact

Main JS Cap
Pre-PR (#73) 807,892 B 819,200 B
This PR 808,316 B 819,200 B

+424 B for the event API + logger imports + rebuildWorld helper.
Comfortably under cap.

Test plan

  • npm run typecheck passes
  • npm run build passes (no new warnings)
  • dotnet build -c Release passes (pre-push hook)
  • In dev, switch terrain preset via the control panel; observe:
    - mesh-link line opacity recomputes against the new terrain
    - LiDAR cyan point cloud disappears briefly then repopulates
    (next scan within ~1 s)
    - browser console shows no errors from the rebuild path
  • On a non-WebGPU browser, preset switch does what it always did
    (no listener fires, no crash, sensor stack stays disabled).

What's deferred

  • Re-running _lidarPoints.geometry.dispose() when terrain changes —
    current code just resets draw range. Geometry is reused. Fine for
    now; if memory churn surfaces, a follow-up can recreate.
  • Multiple subscribers ordering — currently insertion-order via Set.
    Adequate for two subscribers; if a third needs to run before/after a
    specific other, we'll need explicit priority levels.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved synchronization of visualization components when terrain is modified; LiDAR point cloud display and occlusion data now properly refresh on terrain updates.
    • Enhanced error logging for LiDAR scan and terrain query failures with structured diagnostic information.
  • Performance

    • Terrain updates now refresh efficiently without recreating GPU resources.

Closes the highest-risk loose end from PR #70 — `setActivePreset` and
`setHeightmapOverride` at runtime previously left the brick map silently
stale, so mesh-link LoS and LiDAR scans would lie about occlusion until
the next page load.

Architecture (event-driven, no terrain↔WebGPU coupling):
- terrain.ts exports `onTerrainChange(listener) -> unsubscribe`. Fires
  after `setActivePreset` and `setHeightmapOverride` mutate the active
  height field. Listener errors are caught + logged via the project
  logger so a failing subscriber doesn't break the others.
- world.ts adds `rebuildWorld(device, heightFn, world)` — re-voxelizes
  via a shared `_voxelize` helper into the existing `voxelBuf` and
  re-runs `buildBrickMap` against the existing top_grid / brick_pool.
  Zero new GPU buffer allocation; the transient CPU Uint32Array gets
  GC'd after `writeBuffer` copies it.
- sensors.ts subscribes to `onTerrainChange` after publishing the
  context. Listener calls `rebuildWorld` with the world it just built.
- effects.ts subscribes too, clears `_meshLinkOccluded` (cached
  occlusion booleans go stale instantly on terrain change) and resets
  the LiDAR `Points` draw range to 0 (next scan repopulates).

Logger consolidation (per session feedback "I have a logger remember
that"): converted all WebGPU sensor stack `console.warn` / `console.info`
calls to the project's `getLogger` pattern. terrain.ts already used it;
sensors.ts and the LiDAR + LoS catch handlers in effects.ts are now
consistent. Module names: `webgpu/sensors`, `effects`. Structured
context via the `{ error, ... }` second-arg style matching terrain.ts.

Bundle: main 808,316 B (under 819,200 cap), +424 B for the event API +
logger imports + the `rebuildWorld` helper.

Validation: typecheck, vite build, dotnet Release all pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 28, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The changes implement a terrain change notification system that allows modules to subscribe to terrain updates. When terrain state changes via preset updates or heightmap overrides, subscribers (sensors and effects) are notified to invalidate cached data and rebuild dependent structures, such as voxel brick maps and LiDAR visualizations.

Changes

Cohort / File(s) Summary
Terrain Notification Infrastructure
src/ResQ.Viz.Web/client/terrain.ts
Introduces onTerrainChange subscription API with listener registry and _fireTerrainChange dispatcher. Terrain state mutations in setActivePreset() and setHeightmapOverride() now emit notifications to subscribers.
GPU World Rebuild
src/ResQ.Viz.Web/client/webgpu/world.ts
Extracts voxelization logic into _voxelize helper. Adds rebuildWorld function to update existing World buffers in-place by voxelizing from updated heightmap and uploading to GPU via writeBuffer, then refreshing brick map.
Sensor Integration
src/ResQ.Viz.Web/client/webgpu/sensors.ts
Makes bootSensors idempotent by returning cached SensorContext. Subscribes to terrain changes and calls rebuildWorld on notifications. Converts logging from console.* to structured logger with error stringification.
Effects Cleanup & Subscription
src/ResQ.Viz.Web/client/effects.ts
Adds constructor subscription to terrain changes; clears cached mesh-link occlusion results and hides LiDAR point cloud (via draw range reset) on updates. Adds dispose() method for cleanup. Standardizes error handling to structured log.warn.

Sequence Diagram

sequenceDiagram
    participant Terrain
    participant EffectsManager
    participant SensorContext
    participant World
    participant GPU as GPU Device

    Note over Terrain: Preset/Heightmap Updated
    Terrain->>Terrain: _fireTerrainChange()
    
    par Parallel Subscriptions
        Terrain->>EffectsManager: notify (terrain change)
        EffectsManager->>EffectsManager: Clear mesh-link cache
        EffectsManager->>EffectsManager: Hide LiDAR cloud
        
        Terrain->>SensorContext: notify (terrain change)
        SensorContext->>World: rebuildWorld(device, heightFn, world)
        World->>World: Allocate voxel array
        World->>World: _voxelize(heightFn, voxels)
        World->>GPU: writeBuffer(voxelBuf, voxelData)
        World->>World: buildBrickMap(brickMap, voxels)
        Note over World: Brick map updated for LoS/LiDAR
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A terrain shift, a ripple through the code,
Voxels dance anew along the GPU road,
Effects hide and sensors wake with glee,
Brick maps bloom as subscriptions flow free! 🌿✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: introducing terrain-edit invalidation for the brick-map sensor stack via an event-driven API.
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.

✨ 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/webgpu-terrain-invalidation

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.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request implements a terrain-change event system to synchronize state across the visualization engine, including cache invalidation in the EffectsManager and efficient WebGPU buffer updates via a new rebuildWorld function. It also transitions the codebase from console logging to a centralized logging utility. Feedback focuses on ensuring the sensor initialization process is idempotent to prevent duplicate subscriptions and managing the lifecycle of event listeners to avoid potential memory leaks.

Comment thread src/ResQ.Viz.Web/client/webgpu/sensors.ts
Comment thread src/ResQ.Viz.Web/client/webgpu/sensors.ts Outdated
Comment thread src/ResQ.Viz.Web/client/effects.ts Outdated
sensors.ts (Gemini high + medium):
- bootSensors() is now sequentially idempotent. Previously, calling
  bootSensors() a second time after the first resolved would re-init
  the device, allocate fresh GPU buffers (leaking the old ones), AND
  register a duplicate onTerrainChange listener — every preset change
  would trigger N rebuildWorld() calls instead of one. Now we check
  getSensorContext() first; if a context already exists, return it
  directly without re-running boot.
- Added the corresponding `getSensorContext` import alongside
  `setSensorContext` from registry.

effects.ts (Gemini medium):
- Capture the unsubscribe handle returned by onTerrainChange() into a
  new private `_terrainUnsub` field. Add a `dispose()` method that
  calls it. EffectsManager is long-lived in the production viz, but
  holding the handle is best practice for hot-reload, tests, or
  scenario teardown — and avoids the linter complaint about an
  ignored return value.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@WomB0ComB0

Copy link
Copy Markdown
Member Author

Addressed all 3 review threads:

HIGH — bootSensors idempotency (sensors.ts)
Now checks getSensorContext() first; if a context already exists, returns it without re-running boot. Prevents the duplicate-listener / GPU-leak scenario the bot caught.

MEDIUM — getSensorContext import (sensors.ts)
Added the import as part of the idempotency fix above.

MEDIUM — capture onTerrainChange unsubscribe (effects.ts)
Captured into _terrainUnsub field; added a dispose() method that calls it. EffectsManager is long-lived in the production viz, but the handle is now available for hot-reload / tests / scenario teardown.

Build green: typecheck, vite build (808 KB / 819,200 cap), dotnet Release.

@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

🧹 Nitpick comments (3)
src/ResQ.Viz.Web/client/webgpu/world.ts (1)

107-109: Consider reusing a CPU voxel scratch buffer to reduce GC churn.

Line 107 allocates a new Uint32Array(N^3) on every rebuild. During repeated terrain edits, this can produce avoidable allocation pressure.

♻️ Suggested refactor
 export type World = {
     params: WorldParams;
     brickMap: BrickMap;
     voxelBuf: GPUBuffer;
     gridBuf: GPUBuffer;
+    voxelScratch: Uint32Array;
 };

 // createWorld(...)
-    const voxels = new Uint32Array(N * N * N);
-    _voxelize(heightFn, params, voxels);
+    const voxelScratch = new Uint32Array(N * N * N);
+    _voxelize(heightFn, params, voxelScratch);

     const voxelBuf = device.createBuffer({
-        size: voxels.byteLength,
+        size: voxelScratch.byteLength,
         usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
     });
-    device.queue.writeBuffer(voxelBuf, 0, voxels);
+    device.queue.writeBuffer(voxelBuf, 0, voxelScratch);

-    return { params, brickMap, voxelBuf, gridBuf };
+    return { params, brickMap, voxelBuf, gridBuf, voxelScratch };

 // rebuildWorld(...)
-    const voxels = new Uint32Array(N * N * N);
-    _voxelize(heightFn, world.params, voxels);
-    device.queue.writeBuffer(world.voxelBuf, 0, voxels);
+    _voxelize(heightFn, world.params, world.voxelScratch);
+    device.queue.writeBuffer(world.voxelBuf, 0, world.voxelScratch);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ResQ.Viz.Web/client/webgpu/world.ts` around lines 107 - 109, The code
allocates a fresh Uint32Array voxels (size N*N*N) each rebuild which causes GC
churn; change this to reuse a preallocated scratch buffer (e.g., attach
voxelsScratch to the world object or a module-level variable) and ensure its
length >= N*N*N before reuse, then call _voxelize(heightFn, world.params,
voxelsScratch) and device.queue.writeBuffer(world.voxelBuf, 0, voxelsScratch);
also clear or fill the existing buffer when necessary to avoid stale data and
update any code referencing voxels to use the reused voxelsScratch variable.
src/ResQ.Viz.Web/client/terrain.ts (1)

288-291: Consider coalescing terrain-change notifications for batched mutations.

Line 290 emits immediately, and the current heightmap install flow (setHeightmapOverride(...) followed by preset switch in src/ResQ.Viz.Web/client/app.ts Line 280-291) can trigger duplicate downstream rebuilds.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ResQ.Viz.Web/client/terrain.ts` around lines 288 - 291, The immediate
call to _fireTerrainChange() inside setHeightmapOverride causes duplicate
rebuilds during batched mutations; change setHeightmapOverride to schedule a
coalesced terrain-change notification instead of emitting synchronously (e.g.,
debounce/microtask/rAF) so multiple sequential calls (setHeightmapOverride +
preset switch) result in a single _fireTerrainChange invocation; update the
logic around the _fireTerrainChange helper to support a scheduled/flushable
trigger and ensure callers still get a timely notification when no other changes
are pending.
src/ResQ.Viz.Web/client/webgpu/sensors.ts (1)

116-122: Capture and manage the terrain unsubscribe handle for sensor lifecycle parity.

onTerrainChange is registered but never unsubscribed. The onTerrainChange() function returns an unsubscribe handle (see terrain.ts line 53), but sensors.ts doesn't capture it. If sensor context is ever torn down/rebooted (tests/hot-reload/future feature), stale listeners accumulate. effects.ts already follows the correct pattern at line 130 with this._terrainUnsub = onTerrainChange(...) — adopt the same approach here for consistency and resilience.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ResQ.Viz.Web/client/webgpu/sensors.ts` around lines 116 - 122, The
onTerrainChange listener in sensors.ts is registered without capturing its
unsubscribe handle, causing leaked listeners on teardown; capture the return
value from onTerrainChange and store it the same way effects.ts does (assign to
this._terrainUnsub) when calling onTerrainChange(() => rebuildWorld(...)); also
ensure the stored this._terrainUnsub is called during the sensor context
teardown/dispose path so the listener is removed. Use the existing symbols
onTerrainChange, rebuildWorld and this._terrainUnsub to locate and implement the
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/ResQ.Viz.Web/client/effects.ts`:
- Around line 138-146: Call EffectsManager.dispose() to unsubscribe the terrain
listener before recreating terrain in _switchPreset(): locate the
_switchPreset() function and, right before the line that constructs the new
Terrain (the code that does "terrain = new Terrain(...)" or similar), invoke
effectsMgr.dispose() so the stored unsubscribe (_terrainUnsub) is executed; this
ensures EffectsManager's subscriber (set up in its constructor and removed by
dispose()) does not leak and fire stale callbacks against the newly created
Terrain instance.

---

Nitpick comments:
In `@src/ResQ.Viz.Web/client/terrain.ts`:
- Around line 288-291: The immediate call to _fireTerrainChange() inside
setHeightmapOverride causes duplicate rebuilds during batched mutations; change
setHeightmapOverride to schedule a coalesced terrain-change notification instead
of emitting synchronously (e.g., debounce/microtask/rAF) so multiple sequential
calls (setHeightmapOverride + preset switch) result in a single
_fireTerrainChange invocation; update the logic around the _fireTerrainChange
helper to support a scheduled/flushable trigger and ensure callers still get a
timely notification when no other changes are pending.

In `@src/ResQ.Viz.Web/client/webgpu/sensors.ts`:
- Around line 116-122: The onTerrainChange listener in sensors.ts is registered
without capturing its unsubscribe handle, causing leaked listeners on teardown;
capture the return value from onTerrainChange and store it the same way
effects.ts does (assign to this._terrainUnsub) when calling onTerrainChange(()
=> rebuildWorld(...)); also ensure the stored this._terrainUnsub is called
during the sensor context teardown/dispose path so the listener is removed. Use
the existing symbols onTerrainChange, rebuildWorld and this._terrainUnsub to
locate and implement the change.

In `@src/ResQ.Viz.Web/client/webgpu/world.ts`:
- Around line 107-109: The code allocates a fresh Uint32Array voxels (size
N*N*N) each rebuild which causes GC churn; change this to reuse a preallocated
scratch buffer (e.g., attach voxelsScratch to the world object or a module-level
variable) and ensure its length >= N*N*N before reuse, then call
_voxelize(heightFn, world.params, voxelsScratch) and
device.queue.writeBuffer(world.voxelBuf, 0, voxelsScratch); also clear or fill
the existing buffer when necessary to avoid stale data and update any code
referencing voxels to use the reused voxelsScratch variable.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1c726548-565d-4486-8596-62d0df9a0a5a

📥 Commits

Reviewing files that changed from the base of the PR and between 0bebd6d and fdd6194.

📒 Files selected for processing (4)
  • src/ResQ.Viz.Web/client/effects.ts
  • src/ResQ.Viz.Web/client/terrain.ts
  • src/ResQ.Viz.Web/client/webgpu/sensors.ts
  • src/ResQ.Viz.Web/client/webgpu/world.ts

Comment thread src/ResQ.Viz.Web/client/effects.ts
@WomB0ComB0
WomB0ComB0 merged commit 273af6b into main Apr 28, 2026
37 checks passed
@WomB0ComB0
WomB0ComB0 deleted the feat/webgpu-terrain-invalidation branch April 28, 2026 23:22
WomB0ComB0 added a commit that referenced this pull request Apr 29, 2026
- "voxelizes once" was misleading — the brick-map rebuilds when the
  terrain preset switches or a heightmap override is installed (the
  `onTerrainChange` path landed in #74). Reword to "voxelizes at
  boot (and rebuilds on terrain edits)" so the docs match the code.
- Drop "(~17 cases, ~380 ms)" from the Vitest row in Tech Stack —
  machine-specific runtime that drifts with each new test, not a
  capability claim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WomB0ComB0 added a commit that referenced this pull request Apr 29, 2026
* docs: refresh README for the WebGPU sensor stack + version bumps

The README was written before the .NET 10 / Vite 8 / TS 6 upgrade and
predates the entire WebGPU sensor arc (PRs #66–#87). Bring it back
into sync with reality.

- Tech Stack: .NET 9 → 10, Three.js r175 → 0.184, TS 5 + Vite 6 →
  TS 6 + Vite 8; new rows for the WebGPU sensor primitive and Vitest
  frontend tests.
- Features: add the brick-map raymarcher (mesh-link LoS + per-drone
  LiDAR off one kernel) and the SignalR lazy chunk.
- Project Layout: include `webgpu/` (device, sensors, registry,
  world, brickmap, los, lidar, rays, shaders/) and `__tests__/`,
  plus the new `sensorStatsOverlay.ts`.
- Keyboard shortcuts: add `i` for the sensor stats overlay.
- License footer: 2024 ResQ Technologies Ltd. → 2026 ResQ Systems,
  Inc. — matches the SPDX headers across the codebase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: address review feedback on PR #88

- "voxelizes once" was misleading — the brick-map rebuilds when the
  terrain preset switches or a heightmap override is installed (the
  `onTerrainChange` path landed in #74). Reword to "voxelizes at
  boot (and rebuilds on terrain edits)" so the docs match the code.
- Drop "(~17 cases, ~380 ms)" from the Vitest row in Tech Stack —
  machine-specific runtime that drifts with each new test, not a
  capability claim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WomB0ComB0 added a commit that referenced this pull request Apr 29, 2026
* docs: refresh README for the WebGPU sensor stack + version bumps

The README was written before the .NET 10 / Vite 8 / TS 6 upgrade and
predates the entire WebGPU sensor arc (PRs #66–#87). Bring it back
into sync with reality.

- Tech Stack: .NET 9 → 10, Three.js r175 → 0.184, TS 5 + Vite 6 →
  TS 6 + Vite 8; new rows for the WebGPU sensor primitive and Vitest
  frontend tests.
- Features: add the brick-map raymarcher (mesh-link LoS + per-drone
  LiDAR off one kernel) and the SignalR lazy chunk.
- Project Layout: include `webgpu/` (device, sensors, registry,
  world, brickmap, los, lidar, rays, shaders/) and `__tests__/`,
  plus the new `sensorStatsOverlay.ts`.
- Keyboard shortcuts: add `i` for the sensor stats overlay.
- License footer: 2024 ResQ Technologies Ltd. → 2026 ResQ Systems,
  Inc. — matches the SPDX headers across the codebase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: address review feedback on PR #88

- "voxelizes once" was misleading — the brick-map rebuilds when the
  terrain preset switches or a heightmap override is installed (the
  `onTerrainChange` path landed in #74). Reword to "voxelizes at
  boot (and rebuilds on terrain edits)" so the docs match the code.
- Drop "(~17 cases, ~380 ms)" from the Vitest row in Tech Stack —
  machine-specific runtime that drifts with each new test, not a
  capability claim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: document missing keyboard shortcuts in README

The marketing intro mentioned `5`, `K`, and `Ctrl+Shift+R` but those
never appeared in the Keyboard Shortcuts table. The `multi-agency-sar`
scenario was also missing from the REST scenarios list, and the
camera presets (`Shift+1..5`) plus the drone-strip cycling (`[`/`]`)
were undocumented.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: correct wwwroot description (not committed)

The Project Layout entry claimed wwwroot/ is "committed for zero-install
deploys", but `git ls-files src/ResQ.Viz.Web/wwwroot/` returns 0 files
and `.gitignore` excludes index.html, assets/, and .vite/. The
zero-install path is actually the CI artifact `viz-wwwroot-{sha}`
produced by ci.yml's client/build job, not anything in the repo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: split long WebGPU sensor primitive sentence

Addresses gemini-code-assist review feedback on PR #89 — the
"voxelizes ... and serves ..." compound was a single 72-word sentence.
Splitting at the rebuild-trigger clause keeps the trigger conditions
visible while letting the LoS/LiDAR purpose start a fresh sentence.

Backticks around `peakSlotDepth`, `raysOutsideWorld`, and `i` were
already present in the source; only the bot's suggestion block had
stripped them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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