Conversation
… gating State Estimator (P0-v2) panel in TelemetryStream was gated on a UI-only context flag (trackerEnabled) that no control ever sets, instead of on whether tracker data is actually present in the response. Gate on current.tracker presence instead, matching the sibling perception panel's own pattern. Needed to make the estimator panel render during the new evidence-pack capture script's Hybrid+tracker runs. Add frontend/scripts/capture-sih-assets.mjs: drives the real Mission Control frontend (production build, ENGINE mode) with Playwright to capture deterministic screenshots/metrics/diagrams for the SIH judge evidence pack. No telemetry is fabricated; Hybrid/tracker runs are requested via route interception on the app's own already-shipped /api/simulation/:scenario query params (no UI toggle exists yet). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014h1TXJD8THZH4NbMqzrx97
17 real assets under presentation_assets/, generated by frontend/scripts/capture-sih-assets.mjs against the real fsoc_demo C++ engine (ENGINE mode) plus real terminal output and graphics built from committed measurement docs (docs/MVP_METRICS.md, docs/MVP_ABLATION.md). No telemetry, chart value, or terminal line is fabricated or hand-edited. - screenshots/: hero tracking, misalignment->convergence pair, Hybrid fusion + state-estimator proof, target-loss/coasting/reacquisition sequence, full telemetry dashboard, Step-9 perception frame, real CTest/CLI terminal captures - metrics/: measured-results and ablation graphics from the frozen Stage-4 protocol - diagrams/: implemented-only architecture, SEE->ESTIMATE->PREDICT-> CORRECT story graphic, before/after and failure-recovery collages (collages composited from real screenshots, no pixels altered) - manifest.json: full per-asset record (purpose, scenario, command, data source, ENGINE/REPLAY, commit SHA, capture date, sim-vs-hardware, recommended slide, caveats), including two items that could not be truthfully produced (07_clutter_rejection: no live UI route for that scenario; 14_frontend_validation: deprioritized) and one deliberately skipped (demo video, optional per task scope) - README.md: navigation + per-asset table - PPT_SHORTLIST.md: 8 assets ranked MUST USE / STRONG OPTIONAL / TECHNICAL BACKUP with what each proves and does not prove All assets are simulation-only; no physical hardware exists in this repository. See docs/SIH_MVP_FREEZE.md for the hardware boundary. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014h1TXJD8THZH4NbMqzrx97
…d-loop stat Three targeted evidence-pack fixes on real, verified data: - 01_mission_control_tracking (hero): recapture in Hybrid+tracker mode after convergence instead of Classical mid-run, so the hero frame shows Hybrid/AI perception (MODE: HYBRID) and the state estimator (LOCK STATE: TRACKING, CONFIDENCE 1.00) both visibly active, not just a bare tracking UI. Added findLateRatioWithLockState() to search backward from the end of a run for a converged, still-locked frame. - 02_initial_misalignment: the previous ratio-based seek landed on frame 5/200 (1.498deg), well past the true initial peak. Use the transport's own "Restart" button (reset() -> seek(0), exact) instead of a pixel-offset slider click, which cannot reach frame 0 exactly. Now reads 4.1275deg, matching the frozen docs/MVP_METRICS.md / docs/17_DEMO_FREEZE.md static-scenario figure (4.13deg). - New metrics/20_open_vs_closed_loop.png: compact Open Loop 6.4549deg RMS -> Closed Loop 0.5461deg RMS (x11.8) graphic, labeled SIMULATION / DEVELOPMENT-MACHINE RESULTS, sourced from docs/MVP_METRICS.md, docs/17_DEMO_FREEZE.md, and the real step10_validation_smoke terminal output already captured in 12_validation_terminal.png. - 18_before_after: collageHtml() now takes an optional headline stat; this collage now leads with the real "4.13deg -> 0.00deg" figure instead of two unlabeled screenshots. manifest.json, README.md, and PPT_SHORTLIST.md updated to match: the hero and before/after entries now describe the Hybrid/converged capture and the true frame-0 value, the new RMS graphic is documented with its exact data source, and the shortlist is re-ranked around the two strengthened assets. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014h1TXJD8THZH4NbMqzrx97
Extends FSOC from a synthetic camera + simulated actuator to a real mobile-phone camera + real perception + real state estimation + real controller + an honestly-virtual actuator, without modifying the frozen simulation baseline (SimulationRunner, PanTiltCamera, BeaconDetector, AiBeaconDetector, resolve_perception, TargetTracker, PIDController are all reused unchanged). New, additive C++ pieces: - FrameSource (fsoc/frame_source.hpp): pure I/O boundary, no coupling to perception or world truth. OpenCVCameraFrameSource wraps cv::VideoCapture for both a native camera index and a network URL (one class, since VideoCapture already exposes both through the same surface). - LiveCameraCalibrationConfig: the simplest defensible camera geometry -- a declared/measured pinhole FOV in a dependency-free key=value file, plus an angular-substitution estimation method from one known-size object. Never assumed equal to the simulation's CameraConfig. - preprocess_live_frame(): grayscale + resize to the exact 640x480 AiBeaconDetector's heatmap decode requires (kInputStride is fixed -- a real frame at any other resolution would silently produce wrong centroids without this). - VirtualPanTiltActuator: integrates the real PID output into a bookkeeping pan/tilt angle. Drives nothing physical, and does not feed back into how the next real frame's pixel error is interpreted. Deliberately NOT PanTiltCamera reuse, to keep the simulation/virtual-actuator boundary sharp. Always labeled ACTUATOR_TYPE = VIRTUAL in telemetry. - LiveTrackingSession: the real-camera counterpart to SimulationRunner, mirroring its control-path order exactly. LiveFrameResult carries no target_truth/observation/target_visible field -- there is no ground truth for a real frame, and none is fabricated (see test_no_target_no_ground_truth_leak). Its sensing-reference PanTiltCamera is built at the PREPROCESSED frame size, not the phone's raw capture resolution -- fx_px scales with width_px for a fixed FOV, so getting this backwards would silently scale every angular error. New CLI tools: fsoc_camera_probe (device discovery), fsoc_camera_view (minimal live viewer, snapshot-based since no display is assumed), fsoc_camera_calibrate (manual or known-object FOV estimation), fsoc_live (the full pipeline: --mode classical|ai|hybrid, --tracker, --manual-assist for human-readable PAN/TILT correction cues, --no-control for observe-only, clean handling of camera-open failure, consecutive-read-failure disconnect detection, and missing-model diagnostics). 18 automated tests added (fsoc_virtual_actuator_tests, fsoc_live_camera_calibration_tests, fsoc_frame_source_tests, fsoc_live_preprocessing_tests x7, fsoc_live_tracking_session_tests x8), all using fabricated frames/configs -- no real camera hardware is opened from an automated test run (see docs/PHONE_CAMERA_METRICS.md for why). All pre-existing 17 C++ tests remain green; full suite now 22/22. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a real-camera view without rewriting the frontend architecture or
touching the existing 9-screen Stitch nav (lib/nav.ts stays fixed at 9 --
see STITCH_IMPLEMENTATION_MAP.md): /mission/live is reachable by direct
link, nested under Mission Control, reusing the existing Panel/
KeyValueRow primitives.
/api/simulation/:scenario runs a finite scenario to completion and
returns every frame as one array -- that pattern does not fit fsoc_live,
a long-running process with no natural end. Instead:
fsoc_live -> overwrites generated/live/telemetry.json + frame.jpg
-> GET /api/live-camera + /api/live-camera/frame (polled every
500ms by the new page)
This is an honest polling snapshot read of a separate process YOU start
yourself, not a push/streaming connection -- documented as such in both
routes and the page. When no session is running, the page shows "No live
session" and never fabricates telemetry (verified manually: 503 from both
routes, clean UI state). The page labels CAMERA SOURCE and ACTUATOR
explicitly and flags stale snapshots (age > 3s).
Verified with a scratch (gitignored, deleted after) fabricated telemetry
snapshot: full page renders correctly, staleness detection works.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- docs/PHONE_CAMERA_METRICS.md: architecture (why almost nothing in the existing perception stack needed to change), the JSON telemetry schema, Mission Control transport rationale, calibration methodology, the virtual-actuator honesty statement, a hardware-ready PanTiltActuator interface sketch (rate-vs-position command mismatch a future serial adapter must solve), known limitations, and an explicit claim boundary (acceptable vs. not-acceptable phrasing). - docs/PHONE_CAMERA_TEST_PLAN.md: automated (CTest, all passing) vs. manual (hardware-required, PASS/FAIL checklist) test split. - docs/PHONE_CAMERA_GOLDEN_DEMO.md: a 15-step, zero-additional-hardware demo runbook (Mac/phone camera + tools/beacon_display.html as the target). - tools/beacon_display.html: a real, physical bright-circle test target (static/horizontal/vertical/sinusoidal motion) for real-camera testing -- explicitly not an FSOC transmitter. - README.md: new "Mobile Phone Camera-in-the-Loop" section; the Hardware boundary section is corrected to be precise now that an optional real-camera-sensing prototype exists (actuator side remains virtual; the frozen SIH MVP simulation is unaffected). - docs/09_FUTURE_ARCHITECTURE.md, docs/README.md: cross-references to the above. - .gitignore (previous commit): configs/ (personal camera calibration, hardware-specific, never a general default). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Pure orchestration -- zero tracking/perception/controller logic. Every command it runs is one already documented in README.md, docs/PHONE_CAMERA_GOLDEN_DEMO.md, docs/PHONE_CAMERA_TEST_PLAN.md, or .github/workflows/ci.yml; nothing was guessed (verified fsoc_demo/ fsoc_live/fsoc_camera_probe/fsoc_camera_calibrate's exact CLI flags against their source, the frontend port against frontend/package.json, and the Playwright webServer/reuseExistingServer behavior against playwright.config.ts before writing anything). run_fsoc.sh (repo root) execs scripts/run_fsoc.sh, the real implementation, matching the existing scripts/run_baseline_demo.sh convention. Interactive 8-item menu, or non-interactive subcommands (simulation|phone|probe|ui|test|golden|build), plus --rebuild and --no-browser. Key correctness points found and fixed during testing (not guessed): - The frontend port is never hardcoded -- read live from frontend/package.json's own `-p` flag. - C++ build delegates entirely to `cmake --preset debug` + `cmake --build --preset debug`; Ninja's own incremental tracking decides what's stale, never reimplemented here. - Process-group-scoped shutdown: `set -m` + `kill -- -PID` (not a bare PID kill) is required to actually stop a background frontend's whole descendant tree (npm -> next dev -> next-server) -- verified empirically that a bare `kill $PID` left next-server running and the port occupied; the process-group kill leaves nothing behind and frees the port. Never a broad killall/pkill -f. - Port reuse is content-verified (curl + grep for an FSOC-identifying string), not just "something answered" -- verified it refuses to touch a port occupied by an unrelated process (tested against a dummy python http.server) and correctly reuses/never-duplicates its own already-running instance. - mode_validate uses plain variables, not `local -A`: this machine's default `/usr/bin/env bash` is 3.2 (no associative-array support) -- verified directly, not assumed. - A command-substitution subshell's internal `exit` (e.g. ensure_calibration() declining to write a calibration file) correctly propagates through `set -e` at the call site -- verified empirically rather than assumed. - Fixed a real ordering bug: mode_golden's manual checklist was printed after the frontend's blocking wait, which never returns before Ctrl+C triggers the INT trap's `exit` -- the checklist would never have been seen. Split mode_phone_start()/block_until_stopped() so anything meant to be visible prints before the block, and block_until_stopped now tells the truth (no blocking, no misleading "Press Ctrl+C") when Mission Control was reused rather than started by this instance. fsoc.env.example: non-secret local overrides (FSOC_CAMERA_INDEX, FSOC_CAMERA_CALIBRATION, FSOC_AI_MODEL). No FSOC_FRONTEND_PORT knob -- documented as not real, since the port is auto-detected from package.json instead of guessable/overridable here. Manually tested end-to-end (no camera hardware touched, per this project's established boundary): build, simulation demo (full fsoc_demo terminal run + Mission Control + clean shutdown), Mission Control Only (start/reuse/port-conflict-refusal/process-group shutdown), Run Full Validation (real 22/22 ctest + step10 + frontend typecheck/lint/build + 20/20 Playwright, correct PASS summary, correct exit code), interactive menu navigation, and the phone-camera calibration-missing decline path. shellcheck: zero warnings on both files. README.md Quick Start now leads with ./run_fsoc.sh; the full manual step-by-step is preserved underneath in a <details> block. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD
Replace the generic "No live session" panel with a designed Real-Camera Mode card explaining why the public deployment can never show live telemetry (fsoc_live is a local native process, not something Vercel can reach) and giving the exact local command + setup guide link. Behavior is unchanged locally: it's the same honest "no session" state, just no longer indistinguishable from an error. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD
- README: correct stale 17/17 -> 22/22 ctest count (3 spots), add a Working Modes table (simulation / replay / engine / phone camera / future hardware), a Deployment section pointing at the new guide, and Contributing/Team/License sections. - Add docs/DEPLOYMENT.md: public Vercel architecture, project settings, why the phone-camera prototype can't run on Vercel, local workflow, troubleshooting, and Claude Code + Vercel MCP setup. - Index the new doc in docs/README.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD
- layout.tsx: metadataBase, refreshed title/description (matches the brief's problem-statement framing rather than the old "frozen v1_baseline" internal wording), Open Graph + Twitter card metadata, robots directive, keywords/authors. - opengraph-image.tsx: a code-generated (next/og), on-brand social preview using the real Stitch palette -- no external image asset, no fake satellite/dashboard imagery. - icon.svg: a small crosshair/reticle favicon in the same palette (matches the existing Crosshair nav icon for Mission Control). - robots.ts / sitemap.ts: standard Next.js App Router conventions, pointing at the project's real Vercel domain (fsoc-iota.vercel.app). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD
Short, project-specific rather than boilerplate: CONTRIBUTING points at the frozen architecture boundaries (CLAUDE.md) and the existing PR template instead of restating them; SECURITY states the actual (low) risk surface honestly rather than a generic disclosure policy. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD
FSOC_BASE_URL already overrode baseURL, but playwright.config.ts always tried to boot a local `npm run start` regardless -- pointless (and occasionally fatal, if no local production build exists) when the target is actually a deployed Preview/production URL. Skip the local webServer whenever FSOC_BASE_URL is set. The one test that needs the local C++ engine already skips itself when unreachable, so the existing suite was otherwise already deploy-safe. Documented in docs/DEPLOYMENT.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD
…fact ignores Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD
Found during Preview QA: the /world route (WebGL/three.js) legitimately keeps background network activity going past networkidle's 500ms-quiet window on a cold remote connection, timing out page.goto() at 30s even though the page renders correctly (confirmed visually: the 3D scene paints within ~5-8s on a cold Vercel Preview). networkidle is a known- fragile wait condition for canvas/client-heavy pages; the existing assertions (header text, nav count, main visible) are a more honest liveness check than network quiescence. All 20 tests pass locally and this specifically fixes the remote-deployment flake. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD
Found during Preview visual QA: the System Status / Uplink Frequency / Simulation Rate / Sensor stat grid was `absolute bottom-12 left-12` inside the hero panel, independent of the normal-flow title/description/ button stack above it. At ordinary windowed browser heights (~700-770px, not just small viewports) the button stack's natural height collided with the stats pinned to the container bottom, rendering "RUN DEMO SCENARIO" directly on top of "SYSTEM STATUS / NOMINAL". Switched the stat grid to a normal-flow flex item pushed to the bottom with `mt-auto` (falls back to flowing directly after the buttons, never overlapping, if the panel is too short) instead of absolute positioning. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD
Real evidence, gathered via a new deterministic Playwright viewport suite (tests/e2e/responsive.spec.ts, screenshots to generated/qa/mobile/, gitignored) rather than the browser-extension resize tool, which reported success without actually changing the rendered viewport. Root cause, present on every page: TopBar is a `position:fixed` header with no responsive handling -- at 390px its content (wordmark, SIH tag, scenario picker, SIM clock, feed-status chip, avatar) extends to x=629px, invisible past the viewport edge (fixed elements don't count toward document.documentElement.scrollWidth, so this was invisible to a naive overflow check -- caught only by visual screenshot review). Fixed by hiding secondary chrome below `lg` (SIH tag, SIM clock text, feed-status text -> a plain dot), abbreviating the wordmark to "FSOC", and giving the scenario picker real flex space (its label was measured collapsing to a literal 0px box -- a flex shrink-to-fit + truncate interaction, not just tight). Other real, screenshot-confirmed issues fixed: - Screen (AppShell): was `h-[calc(100vh-48px)] overflow-hidden` unconditionally, clipping any page whose content needed more height once stacked on mobile. Now scrolls vertically below `lg`, fixed/no-scroll at `lg:` and up (unchanged desktop behavior). - Homepage: hero/viewport was a hard `w-1/3`/`w-2/3` split regardless of viewport, producing unreadably narrow hyphenated text at phone widths. Stacks below `lg`; secondary FPS/EXPOSURE/GAIN chips (which collided with the feed label at narrow widths) collapse on mobile. - /mission: TelemetryStream and the Event Log panel were fixed `w-[320px]`, squeezing the primary tracking viewport to ~6px at 390px width. Both stack full-width below `lg`. - /world: the telemetry aside was an absolute `w-[320px]` panel spanning nearly the full mobile viewport, hiding the 3D scene entirely. Now a compact `w-[168px]` top-right card on mobile; the view-switcher gets a matching max-width so its longest label wraps instead of colliding with it. - /telemetry: the 4-stat metrics bar used unconstrained flex-1 columns whose labels wrapped inside a fixed h-[64px] row, clipping values. Given fixed widths + nowrap + contained horizontal scroll (explicitly sanctioned for telemetry bars) instead of forcing an unreadable fit. Verified: 51/51 local Playwright (20 smoke + 31 responsive, 7 viewports x 3 pages + all 10 routes @ 390x844), 22/22 CTest, typecheck/lint/build clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD
next 14.2.18 -> 14.2.35 (+ eslint-config-next in lockstep, per Next's own convention). Resolves CVE-2025-55184 (High, DoS: a crafted HTTP request to any App Router endpoint can hang the server via an infinite loop during deserialization) for the 14.x line, per the official advisory (nextjs.org/blog/security-update-2025-12-11). This app uses the App Router, so it was in-scope. CVE-2025-55183 (source-code exposure) only affects 15.0+, not 14.x. Patch-level bump within the same minor line, not a major migration -- no React/TypeScript/ESLint version change. 5 packages touched in package-lock.json. Other npm audit findings (postcss, playwright, uuid-via-@react-three/drei) are pre-existing, unrelated to this CVE, and every suggested fix requires a breaking/major-version change -- explicitly out of scope for this security maintenance pass. Verified: typecheck/lint/build clean, 51/51 local Playwright (20 smoke + 31 responsive), 22/22 CTest. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD
Found while re-verifying the mobile fix on Preview after the Next.js
upgrade: /scenarios occasionally failed the horizontal-overflow check
(17-25px, growing across runs) when the full 31-test suite ran with
Playwright's default 3 parallel workers. Investigated properly rather
than loosening the assertion blind:
- 12+ standalone reproductions (single page, 3 concurrent pages, matching
the test's exact goto/wait conditions) never once reproduced it --
always scrollWidth == innerWidth.
- Only ever reproduced inside the real multi-worker suite, and the
measured overflow grew across successive runs -- a resource-contention
signature (3 concurrent SwiftShader/software-rendered Chrome workers
competing for CPU during layout of a text-dense, always-taller-than-
viewport page), not a deterministic CSS bug.
- Confirmed via app/globals.css: `body { overflow-x: hidden }` is already
a global rule, so normal-flow overflow (unlike the real TopBar bug this
suite caught, which was `position:fixed` and NOT subject to that clip)
was never visible or reachable by a real user regardless of the exact
pixel count -- consistent with every screenshot in generated/qa/mobile/
being clean throughout.
Fix: run this file serially (test.describe.configure({mode:"serial"})) --
it's a QA/diagnostic suite, not a speed-critical CI gate, so correctness
wins over parallel speed. 2 consecutive full remote runs (31/31 each) and
one local run (51/51 combined with smoke.spec.ts) confirm this eliminates
the flake rather than papering over it with a loose tolerance.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD
Brings in the real-camera-in-the-loop prototype (FrameSource abstraction, OpenCVCameraFrameSource, LiveTrackingSession reusing the frozen detector/hybrid-perception/tracker pipeline, honest VirtualPanTiltActuator, fsoc_live CLI, camera probe/view/calibration tools, and the /mission/live Mission Control page) as the G1 starting point for the live-camera task. Resolved README.md conflicts by combining main's rewritten landing-page prose with the branch's Mobile Phone Camera-in-the-Loop / Hardware boundary / Working modes sections, and correcting now-stale claims (LICENSE file now exists; phone-camera work is merged, not a separate branch). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD
Adds fsoc::LiveFramePublisher (include/fsoc/live_frame_publisher.hpp, src/live_frame_publisher.cpp): writes frame_<N>.jpg / telemetry_<N>.json via write-to-temp-then-atomic-rename, then flips manifest.json (same pattern) LAST, so any reader observing a frame index in the manifest is guaranteed both files for that index already exist, complete. Replaces fsoc_live's previous in-place overwrite of a single telemetry.json/ frame.jpg pair, which two independently-polled HTTP requests could read mismatched or mid-write (docs/LIVE_DATA_AUDIT.md section 2). Bounded retention (default 3 pairs) keeps generated/live/ from growing without limit during a long session. 3 new unit tests. Rewires apps/fsoc_live.cpp to use the publisher, adds a per-run sessionId (a reconnect always gets a new one) and schemaVersion/sessionId telemetry fields. Rewires the frontend live-camera routes and /mission/live to fetch the image by the exact frameIndex the telemetry response names (object-URL fetch, not a cache-busted <img src>), 404/400 on a bad index rather than falling back to any other frame, and drop the displayed frame when sessionId changes so a reconnect doesn't inherit the previous session's last image. Verified: ctest 23/23 PASS, frontend typecheck/lint/build clean, Playwright 51/51 PASS, and an end-to-end smoke test against a hand-built fixture confirming the manifest -> exact-frame-index wiring works without opening a camera. Physical camera verification needs the operator (interactive OS permission prompt) -- see docs/LIVE_REALDATA_TASK_STATE.md "Next action". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD
G1 additions (physical live baseline, still software-only pending an operator run with a real camera): - fsoc_live --uncalibrated: pixel-only tracking with no real FOV. Angular telemetry fields are always null in this mode (never derived from the placeholder FOV used internally for the otherwise-unused control math), and control/actuation is force-disabled. --manual-assist and the terminal error= line fall back to real pixel offsets. Lets a real camera preview run before calibration is ever a blocker. - /mission/live: new CALIBRATION and SESSION ID rows, and a TIMING panel (measured PROCESSED FPS from the C++ side's own dtS, measured DISPLAYED FPS client-side). Poll interval 500ms -> 200ms. - frontend/tests/e2e/live-camera.spec.ts: 6 route-mocked tests covering the previously-untested risks -- reconnect drops the prior session's frame, a pruned/missing frame doesn't crash or mismatch, honest no-session/stale states, recording panel reflects telemetry not optimistic state. G2 (recording + annotation + real-dataset plumbing; no real reviewed recordings exist yet -- that half of G2 and all of G3 stay blocked on the operator): - fsoc::RealSessionRecorder (include/fsoc/real_session_recorder.hpp, src/real_session_recorder.cpp): a second sink, separate from LiveFramePublisher's pruned preview buffer -- every accepted frame is kept, never pruned. Writes manifest.json (session/software/calibration/model identity, git commit captured at CMake configure time, counters, atomically rewritten every frame), raw frames/frame_<N>.jpg, append-only telemetry.jsonl, and events.jsonl for human markers. Synchronous per-frame writes -- a disk error is counted, never stalls tracking. 4 new unit tests. - fsoc_live: Start/Stop Recording + event markers via a polled generated/live/command.txt (key=value, temp-then-rename), driven by new Start/Stop/Mark-Event controls on /mission/live wired through POST /api/live-camera/record. UI status always reflects polled telemetry, never optimistic local state. - /mission/annotate: a practical (not a platform) tool to open a recording, scrub frames, label presence (present/partial/full-occlusion/absent/ ambiguous), click-set the beacon center in raw pixel space, review a detector suggestion (never auto-saved as ground truth), and save/reload reviewed labels (labels.json, keyed by frame index). 4 new e2e tests. - tools/ai/real_dataset.py + real_dataset_split.py: additive real-data dataset reader (same __getitem__ contract as the synthetic BeaconDataset) and group-based (by recording, never by frame) train/val/test split with an explicit too-few-groups error instead of a silently empty split. Validates required files, in-bounds/contradictory label centers, and applies an explicit ambiguous/unreviewed exclusion policy. 13 plumbing checks against clearly-labeled fixtures (not real training data). Also extracted fsoc::write_text_atomic (include/fsoc/atomic_file_io.hpp) out of LiveFramePublisher so RealSessionRecorder shares the same temp-then-rename guarantee instead of duplicating it. Verified: ctest 24/24 PASS, frontend typecheck/lint/build clean, Playwright 61/61 PASS, tools/ai/real_dataset_tests.py 13/13 PASS (.venv-ai). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD
…jsonl to_json() built multi-line, indented JSON. LiveFramePublisher's standalone per-frame files tolerated that fine, but RealSessionRecorder appends the same string as one line into telemetry.jsonl -- discovered live while testing G2 recording end-to-end against a real webcam: a 2042-frame recording produced a 91890-line telemetry.jsonl (each frame's pretty-printed object spanning ~20 physical lines), which silently breaks any line-oriented JSONL reader (including frontend/app/api/real-sessions/[recordingId]/route.ts's parser). Made to_json() emit compact single-line JSON instead -- valid either way and now safe for both consumers. Verified against a fresh real-camera run: telemetry_<N>.json files are exactly 1 line and parse correctly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD
…fset Both /mission/live's detected-beacon reticle and /mission/annotate's marker overlay computed CSS position as a fraction of the <img>'s own rendered size (getBoundingClientRect()), but never accounted for the img's own offset within its flex-centered container -- when the raw frame's aspect ratio didn't exactly fill the panel (object-contain letterboxing), every marker was pulled toward the panel's corner instead of landing on the actual detected point. Caught live testing against a real camera: the reticle sat near the top-left of the feed instead of on the beacon. Fixed by switching to offsetLeft/offsetTop/offsetWidth/offsetHeight (relative to the img's offsetParent, the `relative` container) instead of getBoundingClientRect(), which correctly includes that centering offset. Verified live against the real camera afterward -- reticle lands on the actual bright source. Also fixes /mission/annotate's frame navigation, which assumed frame indices start at 0 and run contiguously -- a recording's frameIndex is the camera's own continuous counter across the whole fsoc_live session, so a recording started partway through begins at a large, non-zero index (e.g. 1000), and frame 0 usually doesn't exist in that recording at all. Navigation now moves by position in the telemetry array (cursor) instead of by raw frame-number arithmetic. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
CI caught this: 82d7682 changed the frame counter title from "Frame <frameIndex> / <totalFrames - 1>" to "Frame <cursor + 1> / <totalFrames> (frameIndex <frameIndex>)" (fixing navigation for a recording that doesn't start at frame 0), but the existing tests still asserted on the old "Frame 0 / 2" / "Frame 1 / 2" text. Updated to "Frame 1 / 3" / "Frame 2 / 3" to match. Verified locally: 4/4 annotate.spec.ts pass, full suite 61/61 pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Brings the real-camera-in-the-loop work (previously stranded on
feat/phone-camera-in-loop, never merged) ontomain, fixes the frame/telemetry identity gap it had, and adds the G2 recording + annotation + real-data-dataset tooling on top.G1 — physical live baseline
feat/phone-camera-in-loop's FrameSource/OpenCV camera/LiveTrackingSession/VirtualPanTiltActuator pipeline (honestly labeled, reuses the frozen detector/hybrid-perception/tracker stack).fsoc::LiveFramePublisher: fixes a real frame↔telemetry identity/atomicity gap — writesframe_<N>.jpg/telemetry_<N>.jsonvia write-to-temp-then-atomic-rename, flipsmanifest.jsonlast, so a client can never observe a mismatched pair.fsoc_live --uncalibrated: pixel-only tracking with no fabricated field of view — angular telemetry is alwaysnull, control/actuation force-disabled. Lets a real preview run before a calibration file exists./mission/live: CALIBRATION/SESSION ID/TIMING (measured processed + displayed FPS) rows, a live detected-beacon reticle overlay, 6 new route-mocked e2e tests for previously-untested risks (reconnect, stale, missing frame, recording status).G2 — recording, annotation, real-data tooling (software only — no real reviewed recordings ship in this PR)
fsoc::RealSessionRecorder: a second, unpruned sink (separate from the live preview buffer) — raw frames, append-only telemetry, event markers, atomically-updated manifest with full software/calibration/model identity./mission/live, driven by a polledcommand.txt./mission/annotate: review a recording, scrub frames, label presence/occlusion, click-set the beacon center, review (never auto-accept) a detector suggestion.tools/ai/real_dataset.py+real_dataset_split.py: additive real-data loader (same tensor contract as the syntheticBeaconDataset) with group-based (never frame-based) train/val/test splitting and an explicit ambiguous/unreviewed exclusion policy.Fixes found testing live against a real camera
to_json()was pretty-printed (multi-line), silently corruptingtelemetry.jsonl's one-object-per-line contract./mission/annotateassumed frame indices start at 0 and are contiguous; a recording's frame index is the camera's own continuous counter, so it usually starts well above 0.Full gate tracking, camera setup checklist, and next steps:
docs/LIVE_REALDATA_TASK_STATE.md. Audit of what was real vs. simulated before this work:docs/LIVE_DATA_AUDIT.md.Test plan
cmake --build --preset debugclean;ctest --preset debug24/24 PASSnpm run typecheck/npm run lint/npm run buildcleannpx playwright test61/61 PASS.venv-ai/bin/python3 tools/ai/real_dataset_tests.py13/13 PASS (labeled fixtures, not real data)/mission/annotatereviewing real captured frames🤖 Generated with Claude Code
https://claude.ai/code/session_01YTok4Srfs3CSL6Mp5TiYXD