Skip to content

experiments: live dots, a macOS prototype of the holographic capture view - #86

Merged
AidenJohnston merged 8 commits into
mainfrom
t3/experience
Sep 27, 2026
Merged

AidenJohnston merged 8 commits into
mainfrom
t3/experience

Conversation

@SamGu-NRX

@SamGu-NRX SamGu-NRX commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

What changed

The camera screen on t3/ios-map3d covers the wall the phone has not seen with a frosted haze that lifts as the walk proceeds. Sam asked for the opposite: nothing where the phone has not looked, and dots that sit on the edges and surfaces it has detected. This adds experiments/live-dots, a macOS prototype of that view, so the rules could be seen and tuned before anyone builds them into Map3DOverlay.

It is a Swift package with two targets. LiveDotsCore decodes the synthetic LiDAR replay fixture from #10 (fetch-fixtures.sh pulls it out of origin/t3/ios-mvf), unprojects each keyframe's 256 × 192 depth into 5 cm voxels, marks a voxel as an edge when its normal creases, when it borders space the camera saw as empty, or when a voxel-scale image gradient says the wall is painted there (the fixture's meter, door and window are paint, not relief), and times each dot's birth and evidence rise. LiveDots plays the 41 keyframes in a phone-shaped window and draws the dots with Metal point sprites over the camera image, with a ring that fills as the requirement zone is covered and one instruction line. A "no LiDAR" mode simulates ARKit's feature points from the same depth and image texture; it is a simulation and says so. --export renders frames offscreen at 1170 × 2532 and make-video.sh streams them into ffmpeg.

The rules the prototype implements are listed at the top of Sources/LiveDotsCore/DotField.swift, and the design they come from is in the direction document (~/house-scanning-data/reports/experience/direction.html, section 4).

Why

Two things had to be seen rather than argued. Whether a pixel-scale image gradient could separate painted edges from brick joints (it could not: 31% of outline voxels and 19% of brick voxels qualified; a pyramid level at voxel scale gives 100% against 1%), and whether dots sized by 1/distance would read at a walking distance of 2.6 m (they did not; every dot hit the floor and the field read as speckle, so sizes now floor at 2.5 m and halos carry the colour).

UI changes

New screen, no before. Stills and videos rendered by the package itself, from the synthetic fixture:

LiDAR mode, keyframe 5 LiDAR mode, keyframe 31, the bin in view
Dots on the wall around the meter, none on wall the camera has not seen The bin outlined in violet edge dots, the window frame and the wall-to-ground corner as edge dots, a stipple on the brick
Simulated no-LiDAR mode, keyframe 31 The fog comparison toggle, keyframe 31
Sparse feature points on texture and a light plane stipple A flat veil over unseen wall beside the dots

Three looks behind one switch (--scheme hologram|constellation|ember, or the Look picker), so they can be compared on the same replay. Each has its own video in results/.

A. Hologram B. Constellation C. Ember
White dots with a blue halo on edges and a light stipple on the wall Edge dots only, joined by hairlines, nothing on the flat wall Dots born amber cooling to white
Edges and surface; opacity is evidence. Edges only, as a line drawing. Age in colour: warm at birth, white after six seconds.

Fog that the dots replace (--fog on, default), with recognised boxes and guidance states driven by the replay. Borrowings are credited in the fog section's header in Shaders.swift.

Point at your meter Walking, LiDAR Without LiDAR Aimed at the sky
Fog over the wall, the meter in a blue bracket Fog lifted where measured, dots and boxes Fog remaining on the bin and near ground with a feathered edge Tilt back down to the wall

Videos: fog-lidar.mp4, fog-nolidar.mp4, fog-reduce-motion.mp4.

Video of the LiDAR mode, 30 frames per keyframe-second so the births are visible: results/live-dots-lidar.mp4. The no-LiDAR and Reduce Motion videos are in the report folder, not the repository.

Verification

  • swift test in experiments/live-dots: 33 tests pass (unprojection against a hand-computed point, hash jitter bound and determinism, edge classification on a crease and on a seen-empty boundary, evidence to opacity, the 15° view dedupe, the 30° face-on cap on wall edges and its absence on ground edges, the ring fraction, and fixture errors that name the path and byte counts).
  • swift build with no warnings, Swift 6 language mode.
  • LiveDots --benchmark: 6,000 dots with halos, 0.54 ms GPU per 1170 × 2532 frame on an M4 Pro (p95 0.91 ms), 1.0 ms with fog on. Not measured on a phone.
  • Limits: the fixture is synthetic, so this proves mechanics, not the look on siding or stucco in sun. The no-LiDAR mode is simulated. Playback steps at 4 keyframes per second with no interpolation. The macOS controls (scrubber, mode switch, toggles) were exercised through the export paths, not clicked one by one.

Risk

None to the app: nothing outside experiments/live-dots changes, and the package depends on no other target. Building this into #21 needs two additions to Map3D: an iterator of observed surface voxels with per-frame deltas, and a 5 cm point set beside the 10 cm map (direction document, section 4.7).

Checklist

  • The scope is focused; related changes belong together
  • I explained what changed and why
  • I included before/after images for visible changes (a new screen, after only)
  • I included a video for motion or interaction changes

🤖 Generated with Claude Code

SamGu-NRX and others added 4 commits September 26, 2026 21:39
A Swift package in experiments/live-dots. LiveDotsCore fuses the synthetic
wall fixture's depth into 5 cm voxels, marks edges from creases, seen-empty
boundaries and a voxel-scale colour gradient, and times each dot's birth and
evidence rise. LiveDots draws the replay in a phone-sized window with Metal,
and exports stills and frames at 1170 x 2532. The no-LiDAR mode is simulated
from the same depth.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Dots now grow from 2.5 pt (flat) and 3.5 pt (edge) at 2.5 m to 4 and 6 pt
at 1 m, and every dot carries an additive halo that holds its colour: a
light blue glow around the Hologram core, violet on the bin. The simulated
no-LiDAR mode keeps points seen in 2 of the last 6 keyframes for 4 s, and
the oblique-edge opacity cap applies only to near-vertical surfaces.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The no-LiDAR simulation now takes feature candidates at a 0.1 gradient
instead of the edges' 0.2, because ARKit's raw feature points land on any
high-contrast texture, brick joints included. Edge classification stays at
0.2. Live points in view during the walk rise from 34 to 84 to 96 to 186.

results/ holds the four stills downscaled to 800 px and the LiDAR video,
all rendered from the synthetic fixture.

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

coderabbitai Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 59 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: c58a10e2-1576-4a3e-89b7-fe14feec88e9

📥 Commits

Reviewing files that changed from the base of the PR and between f852207 and b442f0a.

⛔ Files ignored due to path filters (19)
  • experiments/live-dots/results/fog-early-keyframe02.jpg is excluded by !**/*.jpg
  • experiments/live-dots/results/fog-lidar.mp4 is excluded by !**/*.mp4
  • experiments/live-dots/results/fog-nolidar-keyframe31.jpg is excluded by !**/*.jpg
  • experiments/live-dots/results/fog-nolidar.mp4 is excluded by !**/*.mp4
  • experiments/live-dots/results/fog-reduce-motion.mp4 is excluded by !**/*.mp4
  • experiments/live-dots/results/fog-tilt-keyframe39.jpg is excluded by !**/*.jpg
  • experiments/live-dots/results/fog-walk-keyframe31.jpg is excluded by !**/*.jpg
  • experiments/live-dots/results/fog-window-keyframe33.jpg is excluded by !**/*.jpg
  • experiments/live-dots/results/lidar-early-keyframe05.jpg is excluded by !**/*.jpg
  • experiments/live-dots/results/lidar-fog-keyframe31.jpg is excluded by !**/*.jpg
  • experiments/live-dots/results/lidar-walk-bin-keyframe31.jpg is excluded by !**/*.jpg
  • experiments/live-dots/results/live-dots-lidar.mp4 is excluded by !**/*.mp4
  • experiments/live-dots/results/nolidar-walk-keyframe31.jpg is excluded by !**/*.jpg
  • experiments/live-dots/results/scheme-a-hologram.jpg is excluded by !**/*.jpg
  • experiments/live-dots/results/scheme-a-hologram.mp4 is excluded by !**/*.mp4
  • experiments/live-dots/results/scheme-b-constellation.jpg is excluded by !**/*.jpg
  • experiments/live-dots/results/scheme-b-constellation.mp4 is excluded by !**/*.mp4
  • experiments/live-dots/results/scheme-c-ember.jpg is excluded by !**/*.jpg
  • experiments/live-dots/results/scheme-c-ember.mp4 is excluded by !**/*.mp4
📒 Files selected for processing (55)
  • experiments/live-dots/.gitignore
  • experiments/live-dots/Package.swift
  • experiments/live-dots/README.md
  • experiments/live-dots/Sources/LiveDots/Benchmark.swift
  • experiments/live-dots/Sources/LiveDots/BoxOverlay.swift
  • experiments/live-dots/Sources/LiveDots/Command.swift
  • experiments/live-dots/Sources/LiveDots/ContentView.swift
  • experiments/live-dots/Sources/LiveDots/ControlsPanel.swift
  • experiments/live-dots/Sources/LiveDots/DotRenderer.swift
  • experiments/live-dots/Sources/LiveDots/DotsMetalView.swift
  • experiments/live-dots/Sources/LiveDots/Exporter.swift
  • experiments/live-dots/Sources/LiveDots/FieldReport.swift
  • experiments/live-dots/Sources/LiveDots/FogLayer.swift
  • experiments/live-dots/Sources/LiveDots/FrameTimer.swift
  • experiments/live-dots/Sources/LiveDots/GradientReport.swift
  • experiments/live-dots/Sources/LiveDots/LiveDotsApp.swift
  • experiments/live-dots/Sources/LiveDots/LoadProgress.swift
  • experiments/live-dots/Sources/LiveDots/OffscreenTarget.swift
  • experiments/live-dots/Sources/LiveDots/Palette.swift
  • experiments/live-dots/Sources/LiveDots/PhoneChrome.swift
  • experiments/live-dots/Sources/LiveDots/PhoneFrame.swift
  • experiments/live-dots/Sources/LiveDots/ReplayData.swift
  • experiments/live-dots/Sources/LiveDots/ReplayPlayer.swift
  • experiments/live-dots/Sources/LiveDots/Shaders.swift
  • experiments/live-dots/Sources/LiveDots/main.swift
  • experiments/live-dots/Sources/LiveDotsCore/Coverage.swift
  • experiments/live-dots/Sources/LiveDotsCore/CubicBezier.swift
  • experiments/live-dots/Sources/LiveDotsCore/DepthMap.swift
  • experiments/live-dots/Sources/LiveDotsCore/DotField.swift
  • experiments/live-dots/Sources/LiveDotsCore/DotScheme.swift
  • experiments/live-dots/Sources/LiveDotsCore/DotTimeline.swift
  • experiments/live-dots/Sources/LiveDotsCore/EdgeLinks.swift
  • experiments/live-dots/Sources/LiveDotsCore/Evidence.swift
  • experiments/live-dots/Sources/LiveDotsCore/FieldDot.swift
  • experiments/live-dots/Sources/LiveDotsCore/FixtureError.swift
  • experiments/live-dots/Sources/LiveDotsCore/FogMask.swift
  • experiments/live-dots/Sources/LiveDotsCore/FrameInput.swift
  • experiments/live-dots/Sources/LiveDotsCore/GradientPyramid.swift
  • experiments/live-dots/Sources/LiveDotsCore/Instruction.swift
  • experiments/live-dots/Sources/LiveDotsCore/RGBImage.swift
  • experiments/live-dots/Sources/LiveDotsCore/RecognisedBox.swift
  • experiments/live-dots/Sources/LiveDotsCore/Replay.swift
  • experiments/live-dots/Sources/LiveDotsCore/Schedule.swift
  • experiments/live-dots/Sources/LiveDotsCore/ScreenProjection.swift
  • experiments/live-dots/Sources/LiveDotsCore/SimulatedFeatures.swift
  • experiments/live-dots/Sources/LiveDotsCore/StableHash.swift
  • experiments/live-dots/Sources/LiveDotsCore/Tuning.swift
  • experiments/live-dots/Sources/LiveDotsCore/WallCell.swift
  • experiments/live-dots/Tests/LiveDotsCoreTests/FieldRuleTests.swift
  • experiments/live-dots/Tests/LiveDotsCoreTests/FixtureErrorTests.swift
  • experiments/live-dots/Tests/LiveDotsCoreTests/FogAndGuidanceTests.swift
  • experiments/live-dots/Tests/LiveDotsCoreTests/GeometryTests.swift
  • experiments/live-dots/Tests/LiveDotsCoreTests/SchemeTests.swift
  • experiments/live-dots/fetch-fixtures.sh
  • experiments/live-dots/make-video.sh

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.

@github-actions github-actions Bot added the size:XXL 1,000+ effective changed lines. label Sep 27, 2026
SamGu-NRX and others added 4 commits September 26, 2026 22:49
hologram is the current look. constellation draws edge dots only, larger,
joined to their two nearest edge neighbours on the same surface by hairline
links that are born and fade with them. ember draws every dot amber when a
keyframe observes it and cools it to the hologram colours over 6 s.
LiveDots takes --scheme and the controls gain a Look picker; make-video.sh
renders one LiDAR video per scheme, and results/ holds 800 px copies.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LiDAR re-measures every voxel in view on every keyframe, so re-warming on
observation kept the whole screen amber. An ember dot now cools over the 6 s
after its birth and never re-warms, including when it turns from flat to
edge, so the newest part of the map glows. The last-seen bookkeeping the old
rule needed is gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Fog: every live dot splats a soft disc into a 96 x 208 reveal mask, which is
blurred, smoothstepped and eased with a lag (600 ms lifting, 120 ms
returning) before a chalk fog with slow domain-warped noise and a depth term
is drawn over the camera image and under the dots. --fog on|off|veil keeps
the old flat veil for comparison.

Boxes: the fixture's meter, gas meter, door and window, simulated from the
scene. The meter is solid Signal blue from its first view; the others appear
dashed at 60% in view and settle solid when seen face-on with both sides in
frame.

Guidance: point at the meter, hold still while the ring fills (the third
keyframe now holds 1.15 s), walk left, walk right, tilt back down with a
chevron, done, plus a distance-from-meter badge during the walk.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@SamGu-NRX

Copy link
Copy Markdown
Owner Author

@greptileai

@greptile-apps

greptile-apps Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 2/5

[Low risk] Adds a new macOS prototype app in an isolated experiments folder.

The PR does not yet appear safe to merge because Constellation loses no-LiDAR feature points and two previously reported rendering defects remain.

Fix All in CodexFindings

  1. P1 Constellation hides feature points ▶
  2. P1 Stale fog reveal survives view changes ▶
  3. P1 Unreliable depth creates lasting edges ▶
  4. P2 Fixture refresh loses existing data ▶
  5. P2 Still exports can be mislabeled ▶
Fix with agent prompt
### Issue 1
experiments/live-dots/Sources/LiveDots/Shaders.swift:89-91
In simulated no-LiDAR mode, Constellation keeps feature points in the draw buffer, but this rule uses their never-set edge time as their birth time. That time becomes 1,000,000 seconds, so the shader hides every feature point throughout the replay and the view appears empty instead of showing them.

```suggestion
    static float visibleBirth(Sprite s, int scheme) {
        return scheme == 1 && s.a.w < 0.5f ? max(s.b.x, s.c.x) : s.b.x;
    }
```

### Issue 2
experiments/live-dots/Sources/LiveDots/DotRenderer.swift:undefined-127
When someone scrubs during playback, switches LiDAR modes, or turns fog off and back on, the fog keeps the previous view’s screen-space reveal mask. The camera image and dots change, but the old mask can briefly leave an unmeasured region clear. Reset the mask for these discontinuous changes rather than easing from an unrelated view.

### Issue 3
experiments/live-dots/Sources/LiveDotsCore/DotField.swift:241-244
Surface fusion rejects low-confidence depth pixels, but free-space carving reads them without checking confidence. An unreliable zero or distant reading can mark a neighboring voxel as empty. That can turn a wall dot into a boundary edge that remains after later observations, leaving a false edge in the dot field.

### Issue 4
experiments/live-dots/fetch-fixtures.sh:8
The script deletes the existing `Fixtures` directory before extracting its replacement. If archiving or extraction fails after the fetch succeeds, a working replay is lost and the prototype cannot run until the fixtures are recovered. Extracting and verifying the replacement first would preserve the working copy on failure.

### Issue 5
experiments/live-dots/Sources/LiveDots/Exporter.swift:67-73
`--still 2 --at 0.3` passes keyframe 2’s 0.25-second slot and renders keyframe 3. The completion message still names keyframe 2 and reports its dot counts, so someone using the stills to compare keyframes can unknowingly use the wrong image and statistics. Reject out-of-slot offsets or report the keyframe actually rendered.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Summary

The PR adds a standalone macOS replay prototype that fuses synthetic depth into dot fields, compares three rendering looks, and exports stills and videos. Review found that Constellation hides the simulated no-LiDAR feature points it is configured to retain, and that a failed fixture extraction can discard an existing working copy.

Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Replay fixture] --> B[Depth and image decoding]
  B --> C[LiDAR voxels or simulated features]
  C --> D[Keyframe dot timeline]
  D --> E[Scheme filtering and Metal rendering]
  E --> F[Window or offscreen export]
Loading

Reviews (2) · Last reviewed commit: "Trim the live dots README back toward th..."

Comment thread experiments/live-dots/Sources/LiveDots/DotRenderer.swift
Comment thread experiments/live-dots/Sources/LiveDotsCore/DotField.swift
Comment thread experiments/live-dots/Sources/LiveDots/Exporter.swift
@AidenJohnston
AidenJohnston marked this pull request as ready for review September 27, 2026 15:27
@AidenJohnston
AidenJohnston merged commit 40eb07e into main Sep 27, 2026
4 checks passed
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@codemetrics-ai

Copy link
Copy Markdown

Note

Review skipped

Codemetrics is installed on this repository, but it hasn't been added to your team yet.

To enable AI code reviews for this repo, go to your dashboard and add house-scanning-master to your team's repositories.

Comment thread experiments/live-dots/Sources/LiveDots/Shaders.swift
Comment thread experiments/live-dots/fetch-fixtures.sh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ effective changed lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants