Skip to content

feat(viz): allow URL overrides of WebGPU world bounds - #85

Merged
WomB0ComB0 merged 2 commits into
mainfrom
feat/configurable-world-bounds
Apr 29, 2026
Merged

WomB0ComB0 merged 2 commits into
mainfrom
feat/configurable-world-bounds

Conversation

@WomB0ComB0

Copy link
Copy Markdown
Member

Summary

Boot-time URL overrides for the brick-map sensor world. Defaults preserved; nothing changes for normal users.

The motivation is the 4 km terrain vs 1 km default sensor world gap surfaced by LosQueryStats.raysOutsideWorld in #79 — operators can now test ?worldGrid=256&voxelScale=16 (4096 m cube, 8× GPU memory) without redeploying, watch the counter drop, and decide whether the resize is justified before committing it as the default.

URL params (all optional)

Param Validation Default
worldGrid=N positive integer divisible by 8 128
voxelScale=V positive finite number 8
worldOriginX/Y/Z=K finite number auto-centred on world X/Z, Y=0

Origin auto-centres so a ?worldGrid=256&voxelScale=16 URL gets a 4096 m cube straddling the terrain origin without the operator computing offsets. Invalid values warn-and-fall-back per param; boot continues.

Boot log now emits the resolved world params so the audit script can correlate raysOutsideWorld against actual bounds.

Bundle

Chunk Before After
main 756.4 KB 756.4 KB
sensor 20.5 KB 21.5 KB

Test plan

  • npm run build passes; npm test 14/14 green
  • tsc --noEmit clean
  • Browser: open /?worldGrid=256&voxelScale=16 and check the console for the WebGPU world bounds log line
  • Verify getSensorContext()?.world.params reflects the override

🤖 Generated with Claude Code

Lets ops/dev override the brick-map world without redeploying — the
key motivation is testing the 4 km terrain vs 1 km default sensor
world gap surfaced by `LosQueryStats.raysOutsideWorld` (PR #79). The
defaults stay exactly where #69 set them; nothing changes for normal
users.

URL params (all optional):
  ?worldGrid=N          gridSize, must be > 0 and divisible by 8
  ?voxelScale=V         metres per voxel, positive finite
  ?worldOriginX/Y/Z=K   finite numbers; if omitted, the cube
                        auto-recentres on world X/Z and starts at
                        Y=0 so the new cube still straddles the
                        terrain.

Example: `?worldGrid=256&voxelScale=16` → 4096 m cube at 16 m/voxel
covering the full 4000 m terrain (8× GPU voxel-buffer memory). Boot
log emits the resolved params so the audit can correlate
`raysOutsideWorld` against actual world bounds.

Defaults preserved:
  gridSize: 128, voxelScale: 8, origin: [-512, 0, -512]

Invalid values warn-and-fall-back per param; boot continues.

Sensor chunk +1 KB (now 21.5 KB); main bundle unchanged at 756.4 KB.

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

coderabbitai Bot commented Apr 29, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@WomB0ComB0 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 55 minutes and 28 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dfe4a8e4-2633-4e90-b727-46309b817aa6

📥 Commits

Reviewing files that changed from the base of the PR and between a9f32a9 and 38dab6b.

📒 Files selected for processing (1)
  • src/ResQ.Viz.Web/client/webgpu/sensors.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/configurable-world-bounds

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
Review rate limit: 0/1 reviews remaining, refill in 55 minutes and 28 seconds.

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 adds functionality to override WebGPU world parameters via URL search parameters, enabling dynamic configuration of the grid size, voxel scale, and origin. The review feedback recommends using the "BRICK" constant instead of a hardcoded value for consistency and adopting "Number()" for stricter validation of numeric inputs compared to "parseInt" and "parseFloat".

Comment thread src/ResQ.Viz.Web/client/webgpu/sensors.ts Outdated
Comment thread src/ResQ.Viz.Web/client/webgpu/sensors.ts Outdated
Comment thread src/ResQ.Viz.Web/client/webgpu/sensors.ts Outdated
Address Gemini review on PR #85:
- Import `BRICK` from `./brickmap` and use it as the divisibility check
  in `_readPositiveInt` instead of the hardcoded `8`. Keeps the world
  parser in sync if BRICK ever changes.
- Replace `parseInt`/`parseFloat` with `Number()`. The lenient parsers
  silently accept trailing garbage like "128abc" and truncate decimals
  like "128.9" — for a config override that should fall back to the
  default rather than partially parse.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WomB0ComB0 added a commit that referenced this pull request Apr 29, 2026
Address Gemini review on PR #86:
- Move the 4096 capacity constant out of `effects.ts:LIDAR_MAX_RAYS`
  and into `webgpu/registry.ts:LIDAR_MANAGER_CAPACITY`. Both
  `sensors.ts` (constructing the LosQueryManager) and `effects.ts`
  (validating user-overridden scan params) now read the same
  number, eliminating the maintenance risk of two literal copies
  drifting apart. registry.ts is already the chunk-split-safe seam
  effects.ts uses for getSensorContext, so this doesn't change the
  bundle layout.
- Replace `parseInt`/`parseFloat` with `Number()` in the three URL
  parser helpers — same change as PR #85; lenient parsers silently
  truncate decimals and accept trailing garbage, which for a config
  override should fall back rather than partially parse.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@WomB0ComB0
WomB0ComB0 merged commit 991cee2 into main Apr 29, 2026
37 checks passed
@WomB0ComB0
WomB0ComB0 deleted the feat/configurable-world-bounds branch April 29, 2026 06:16
WomB0ComB0 added a commit that referenced this pull request Apr 29, 2026
Defaults preserved exactly (16 × 256 = 4096 rays, ±22.5° FOV, 200 m
range, 1 s interval); operators can tune any of them from the URL
for testing. Pairs with the world-bounds overrides in PR #85 — both
share the same warn-and-fall-back validation pattern, both read at
boot only.

URL params (all optional):
  ?lidarElev=N       elevationCount, positive integer
  ?lidarAzim=N       azimuthCount,   positive integer
  ?lidarFov=D        elevationFov in degrees, 1–180
  ?lidarRange=M      max range in metres, positive finite
  ?lidarInterval=S   seconds between scans, positive finite

Per-scan ray count must not exceed 4096 (the LiDAR manager's per-slot
capacity from `webgpu/sensors.ts`); both elev/azim fall back to
defaults together if `elev × azim` exceeds it, since partial
fallbacks would produce unexpected aspect ratios.

A full Settings-panel UI is deliberately deferred: tuning these knobs
is mostly a dev/audit workflow, not a per-session user preference,
and URL-driven overrides keep the Settings/UI surface from growing
without a clear use case.

Bundle: main 756.4 → 757.7 KB (+1.3 KB; 92.5 % of cap).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WomB0ComB0 added a commit that referenced this pull request Apr 29, 2026
Address Gemini review on PR #86:
- Move the 4096 capacity constant out of `effects.ts:LIDAR_MAX_RAYS`
  and into `webgpu/registry.ts:LIDAR_MANAGER_CAPACITY`. Both
  `sensors.ts` (constructing the LosQueryManager) and `effects.ts`
  (validating user-overridden scan params) now read the same
  number, eliminating the maintenance risk of two literal copies
  drifting apart. registry.ts is already the chunk-split-safe seam
  effects.ts uses for getSensorContext, so this doesn't change the
  bundle layout.
- Replace `parseInt`/`parseFloat` with `Number()` in the three URL
  parser helpers — same change as PR #85; lenient parsers silently
  truncate decimals and accept trailing garbage, which for a config
  override should fall back rather than partially parse.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WomB0ComB0 added a commit that referenced this pull request Apr 29, 2026
* feat(viz): URL-overridable LiDAR scan params

Defaults preserved exactly (16 × 256 = 4096 rays, ±22.5° FOV, 200 m
range, 1 s interval); operators can tune any of them from the URL
for testing. Pairs with the world-bounds overrides in PR #85 — both
share the same warn-and-fall-back validation pattern, both read at
boot only.

URL params (all optional):
  ?lidarElev=N       elevationCount, positive integer
  ?lidarAzim=N       azimuthCount,   positive integer
  ?lidarFov=D        elevationFov in degrees, 1–180
  ?lidarRange=M      max range in metres, positive finite
  ?lidarInterval=S   seconds between scans, positive finite

Per-scan ray count must not exceed 4096 (the LiDAR manager's per-slot
capacity from `webgpu/sensors.ts`); both elev/azim fall back to
defaults together if `elev × azim` exceeds it, since partial
fallbacks would produce unexpected aspect ratios.

A full Settings-panel UI is deliberately deferred: tuning these knobs
is mostly a dev/audit workflow, not a per-session user preference,
and URL-driven overrides keep the Settings/UI surface from growing
without a clear use case.

Bundle: main 756.4 → 757.7 KB (+1.3 KB; 92.5 % of cap).

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

* fix(viz): centralize LiDAR capacity + tighten URL parser

Address Gemini review on PR #86:
- Move the 4096 capacity constant out of `effects.ts:LIDAR_MAX_RAYS`
  and into `webgpu/registry.ts:LIDAR_MANAGER_CAPACITY`. Both
  `sensors.ts` (constructing the LosQueryManager) and `effects.ts`
  (validating user-overridden scan params) now read the same
  number, eliminating the maintenance risk of two literal copies
  drifting apart. registry.ts is already the chunk-split-safe seam
  effects.ts uses for getSensorContext, so this doesn't change the
  bundle layout.
- Replace `parseInt`/`parseFloat` with `Number()` in the three URL
  parser helpers — same change as PR #85; lenient parsers silently
  truncate decimals and accept trailing garbage, which for a config
  override should fall back rather than partially parse.

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
Address the security review on PRs #85 / #86. URL overrides for
world bounds and LiDAR scan params previously accepted any positive
finite value; a typo like `?worldGrid=8192` (~2 TB voxel buffer) or
`?lidarRange=1e30` (DDA walks every voxel + float precision erosion)
would lock up the GPU. Self-DOS only — no server amplification, no
cross-origin attack surface — but a sane upper bound prevents
"my browser tab died" footguns.

Caps:
  worldGrid       ≤ 1024     (1024³ × 4 B = 4 GB voxel buffer ceiling)
  voxelScale      ≤ 256      (1024 × 256 = 256 km cube ceiling)
  lidarElev/azim  ≤ 4096     (the LiDAR manager capacity)
  lidarRange      0.1–10000  (m; visualization terrain is only 4 km)
  lidarInterval   0.001–60   (s)

Also: truncate `raw` log value to 64 chars (+ `…` ellipsis) so a
poisoned URL with megabytes of garbage can't bloat the console or
log aggregator. Helpers in both files share the same `_truncRaw`
implementation.

The lenient `_readPositiveFinite` helper in effects.ts is removed —
the two call sites (lidarRange, lidarInterval) now use
`_readNumberInRange` with explicit bounds, matching the pattern
already used for `lidarFov`.

Bundle: main 757.7 → 757.9 KB (+0.2 KB).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WomB0ComB0 added a commit that referenced this pull request Sep 10, 2026
* docs: correct the SDK pin, and guard what the compiler cannot

CLAUDE.md said the submodule is "pinned to a release tag". It is not. It is
pinned to a3f8b89 on release/0.6.x, three commits past v0.6.0, and those three
commits (#85-#87) are what added drone attitude, the explicit yaw command, and
landing recovery. No tag contains them. The line invited exactly the change that
would drop them.

Two findings while checking it, one of which corrects an assumption I had been
repeating.

FIRST, THE SDK's main HAS MOVED PAST US. ResQ.Simulation.Engine, ResQ.Mavlink,
ResQ.Mavlink.Dialect and ResQ.Mavlink.Mesh do not exist on main - MAVLink is gone
from it entirely, and main carries a different set (Core, Clients, Protocols,
Blockchain, Storage, Simulation). All four are ProjectReferences in
ResQ.Viz.Web.csproj. So viz is not on a stale pin; it is on a branch main has
structurally abandoned, 43 commits back on a divergent line. Reconciling that is
an architecture decision for a human, and this commit does not attempt it - it
writes it down where the next person will look.

SECOND, THE SILENT-REVERT RISK IS NOT REAL, and I had been asserting it was.
Both ways of moving the pin fail loudly:

  v0.6.0  -> compile error; viz calls Hover(yaw) and GoTo(..., yaw:), which that
             tag has no overloads for
  main    -> the four project references do not resolve at all

Measured, not reasoned: checking out v0.6.0 in the submodule produced five
CS1501/CS1739 errors in viz's own production code before any test ran.

What neither catches is a behaviour that changed without changing a signature.
Landing recovery is a re-arm inside ApplyCommand, and attitude integration is the
roll/pitch term inside IntegrateAttitude; reverting either compiles cleanly.
Verified by doing it: the re-arm revert and the heading-only attitude revert each
build green and each fail exactly one of the new tests.

So SdkFlightContractTests covers the part that is genuinely silent. Four tests:
explicit yaw steers heading, forward flight produces real pitch, a landed drone
re-arms and climbs on a non-Land command, and Land still latches so the re-arm
did not make HasLanded meaningless. They duplicate tests the SDK already has, on
purpose - this is viz asserting the submodule it is pinned to still behaves the
way its client and physics assume.

1376 tests green. dotnet format clean. The submodule pin is unchanged and its
working tree was restored after every mutation.

* test: make the re-arm test cover what its name claims, and drop a stale claim

Two review findings, both right.

FIRST, the test file's own doc still said moving the pin to v0.6.0 "still builds,
still passes every other test, and silently reverts takeoff and rotation". That
was the assumption this PR exists to correct, and CLAUDE.md now says the opposite
two files away: v0.6.0 is a compile error because viz calls Hover(yaw) and
GoTo(..., yaw:). The stale wording survived where the argument for the tests
lives, which is the worst place for it. Rewritten to say what was measured, and
to say plainly what the tests actually guard - behaviour that changed without
changing a signature, which no compiler can see.

SECOND, the re-arm test was named "on any non-Land command" and exercised GoTo
alone. The re-arm is keyed on command.Type, so a regression that cleared
HasLanded for GoTo and not for Hover or RTL would have passed. Now a Theory over
GoTo, Hover and RTL.

Verified rather than assumed: rewriting the guard to
`command.Type == FlightCommandType.GoToWaypoint` fails the Hover and RTL cases
and passes GoTo - precisely the regression described, which the old test would
have shipped green. The first attempt at that mutation used a FlightCommandType
member that does not exist and failed to compile; the result above is from the
one that actually applied.

Hover asserts the flag rather than a climb, because RTL rewrites to
GoTo(LaunchPosition) and has somewhere to go while Hover holds station.
Asserting a climb for Hover would assert the wrong contract.

1378 tests green. dotnet format clean. Submodule restored after each mutation.
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