Codync
A free, open-source 1:1 alternative to Grok Bot and Muse. Message Claude Code, Codex, Cursor and 40+ coding agents as persistent bots from your iPhone, Mac or Linux desktop. Group chats, threads, appr
PorchBench (unofficial demo): doorbell camera simulation and person-detection benchmark in many environments.
Translation pending
Developer toolsGitHub @hoeksemaaOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Repository information has not been collected yet.
No updates detected yet. GitHub releases will appear here.
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗A doorbell camera on a simulated front porch, in many environments. It benchmarks one detection task: find the person before they reach the door.
Unofficial demo by John Hoeksema for the Member of Technical Staff, Core Platform role at Antioch. Not affiliated with Antioch. All scenes and results are simulated and uncalibrated.
Live: https://antioch-demo-hoeksema.vercel.app (Simulation), https://antioch-demo-hoeksema.vercel.app/benchmarks (Benchmark), and https://antioch-demo-hoeksema.vercel.app/gallery (Gallery).
| Part | Language | Folder | What it does |
|---|---|---|---|
| 3D scene | Three.js (TypeScript, in the browser) | scene/src/ (viewer.ts, houses.ts, delivery.ts, …) | Houses, the delivery person and package, the doorbell camera, the overview |
| Scenario axes | TypeScript (browser) | scene/src/settings.ts, lights.ts, weather.ts, camera.ts, sensor.ts, people.ts, surfaces.ts, props.ts, events.ts | The axes: house, event, light, weather, camera, person, speed, surfaces, occlusion, with a v1 baseline value and a showcase value for each |
| Simulation page | Three.js viewer plus buttons | scene/index.html, scene/src/main.ts, simulations.ts | The scene browser: overview with the doorbell inset at the bottom right, and a panel above it with one row of buttons per axis, each named by a symbol (icons.ts). Click the inset (or press V) for the doorbell view; click again to go back. Keys 1–5 switch houses. Also capture mode for Python |
| Benchmark hook | Three.js (TypeScript, in the browser) | scene/src/bench.ts | window.bench: renders the frame at time t and returns the ground truth (person box, distance, phase) |
| Scenario list | Python | bench/scenarios.py → bench/scenarios.json | The fixed 100 scenarios that every detector runs on |
| Frame capture | Python | bench/capture.py | Opens the scene in headless Chromium (Playwright), calls window.bench, takes screenshots, caches them in bench/frames/ |
| Detectors | Python | bench/detector.py | 5 detectors (torchvision and Ultralytics); add one entry to add a detector |
| Scoring and results | Python | bench/run.py | Matches detections to the ground truth; writes scene/public/results/ |
| Benchmark page | TypeScript (browser) | scene/benchmarks.html, scene/src/benchmarks.ts | One chart of the scores: a column for each detector, a colored row group for each category |
| Gallery page | TypeScript (browser) with the Three.js viewer | scene/gallery.html, scene/src/gallery.ts, replay.ts | A replay of any scene with the boxes on the doorbell camera, and a grid of the notable frames from bench/gallery.py (scene/public/notable/), with no text |
| Hosting | Vercel | scene/ | Serves the Three.js scene as a static site. Python does not run on Vercel |
Python (bench/) Three.js (scene/, in headless Chromium)
run.py -- window.bench.render(t) --------------> renders the doorbell frame at time t
<-- ground truth: person box, distance --- projects the person onto the camera image
capture.py -- screenshot -----------------------> (the frame)
detector.py finds "person" boxes in the frame
run.py scores each frame: hit, miss, false alarm
Each source file starts with a comment that says "Three.js (browser)" or "Python".
Why this split: Three.js is fast to build and runs in any browser, so the same scene is the public demo and the replay viewer. Python does the detection, the scoring, and the runs, which is where the Core Platform post puts its weight.
cd scene
npm install
npm run dev
Three pages, with the same header: the title at the left, and one bar with Simulation, Benchmark, Gallery, View source, and View creator. On a narrow screen (760 px or less), the header is two rows: the title with Source and Creator at the right, then Simulation, Benchmark, and Gallery as a segmented control:
? button opens a panel with the keys and the symbol names. Press 1–5 to switch houses. Click the doorbell camera inset, or press V, for the doorbell camera view; click that view, or press V or Esc, to go back to the overview./benchmarks): the detector scores (see below)./gallery): the replay of any scene, and the notable frames from the benchmark run (see below).Capture mode (/?view=doorbell) shows the doorbell camera alone, in the full window, with no page around it. The Python benchmark uses this mode, for example ?house=stoop&view=doorbell.
URL parameters:
scene/src/settings.ts, for example ?house=<id> (ids below). On Gallery, ?replay=<scenario>&detector=<id>&t=<seconds> picks the replay.?t=<seconds> freezes the delivery at that time, for example ?house=porch&view=doorbell&t=16.?seed=<n> seeds every random choice in the scene. Seed 0 (the default) is the canonical scene, with no jitter. Another seed moves the delivery's start point (±2 m) and speed (±10%). Each module takes its own named stream from scene/src/random.ts, so a change on one axis does not change the random values of another module.window.sim.stats() gives the draw calls and triangles of the last frame (all passes), for the browser budget checks. Static meshes that share a material are merged into one mesh, so a frame costs about 90–110 draw calls.Five daytime houses. Each has its own entry, approach path, and doorbell position, so the same scenario runs against different geometry.
| id | House | Entry | Visitor approach | Doorbell (as seen from the street) |
|---|---|---|---|---|
porch | One story | Raised porch, 3 front steps, railings | Straight walk from the sidewalk | Right of the door, 1.22 m above the porch floor |
stoop | Attached row houses | Tall stoop, 10 risers, iron rails | Straight up the stoop from a close sidewalk | Left door trim, about 2.9 m above the yard |
side | Bungalow | Porch behind a 0.75 m knee wall, steps on the left end | Up the side walk, then onto the porch from the side | Left of the door, turned toward the side steps |
flush | Modern, with garage | No porch: a pad at ground level, a canopy | Up the driveway, then along a walk across the front | Right of the door, low, turned toward the driveway |
portico | Two story, set far back | Small portico on two columns, 1 step | Through a gate in a picket fence, on a walk that bends | Left of the door, a bit higher |
scene/src/houses.ts (one function for each house), parts.ts (shared building parts), street.ts (street and neighbors).?light=)| id | Light | Shadow light | Other lights |
|---|---|---|---|
day | Sun at 44°, sky with light clouds | Sun | Sky ambient light |
dusk | Low warm sun toward the street, into the doorbell camera | Sun | Porch light, street lamps, some lit windows |
night | Dark sky, dim moonlight | Porch light | Street lamps, some lit windows |
ir | Night with the porch light off | None | IR light at the doorbell (falls off with distance); the doorbell image is monochrome. The overview does not show the IR light |
v1 (not a button) is the v1 light: a flat blue background, a hemisphere light, and the sun. Capture mode and Benchmark use it when ?light= is missing.scene/src/sensor.ts) when an effect is on; for now the only effect is IR monochrome.scene/src/lights.ts.?weather=)| id | Weather | What changes |
|---|---|---|
clear | Clear | Nothing (the baseline) |
rain | Rain | Gray overcast sky, dimmer sun; 16k streaks; wet, glossy ground (lamps reflect at night); drops glow near the doorbell in IR; drops on the fisheye camera's lens |
snow | Snow | Overcast; 7k flakes; white lawn, roofs, and bushes; dark shoveled paths |
fog | Fog | Exponential fog in the light's haze color |
wind | Windy fall day | Trees and bushes sway (and their shadows move), fall colors, 300 blowing leaves |
scene/src/weather.ts; lens drops in scene/src/sensor.ts.?camera=)| id | Camera | Image |
|---|---|---|
basic | The v1 camera: a plain perspective camera | 16:9, 70° vertical; no sensor effects |
ring | A square fisheye, like a wide-angle video doorbell | About 150° corner to corner, 115° side to side (stereographic); auto exposure, noise, vignette, lens softness, compression blocks, bloom, lens flare |
scene/src/sensor.ts) maps it to the fisheye and adds the effects. A real doorbell fisheye is 150° × 150°; one perspective render cannot reach that, because the corners would need rays more than 90° off the axis.window.bench boxes go through the same fisheye mapping (scene/src/camera.ts).?person=, ?speed=)| id | Person | Height |
|---|---|---|
man | Shirt and trousers | 1.80 m |
woman | Shirt and trousers | 1.66 m |
dark | The man in dark clothes (a hard case at night) | 1.80 m |
hooded | Outfit with the hood up | 1.80 m |
child | The woman scaled to child height (the free pack has no child body) | 1.20 m |
man when ?person= is missing. The v1 capsule figure (the mannequin) is gone.walk 1.4 m/s, run 4.5 m/s. The run speed matches the stride of the free jog clip, so the feet stay planted.scene/public/people/ (see LICENSE.txt there). The page loads a person's file (1.4–2.3 MB) only when it is on screen.scene/src/people.ts. window.bench boxes come from the skinned vertices.?event=)The site calls this axis Scene (what plays out in front of the doorbell); its URL parameter and id stay event.
| id | Scene | Person in view |
|---|---|---|
delivery | The person carries the package to the door (the baseline) | Yes |
dog | A dog trots up the walk, sniffs near the steps, and trots back | No |
car | An SUV drives past; at dusk, night, and IR its headlights and taillights are on | No |
empty | Nothing moves but the weather (with weather=wind: swaying trees and moving shadows) | No |
dog, car, and empty, window.bench reports phase none, no person box, and no distance, so every detection is a false alarm.scene/public/props/LICENSE.txt.scene/src/events.ts.?surfaces=, ?occlusion=)surfaces=flat is the v1 look (one color per material). surfaces=textured gives walls, roofs, decks, walks, foundations, the street, and the lawn CC0 Poly Haven textures (scene/public/surfaces/, about 3 MB): a 1K color map, and 512 px normal and AO/roughness maps. UVs are in meters, so a tile has the same size on every wall. Siding takes each house's paint color.userData.surface, set in parts.ts); the static merge keeps one draw call per material, so textures add no draw calls.occlusion=bins puts two curbside wheelie bins between the doorbell and the walk. occlusion=car parks a sedan (Quaternius, CC0): in the no-porch house's driveway it hides the approach; at the other houses it is at the curb.scene/src/surfaces.ts, scene/src/props.ts.In each house, one person carries one package along that house's route: from the sidewalk, up the approach, to a drop spot that the doorbell can see. The person sets the package down, turns around, and walks back. Then the loop starts again.
scene/src/delivery.ts), so the frame at time t is the same on every run.route (a polyline; y is the floor height under the feet) and its drop spot in houses.ts. Stairs are ramps in the route, so the feet do not land on each step.One-time setup, from the repo root:
uv venv --python 3.12 bench/.venv
uv pip install --python bench/.venv/bin/python -r bench/requirements.txt
bench/.venv/bin/python -m playwright install chromium
Start the scene (cd scene && npm run dev), or use the live site with --url. The dev server reloads the page when a file in scene/ changes, and a reload stops the run; for a long run, use the live site or a build (npm run build && npx vite preview). Then:
bench/.venv/bin/python bench/run.py # all 5 detectors, all 100 scenarios, 2 frames per second
bench/.venv/bin/python bench/run.py --detectors ssdlite yolo11x
bench/.venv/bin/python bench/run.py --limit 5 # the first 5 scenarios only
bench/.venv/bin/python bench/run.py --scenarios s001 s007
bench/.venv/bin/python bench/run.py --images # also write marked-up frames to bench/out/
bench/.venv/bin/python bench/run.py --url https://antioch-demo-hoeksema.vercel.app
Progress: one line for each scenario and detector, printed as soon as it is scored, then a table by setting at the end:
[ 12/100] s012 ssdlite 46% (21/46) FA 0 | so far 58% | stoop · delivery · night · rain · fisheye · hooded · walk · textured · bins | 62 frames, 14 s, about 22 min left
Detectors (all trained on COCO, real photos; only "person" boxes with a score of at least 0.5 count):
| id | Model | Library | Type |
|---|---|---|---|
frcnn-mobilenet | Faster R-CNN MobileNetV3 (fasterrcnn_mobilenet_v3_large_fpn) | torchvision | Two-stage, small (v1) |
ssdlite | SSDlite320 MobileNetV3 (ssdlite320_mobilenet_v3_large) | torchvision | One-stage, mobile, fastest (v1) |
frcnn-resnet50 | Faster R-CNN ResNet-50 v2 (fasterrcnn_resnet50_fpn_v2) | torchvision | Two-stage, strong |
yolo11x | YOLO11x | Ultralytics (AGPL-3.0) | One-stage CNN, the usual industry choice |
rtdetr-x | RT-DETR-x | Ultralytics (AGPL-3.0) | Transformer |
bench/weights/ (not in git). The Ultralytics usage analytics are off.--device). MPS and the CPU gave the same boxes, and two MPS runs gave the same boxes.frcnn-mobilenet 108 ms, ssdlite 109 ms, yolo11x 307 ms, frcnn-resnet50 315 ms, rtdetr-x 352 ms.Scenarios: the fixed list in bench/scenarios.json, made by bench/scenarios.py. Every detector runs on the same 100 scenarios:
window.bench.settings()), so a renamed value fails and does not fall back to a default.Frames:
basic, 960 × 960 for ring (the same number of pixels).--software).bench/frames/<scenario>/ caches the PNG frames, so every detector reads the exact same pixels and a second detector does not render again. The cache key is the query, the frame rate, the URL, and the last git commit of scene/ (not counting the results and the Benchmark page files). The full list is about 4.3 GB. --recapture renders again.How it scores:
ring) and takes the box around them. The box covers the whole body, also the parts behind walls or rails.Output (in git, so the deployed page shows it):
scene/public/results/index.json: the scenario list, and for each detector a summary of each scenario, of all scenarios, and by setting.scene/public/results/<detector>/<scenario>.json: every frame, with the true box and every detection.A run replaces the results for the detectors and scenarios it runs and keeps the others. Results for scenarios that are not in the list are dropped.
Notable frames: after a run, bench/.venv/bin/python bench/gallery.py picks frames (every detector missed a near person, only one detector hit, the farthest hits, low light on the fisheye, false alarms, the best and worst scenarios) and saves them in scene/public/notable/, with a 640 px copy of each in thumbs/ for the Gallery page (the boxes only, with no caption or tags). Each picture shows the true box and every detector's boxes, with a caption band: the reason, the scenario, every setting, the time, the distance, and each detector's result. A JSON file next to each picture has the same data and links to the frame on the live site. scene/public/notable/README.md shows them by category on GitHub.
More data in each frame record: low (person boxes from 0.1 up to the threshold, not counted, for other thresholds later), iou (the best overlap of a counted box with the true box), and ms (detector time). With --images, marked-up frames also go to bench/out/<detector>/<scenario>/ (not in git).
One sentence on what ran, a picture of one frame with the best detector's box (notable/hero.jpg, made by bench/gallery.py from HERO), then one chart, styled after antioch.com. One column for each detector; its name links to the model's page (torchvision or Ultralytics docs), with one sentence on how the model works and its parameter count under it (MODELS in scene/src/results.ts; counted from the weights). One row group for each category, each with its own color: a block at the left with the category name and its unit (for example "% detected"; hover it for what the numbers mean), and a light tint on its rows.
Only the best value in each row has a color. In the detection rate rows (%), the color is on a scale from red (the lowest best rate on the chart: child height, 56%) to bright green (the highest: 0–3 m, 98%), through amber. The scale bends toward green (GREEN_BIAS 0.25 in benchmarks.ts): about 75% is green, and only the rates near the low end are red. The other rows (first seen, false alarms, time) show their best value in green. Hover a row label for its frame count, and a value for its counts. The replay is on the Gallery page.
/results and /results.html (the old results page) redirect to /benchmarks (scene/vercel.json).
No text: the replay, then the pictures.
bench/gallery.py, in one grid. Hover a picture for why it is notable. Click it to load that frame in the replay.All 5 detectors on the 100 fixed scenarios (24,140 detector frames; 1 h 43 min on an M1 Pro from the frame cache).
| frcnn-resnet50 | rtdetr-x | yolo11x | frcnn-mobilenet | ssdlite | |
|---|---|---|---|---|---|
| Detection rate (deliveries) | 74% | 67% | 65% | 48% | 15% |
| Day / dusk / night / IR | 82 / 78 / 69 / 67% | 72 / 75 / 65 / 58% | 74 / 67 / 63 / 58% | 61 / 50 / 47 / 36% | 19 / 17 / 14 / 13% |
| Basic / fisheye | 83 / 65% | 71 / 64% | 78 / 53% | 53 / 43% | 17 / 14% |
| Child height | 56% | 50% | 43% | 30% | 7% |
| At 12+ m | 62% | 53% | 49% | 15% | 0% |
| False alarms, no-person events | 3.9/min | 2.5/min | 1.4/min | 5.7/min | 0.0/min |
| Time per frame (MPS) | 315 ms | 352 ms | 307 ms | 108 ms | 109 ms |
Vercel project antioch-demo-hoeksema, with Root Directory scene. Vercel detects Vite; the output is dist/. GitHub is connected, so each merge to main deploys to production. A manual deploy is vercel deploy --prod --yes from the repo root.
Visitor tracking: Vercel Web Analytics (page views, no cookies), from scene/src/analytics.ts on all three pages. It must be on in the Vercel dashboard (project → Analytics → Enable), and it starts after the next deploy. A browser that a script drives (navigator.webdriver or headless Chrome: the benchmark's captures, Playwright checks) does not count. The data is in the dashboard under Analytics.
v9: three pages (Simulation, Benchmark, Gallery); nine scenario axes; a Python person-detection benchmark with five detectors on 100 fixed scenarios, and 67 notable frames.