DEV Community

Cover image for Reality Compiler: Gemma 3 Turns a Room Photo into Data You Can Check, Including What It Can't See
Changsu Seong
Changsu Seong

Posted on

Reality Compiler: Gemma 3 Turns a Room Photo into Data You Can Check, Including What It Can't See

Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle Submission 💸

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle

What I Built

Reality Compiler turns one photo of a room into a RoomGraph: JSON that other tools can read, not a picture to look at.

Most photo-to-room tools aim for a smooth 3D mesh. A mesh looks equally sure of itself everywhere, including behind the bed, where the camera saw nothing. If the next question is "will this sofa get through that door?", that false confidence is the expensive part.

The 3D view of a real compile: a dorm room with a bed, wardrobe, desk, chair and bedside table, solid volumes for observed objects and dashed ones for inferred objects, each with a plus-or-minus centimetre shell

This is a real compile of a CC0 dorm-room photo ("Single dorm room, UVA.jpg" on Wikimedia Commons) with gemma3:4b, shown in the app's 3D view. Solid volumes are observed, dashed ones are inferred, and every object has its ±cm shell.

So the output is data with its doubt attached:

  • Every object says how it is known. Each node is observed (its floor corners were visible), inferred (partly hidden, or sized from a standard-furniture prior) or occluded_prior (in a blind spot, deduced from context). It also carries a confidence and a ±cm band on position and size.
  • A moving what-if check runs on top of that. It evaluates the layout twice: with the nominal numbers, and with every error band pushed the wrong way. It reports DOOR_BOTTLENECK, OBJECT_COLLISION, OUT_OF_BOUNDS and UNCERTAINTY_OVERFLOW, so a 2 cm gap that could really be negative becomes "measure this one gap with a tape measure" instead of a green tick.
  • A workbench to look at it. A 2D top-down blueprint (drag, rotate by 90°, drop in a King bed or a washer-dryer to test), a 3D view (Three.js) where observed objects are solid, inferred ones see-through and occluded ones ghosted, with dashed ±cm uncertainty shells, and where you can drag objects across the floor, JSON and CSV export, and an English/Korean UI.
  • A rough moving quote. It is a hand-written formula over volume, weight and disassembly count, with a wider upper bound when many objects are inferred. It is not machine learning, and the P10/P90 values are heuristic spreads, not calibrated quantiles.

One node of the output looks like this (trimmed; values are illustrative):

{
  "id": "obj-1-queen-bed",
  "label": "Queen Bed",
  "epistemic": "inferred",
  "confidence": 0.82,
  "bbox2d": null,
  "footprint": { "xCm": 20, "yCm": 30, "widthCm": 154, "depthCm": 206, "heightCm": 70, "rotationDeg": 0 },
  "uncertainty": {
    "posPlusMinusCm": 8,
    "dimPlusMinusCm": 10,
    "reason": "Headboard hidden by the duvet; sized from a standard-queen prior"
  }
}
Enter fullscreen mode Exit fullscreen mode

A real node from the dorm-room compile, as the app's JSON tab shows it:

One node of the real RoomGraph JSON: the bed with epistemic inferred, confidence 0.85, bbox2d null and its footprint

And the same bed in the inspector. Gemma had placed it outside the room, so the code moved it 20 cm inside, took it from observed to inferred and wrote the move into its reason. Its position is a guess, not a reading.

The scene inspector for the bed: inferred, 85 percent confidence, plus-or-minus 20 cm on position, and the reason that says the model placed it outside the room and it was moved 20 cm to fit inside

The side hustle

The idea is for people who plan moves or furniture deliveries without visiting the room: a small moving crew pricing a studio from a customer's photo, or a tenant asking "does the 140 cm standing desk still fit if I rotate it?". I haven't put it in front of a real customer, so treat that as a hypothesis, not a result.

Demo

An 11-step walkthrough of one real compile: upload a dorm-room photo, gemma3:4b compiles it in 48 seconds, the RoomGraph appears as a 3D room, the inspector shows the bed as inferred and moved 20 cm into the room, the what-if verifier flags collisions, then the JSON, the Sentry trace and the MongoDB Atlas tab

The GIF is one real compile of a CC0 dorm-room photo with gemma3:4b on my Mac. It is stitched from screenshots taken during that run, not a continuous screen recording, and the 48 s wait is cut. The captions say what each step shows.

It runs locally. You need Node 20 or newer and Ollama.

npm install
ollama pull gemma3:4b
npm run dev
Enter fullscreen mode Exit fullscreen mode

Then open the Vite URL it prints (http://127.0.0.1:5173 by default), upload a PNG, JPEG or WebP, set the reference object (a 210 cm door frame by default) and wait. On my Mac a compile takes 40 to 50 seconds, and 90 to 110 seconds when Gemma's first answer is rejected and it gets a second try.

If Gemma can't produce a usable answer, the app does not pretend. It shows a bundled sample room under an amber banner that says it is not your photo and why, and it does not save that result automatically. The three rooms in the sidebar are hand-authored samples, labelled as such; loading one does not call a model.

What a compile gives you

The source photo stays next to the result at its own aspect ratio. Compiled rooms get no object boxes, because gemma3:4b could not place them, and the panel says so instead of drawing guesses.

The source photo panel for the dorm-room compile, with a note that no object boxes are drawn because the vision model is not asked to locate objects

The moving what-if check runs on the same graph. Here the nominal layout is blocked by a desk and chair that overlap by 10 cm, and the worst-case run adds the gaps that look fine but could be wrong: the desk and the bedside table touch at 0 cm, with a combined uncertainty of ±46 cm.

The what-if verifier: nominal geometry blocked by a collision, worst-case uncertainty collision, and a list of alerts such as OBJECT_COLLISION and UNCERTAINTY_OVERFLOW with a note to verify one gap with a tape measure

Code

Repository: github.com/scs0209/reality-compiler

Stack: TypeScript, React, Vite, Tailwind, Three.js, Express, Zod, Vitest, Ollama. The code is MIT-licensed; Gemma is not part of the repo and stays under Google's Gemma Terms of Use.

  • shared/roomGraphSchema.ts: the roomgraph.v1.2 schema.
  • server/gemmaCompiler.ts: the Ollama call, the retry, and the fallback.
  • server/gemmaScene.ts: the prompt, the JSON Schema given to Ollama, validation of Gemma's answer, and edge derivation.
  • shared/spatialEngine.ts: the what-if checks and the quote formula.
  • server/sentryAgentTracer.ts: tracing (optional, see below).
  • tests/: 33 tests. Ollama is mocked, and Sentry's outgoing envelopes are intercepted to check what would be sent.

How I Built It

Schema first. roomgraph.v1.2 is a Zod schema, and every graph, whether it comes from Gemma or a sample, has to pass it.

Ask the model for less. A 4B model is not a surveyor. Gemma gets the photo, a reference object of known size, and a JSON Schema that Ollama uses to constrain decoding. It returns the room size, the objects, the openings and the blind spots. Everything else is code: ids, the relations between objects (derived from footprint geometry), and the epistemic counts.

Don't trust the model's own confidence. An object marked inferred is capped at 0.85 and an occluded_prior one at 0.6, however sure Gemma sounds, and the number of capped objects is recorded.

A failure that looks like a success is worse than a failure. A rejected answer is retried once with the validation error in the prompt. If that fails too, the user sees the sample room and the reason. An earlier version of this project returned canned sample data with made-up token counts when the model wasn't reachable. I deleted all of that. Token counts now come from Ollama, and durations are measured.

What broke.

  • Photo boxes. First Gemma copied the footprint's centimetre values into the box field, so every box had zero area. I fixed the wording, but on a real room photo the boxes then landed in the top-left corner of the photo, or on the wrong object (the bed's box over the window, a chair's box over a shelf), whether I asked for 0 to 1000 or for pixels. That was one photo, not a benchmark, but it was enough: the model is no longer asked for boxes. Compiled rooms have none, the UI says so, and only the hand-authored samples draw boxes on their photo.
  • Placement. Gemma also places objects outside the room it just described, even when told not to. On that same photo it put a 280 cm bed 300 cm from the west wall of a 360 cm room. I couldn't prompt that away, so the code moves such an object back inside, marks it inferred with a wider ± band, and writes the move into its reason. Its position is a guess, not a reading. The number of moved objects is recorded in the trace.
  • A range check that doubled the run time. gemma3:4b writes the ceiling height in millimetres: 2700 and 2400 for 270 cm and 240 cm ceilings. My validation rejected anything above 600 cm, so two of my five test photos were thrown away and asked again, taking 107.8 s and 90.6 s instead of about 45. The ceiling height only sets the wall height in the views, so it is now corrected instead of rejected: 1800 to 6000 is read as millimetres, any other value outside 180 to 600 cm is clamped, and each correction is recorded in the trace (gemma.ceiling_height_adjusted). The same two photos now pass on the first attempt, in 46.2 s and 40.0 s. Room width and depth keep their strict checks, because a "corrected" room size would just be a wrong one.

Which Gemma? The newer Gemma 4 exists, and gemma4:e4b (6.6 GB in Ollama, shipped with an Apache 2.0 licence file) fits on my 16 GB Mac, so I ran the same five room images through both models: same prompt, same schema, temperature 0, one run each.

gemma3:4b gemma4:e4b
Time per photo 40 to 51 s 107 to 120 s
Output tokens per attempt 1.2k to 1.4k 2.4k to 2.9k
Photos that ended in a usable RoomGraph 5 of 5 4 of 5
Objects the model placed outside the room 4 of 28 2 of 20

On the four photos where both finished, that last row is 3 of 22 against 2 of 20. Gemma 4 failed on the busiest room (a dining area with a shelving unit): both attempts ran into the 3,500-token output limit, so the app showed its sample room after 285 seconds.

Four room images from Wikimedia Commons, each with the floor objects gemma3:4b and gemma4:e4b listed for it, plus time and the number of objects placed outside the room

The figure shows the four images I can show, with what each model listed. The fifth, a bedroom photo, is in the table above but not in the figure (see below).

What I saw, judged by eye: Gemma 4 was better on a living room (sofa, armchair, TV console and three plants, missing only the floor lamp) and slightly cleaner on a 3D render. Gemma 3 was better on a bedroom (bed, chair, shelf, plant and stool, where Gemma 4 gave bed, nightstand, drawers and chair). Both found the bed, desk, chair and wardrobe in a dorm room. Both make mistakes: Gemma 3 called a cat a dog and listed a TV and a candle as floor objects; Gemma 4 returned Korean labels for one photo and snake_case names for another although the prompt asks for English names.

My reading: Gemma 4 E4B put fewer objects outside the room, but not by enough to matter in five photos, and it was about 2.4 times slower and less reliable on a busy room. The app keeps gemma3:4b as its default. I did not tune the prompt or raise the output limit for Gemma 4, so this compares one setup, not the models.

The limits of this test: five images, one run each, no labelled ground truth, so "right" and "wrong" are my own reading of each picture. One image is a 3D render, not a photograph, and a difference of one or two objects is noise at this size. It is not a benchmark. The bedroom photo is one I found online without recording its source or licence, so I don't show it here. The other four are CC0 files from Wikimedia Commons, credited here and in the figure although CC0 does not require it: "Single dorm room, UVA.jpg", "Living room (Unsplash).jpg", "The living room that needs houseplants.jpg", and "Living Room Interior Designer - Picker Online.jpg" (the 3D render). Authors and source pages are in docs/post-images/CREDITS.md.

Optional integrations. With SENTRY_DSN set, each compile is sent to Sentry as a gen_ai.invoke_agent trace: one span for the preflight check, one for each Gemma request (with Ollama's real input and output token counts), one for validation and one for the save. Photos, file names and prompt text are never attached. Without a DSN nothing is sent, and the header and /api/health say "not sending". With MONGODB_URI set, compiled RoomGraphs are stored in MongoDB Atlas; note that this includes the source photo, so it is an explicit opt-in.

Here is a normal gemma3:4b compile in Sentry. The whole run is 46.67 s and the model call is 46.58 s of it, about 99.8%. The Ollama preflight takes 13 ms, validation 3 ms and the Atlas save 40 ms, so the model is the only thing worth optimising.

The Sentry waterfall of a gemma3:4b compile: gen_ai.invoke_agent of 46.67 seconds with a preflight, one gen_ai.chat span of 46.58 seconds, compiler.assemble and db.mongodb.upsert

And the run that failed: gemma4:e4b on the busiest of my five rooms. Two gen_ai.chat spans of 2.26 and 2.20 minutes each ended at the 3.5k output-token limit, both answers were cut off, and the app fell back to its sample room after 4.45 minutes. Sentry's Agents view lists it with one error, and nothing was saved to Atlas.

The Sentry waterfall of the failed gemma4:e4b run: two gen_ai.chat spans of about two minutes each, two compiler.assemble spans and then compiler.fallback_preset

Sentry's Agents view listing six compiles of the same test set, with root duration, errors, LLM calls and total tokens, and the failed run marked with one error

And the saved-rooms list in the app, read back from the Atlas collection: the three sample rooms and the compiles stored there.

The app's MongoDB Atlas tab: a search box, an upsert button and the list of saved rooms with their scene ids and object counts

And the collection in Atlas itself: 11 documents and 3 indexes (the default _id, a unique sceneId and the text index), with the three seeded sample rooms first.

The roomgraphs collection in MongoDB Atlas: database reality_compiler, 11 documents, 3 indexes, and the first two seeded sample rooms as documents

Opened on one stored compile, compileMeta holds the model that answered, its runtime and measured latency (46,634 ms) and the Sentry trace id, so a stored room can be matched to its trace. It is the same run as the first Sentry waterfall above.

One stored compile in Atlas with compileMeta expanded: model gemma3:4b, runtime ollama-gemma3:4b, latencyMs 46634 and the Sentry trace id

Why Does Open Innovation Matter?

  • Photos of your home are private. With gemma3:4b running locally through Ollama, the photo stays on your machine. That changes if you point the app at a remote Ollama or turn on Atlas storage, and both are your choice.
  • Open weights let the whole pipeline run on one machine. A pinned model, schema-constrained decoding and no network dependency make "validate it, retry once, show the failure" cheap enough to do on every photo.
  • The trade-off is speed. There is no per-photo API bill, but a compile takes 40 to 50 seconds on my Mac. That suits "check before moving day", not interactive editing.
  • Be precise about "open". Gemma 3 is open-weight under Google's own terms, not open source in the OSI sense; the Gemma 4 build I tried ships with an Apache 2.0 licence file. The model name is a config value (GEMMA_MODEL). I compared gemma3:4b and gemma4:e4b on five images (see "Which Gemma?"), and the default and the demo use gemma3:4b.

My Agent Session

I built this with Claude Code, and Entire recorded the sessions and linked them to
commits: 14 of the repository's 15 commits carry an Entire-Checkpoint: trailer. The records are in a private repository because they also contain my
local paths and e-mail address. Entire kept two session records for this build; the second overlaps the
first and holds 42 of my prompts against 64 in the first.

My prompts were in Korean. Each heading below is my English translation, and the Korean original is under
it. The embedded sessions show only the conversation: my prompts, the agent's replies and its written
reasoning, each with the Korean original and my English translation under it. Tool calls and their output
are left out. I picked seven moments where something was decided or broke, including several where
something was wrong and had to be undone.

1. "Start with the real Gemma integration. Validate Ollama's structured output with RoomGraphSchema, say in the UI when it falls back, and remove the fake token and latency values."

Start with the real Gemma integration. Validate Ollama's structured output with RoomGraphSchema, say in the UI when it falls back, and remove the fake token and latency values. I allow the gemma3:4b download (ollama pull).

An earlier prototype of this project returned canned data with made-up token counts when the model was not
reachable. The agent replaced it with a real Ollama call that asks for JSON Schema output, validates the
answer against the RoomGraph schema, retries once with the validation error, and otherwise shows a
bundled sample room under a banner that says it is not your photo. Tokens now come from Ollama and durations
are measured. My next prompt asked for the same honesty from Sentry: real gen_ai spans, a real trace id,
and a header that says "not sending" when there is no DSN. The TabPFN labels went too, because the moving quote
is a hand-written formula and not a model.

Commit: 2f22321 "Wire Gemma and Sentry for real; drop fabricated telemetry and TabPFN claims"

Claude Code Session claude-sonnet-5-5
You

Gemma 실제 연동부터 시작해줘. Ollama 구조화 출력을 RoomGraphSchema로 검증해서 쓰고, 실패하면 폴백임을 UI에 명시해줘. 가짜 토큰·지연 값도 제거해줘. gemma3:4b 다운로드(ollama pull)는 허락해.


English translation:

Start with the real Gemma integration. Validate Ollama's structured output with RoomGraphSchema, say in the UI when it falls back, and remove the fake token and latency values. I allow the gemma3:4b download (ollama pull).

Agent
Agent
Agent
Agent
Agent

Gemma 다운로드를 먼저 백그라운드로 시작하고, 그동안 좌표 규칙과 UI 흐름을 읽겠습니다.


English translation:

I'll start the Gemma download in the background first, and read the coordinate rules and the UI flow in the meantime.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

이제 구현합니다. 먼저 스키마를 정직하게 표현할 수 있게 확장합니다(런타임 구분, 폴백 정보, 보정값 없음 허용).


English translation:

Implementing now. First I'm extending the schema so it can say honestly what happened (runtime kind, fallback info, and allowing "no calibration value").

Agent
Agent
Agent
Agent
Agent
Agent
Agent

이제 Gemma 출력을 검증·조립하는 모듈을 새로 작성합니다. 네트워크 호출이 없는 순수 함수라서 테스트하기 쉽습니다.


English translation:

Now I'm writing a new module that validates and assembles Gemma's output. It's pure functions with no network calls, so it's easy to test.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

Gemma 모델 다운로드는 아직 진행 중이고, 서버 쪽 타입 오류 두 건을 고치는 중입니다. atlasPersisted 기본값이 빠졌습니다.


English translation:

The Gemma model download is still in progress, and I'm fixing two type errors on the server side. The atlasPersisted default was missing.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

Now the preset/upload/recompile handlers: preset selection becomes a plain sample load, and upload failures show an error instead of faking a graph.

Agent
Agent

다운로드 작업이 끝났습니다. 결과부터 확인합니다.


English translation:

The download job finished. Checking the result first.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

변환한 이미지는 사진이 아니라 도식 일러스트입니다. 파이프라인 동작을 확인하는 용도로만 쓰고, 지금 실제 Gemma 호출을 백그라운드로 돌립니다.


English translation:

The converted image is a schematic illustration, not a photo. I'm only using it to check that the pipeline works, and I'm running the real Gemma call in the background now.

Agent
Agent

실행하는 동안 내가 바꾼 동작과 어긋나는 문서 문구(없어진 span 이름 등)를 찾아 두겠습니다.


English translation:

While it runs, I'll look for wording in the docs that no longer matches the behaviour I changed (removed span names and so on).

Agent
Agent
Agent

스크립트 형식 문제입니다(최상위 await). .mts로 바꿔 다시 실행합니다.


English translation:

It's a script format problem (top-level await). Renaming it to .mts and running it again.

Agent
Agent
Agent
Agent

모델이 초당 약 32토큰으로 생성 중입니다(Metal GPU). 기다리는 동안 전체 빌드를 확인합니다.


English translation:

The model is generating at about 32 tokens per second (Metal GPU). I'll check the full build while we wait.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

5173 포트를 이미 다른 node 프로세스가 쓰고 있습니다. 이 프로젝트의 개발 서버인지 먼저 확인하겠습니다. 프로세스는 건드리지 않습니다.


English translation:

Port 5173 is already taken by another node process. I'll check first whether it's this project's dev server. I won't touch the process.

Agent
Agent
Agent
Agent
Agent
Agent

프리뷰 도구가 PORT=5180을 주입해서 API 서버가 Vite와 같은 포트를 잡으려 했습니다. API 포트를 8787로 고정해 다시 띄웁니다.


English translation:

The preview tool injected PORT=5180, so the API server tried to grab the same port as Vite. Pinning the API port to 8787 and restarting.

Agent
Agent
Agent
Agent
Agent
Agent
Agent

폴백 배너가 의도대로 뜹니다. 이제 실제 Gemma 업로드를 UI에서 실행합니다(약 100초).


English translation:

The fallback banner shows up as intended. Now I'm running a real Gemma upload from the UI (about 100 seconds).

Agent
Agent
Agent
Agent
Agent

컴파일이 끝났고 폴백 배너가 사라졌습니다. 결과 화면과 Sentry 탭의 span(재시도 사유 포함)을 확인합니다.


English translation:

The compile finished and the fallback banner is gone. Checking the result screen and the spans in the Sentry tab (including the retry reason).

Agent
Agent
Agent
Agent
Agent

이제 실제 Gemma를 한 번 더 돌려서 첫 시도 실패의 구체적 원인을 봅니다.


English translation:

Now I'll run the real Gemma once more to see the specific cause of the first-attempt failure.

Agent
Agent
Agent
Agent
Agent
Agent
Agent

bbox를 null 허용으로 바꾸는 작업 중입니다. 서버·UI는 타입체크를 통과했고, 새 형식에 맞게 테스트를 고치고 있습니다.


English translation:

I'm in the middle of making bbox nullable. Server and UI pass the typecheck, and I'm updating the tests for the new format.

Agent
Agent
Agent
Agent
Agent

최종 코드로 UI를 다시 확인합니다. 실제 업로드 후 사진 위 박스와 배너 상태를 봅니다.


English translation:

Checking the UI again with the final code. After a real upload I'll look at the boxes on the photo and the banner state.

Agent
Agent
Agent
Agent
Agent

UI에서 실제 Gemma 결과가 그대로 반영됩니다(폴백 배너 없음, 사진 위 박스 5개, px/cm: n/a). 이제 임시 서버를 정리하고 문서의 낡은 표현을 고칩니다.


English translation:

The UI shows the real Gemma result as is (no fallback banner, 5 boxes on the photo, px/cm: n/a). Now I'll clean up the temporary servers and fix outdated wording in the docs.

Agent
Agent
Agent

이미지 서버 종료 알림은 방금 제가 pkill한 결과라 정상입니다. 이제 DEV_SUBMISSION의 Gemma 설명을 실제 구현에 맞춥니다.


English translation:

The image server shutdown notice is expected: I just ran pkill on it. Now I'll make the Gemma description in DEV_SUBMISSION match the actual implementation.

Agent
Agent
Agent

최종 검증을 돌린 뒤 HANDOFF.md를 현재 상태로 갱신합니다.


English translation:

I'll run the final checks and then update HANDOFF.md to the current state.

Agent
Agent
Agent

Gemma 실제 연동을 마쳤습니다. 사진 한 장을 gemma3:4b로 분석해 RoomGraph를 만들고, 실패하면 폴백이라고 UI에 표시합니다.

바뀐 것
- Gemma 호출 (server/gemmaCompiler.ts): 사진과 JSON Schema를 Ollama에 보내 구조화 출력을 받습니다.
- 응답은 항목별로 검증하고 RoomGraphSchema로 최종 검증합니다.
- 객체·구멍은 잘못된 항목만 버립니다. 엣지와 요약은 코드로 계산합니다.
- 첫 답이 거부되면 사유를 붙여 한 번 재시도합니다.
- 검증·조립 로직은 새 파일 server/gemmaScene.ts에 있습니다.
- 폴백 표시: 두 번 모두 실패하면 샘플 방으로 대체하고 compileMeta.fallback에 단계와 원인을 기록합니다. 화면에는 "샘플 방이며 사진이 아님" 경고 배너가 뜹니다. 폴백 결과는 Atlas에 저장하지 않습니다.
- 가짜 수치 제거:
- 지어낸 토큰 수와 +110ms 같은 span 시간을 없애고, Ollama가 보고한 값과 실측값만 씁니다.
- 프리셋의 가짜 Gemma/Sentry 메타데이터와 서버 시작 시 가짜 트레이스 주입도 제거했습니다.
- atlasPersisted는 메모리에만 저장된 경우 이제 false입니다.
- UI:
- 프리셋은 "직접 작성한 샘플"로만 불러옵니다(컴파일하지 않습니다).
- "다시 컴파일"은 업로드한 사진이 있을 때만 켜집니다.
- 업로드 실패 때 가짜 그래프를 만들던 코드를 에러 표시로 바꿨습니다.
- /api/health가 Ollama 도달 여부와 모델 설치 여부를 실제로 보고합니다.

검증 (typecheck, 테스트 16/16, 빌드 통과)
- 실제 Gemma를 UI 업로드부터 사진 위 박스와 what-if까지 돌렸고, 약 48초에 재시도 없이 성공했습니다.
- 폴백도 실제로 확인했습니다. SVG 업로드와 모델이 실제로 두 번 실패한 경우 모두 배너가 뜨고 기록이 남았습니다.
- 모델 응답을 직접 보니 bbox에 cm 숫자를 넣고 있어서, 형식을 0~1000 정수 모서리로 바꿨습니다. 쓸 수 없는 박스는 객체를 버리지 않고 null로 둡니다.

한계
- 실사 사진으로는 아직 돌려보지 못했습니다. 테스트 입력은 도식 일러스트였고, 침대를 "소파"로 읽는 등 라벨이 틀렸습니다. 실제 정확도는 실사 사진으로 확인이 필요합니다.
- 4B 모델이 방 크기를 정하고도 방 밖에 배치하는 일이 반복됩니다. what-if가 OUT_OF_BOUNDS로 표시하고 개수는 trace에 기록됩니다. 프롬프트로는 고쳐지지 않았습니다.
- Sentry는 여전히 Sentry.init만 있고 실제 span 전송이 없습니다. 화면의 trace ID는 로컬 난수이고, /api/health와 헤더 표시도 실제와 다릅니다.
- entire-session/devrelay-session.json에는 옛 스팬 이름과 TabPFN 서술이 남아 있습니다.

환경에 남은 것
- ollama serve를 제가 백그라운드로 띄워 둔 상태이고, gemma3:4b(3.4GB)가 설치됐습니다.
- .claude/launch.json을 추가했습니다.
- 5173 포트는 다른 도구(freebuff)의 Vite가 쓰고 있어 건드리지 않았고, 저는 5180 포트를 썼습니다.
- 변경 사항은 모두 커밋하지 않은 상태입니다. HANDOFF.md도 갱신했습니다.


English translation:

The real Gemma integration is done. One photo is analysed with gemma3:4b to build a RoomGraph, and when that fails the UI says it's a fallback.

What changed
- Gemma call (server/gemmaCompiler.ts): sends the photo and a JSON Schema to Ollama and gets structured output back.
- The response is validated item by item, then validated as a whole with RoomGraphSchema.
- Only invalid objects and openings are dropped. Edges and the summary are computed in code.
- If the first answer is rejected, it retries once with the reason attached.
- The validation and assembly logic lives in a new file, server/gemmaScene.ts.
- Fallback display: if both attempts fail, it substitutes a sample room and records the stage and cause in compileMeta.fallback. The screen shows a warning banner saying "this is a sample room, not your photo". Fallback results are not saved to Atlas.
- Fake numbers removed:
- Made-up token counts and span times like +110ms are gone; only values reported by Ollama and measured values are used.
- I also removed the fake Gemma/Sentry metadata in the presets and the fake traces injected at server startup.
- atlasPersisted is now false when the result is only stored in memory.
- UI:
- Presets load only as "hand-authored samples" (they are not compiled).
- "Recompile" is enabled only when there is an uploaded photo.
- The code that built a fake graph when an upload failed now shows an error instead.
- /api/health really reports whether Ollama is reachable and whether the model is installed.

Verification (typecheck, tests 16/16, build passing)
- I ran the real Gemma from a UI upload through to the boxes on the photo and the what-if; it succeeded in about 48 seconds with no retry.
- I also checked the fallback for real. Both for an SVG upload and for a case where the model genuinely failed twice, the banner appeared and the record was kept.
- Looking at the model's responses directly, it was putting cm numbers into bbox, so I changed the format to integer corners from 0 to 1000. An unusable box is set to null instead of dropping the object.

Limits
- I haven't run it on a real photograph yet. The test input was a schematic illustration, and the labels were wrong (it read the bed as a "sofa", for example). Real accuracy needs to be checked with real photos.
- The 4B model keeps placing objects outside the room even after deciding the room size. The what-if flags it as OUT_OF_BOUNDS and the count is recorded on the trace. Prompting didn't fix it.
- Sentry still only has Sentry.init and sends no real spans. The trace ID on screen is a local random number, and /api/health and the header don't reflect reality either.
- entire-session/devrelay-session.json still has the old span names and the TabPFN description.

Left running in your environment
- I started ollama serve in the background, and gemma3:4b (3.4 GB) is installed.
- I added .claude/launch.json.
- Port 5173 is used by another tool's (freebuff's) Vite, so I left it alone and used port 5180.
- None of the changes are committed. I also updated HANDOFF.md.

Agent
You

Sentry 실제 span 전송을 구현해줘. server/sentryAgentTracer.ts에서 Sentry.startSpan으로 ai.run.gemma3, compiler.assemble, db.mongodb.upsert를 실측 시간과 gen_ai 속성으로 보내고, 실제 Sentry trace id를 compileMeta에 쓰게 해줘. DSN이 없으면 UI와 /api/health가 정직하게 '전송 안 함'으로 표시하게 해줘.


English translation:

Implement real Sentry span sending. In server/sentryAgentTracer.ts, use Sentry.startSpan to send ai.run.gemma3, compiler.assemble and db.mongodb.upsert with measured times and gen_ai attributes, and write the real Sentry trace id into compileMeta. When there's no DSN, have the UI and /api/health honestly show "not sending".

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

이제 컴파일러를 새 트레이서에 연결합니다. 시도마다 실제 Ollama 호출과 조립을 각각의 span으로 감싸고, 가짜 trace id 생성은 없앱니다.


English translation:

Now I'm wiring the compiler to the new tracer. Each attempt wraps the real Ollama call and the assembly in their own spans, and the fake trace id generation goes away.

Agent
Agent
Agent
Agent

덮어쓰기가 거부됐습니다. 파일이 제가 마지막으로 읽은 뒤 바뀌었다는 뜻이라, 현재 내용부터 읽고 확인한 다음 진행하겠습니다.


English translation:

The overwrite was refused. That means the file changed since I last read it, so I'll read the current content and check it before going on.

Agent
Agent
Agent

내용은 제가 앞서 파이썬으로 수정한 그대로여서 외부 변경은 아닙니다. 읽기 이력이 갱신됐으니 다시 씁니다.


English translation:

The content is exactly what I changed earlier with Python, so it isn't an outside change. The read history is updated now, so I'll write it again.

Agent
Agent
Agent

서버 쪽 타입체크가 통과했습니다. 이제 /api/health를 실제 상태로 바꾸고, 종료 시 대기 중인 span을 flush하도록 합니다.


English translation:

The server-side typecheck passes. Now I'll make /api/health reflect the real state and flush pending spans on shutdown.

Agent
Agent
Agent
Agent
Agent

이제 UI입니다. 헤더의 Sentry 표시와 Sentry 탭이 서버의 실제 상태를 따르게 합니다.


English translation:

Now the UI. The Sentry indicator in the header and the Sentry tab will follow the server's real state.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

실제 Gemma 호출 + SDK 기본 HTTP 전송 경로에서 transactionsSent: 1, 응답 200을 확인했습니다. 수신 서버에 실제로 도착한 내용을 열어 봅니다.


English translation:

On the path with a real Gemma call + the SDK's default HTTP transport, I see transactionsSent: 1 and a 200 response. Opening what actually arrived at the receiver.

Agent
Agent
Agent
Agent
Agent

22개 테스트가 모두 통과했습니다. 이제 UI를 두 가지 상태에서 확인합니다. 먼저 DSN이 없는 기본 상태입니다.


English translation:

All 22 tests pass. Now I'll check the UI in two states, starting with the default state with no DSN.

Agent
Agent
Agent

DSN이 없을 때 헤더가 "Sentry: 전송 안 함"으로 바뀌었고 /api/health도 enabled: false와 사유를 보고합니다. 이제 컴파일 후 Sentry 탭을 확인합니다.


English translation:

With no DSN the header changed to "Sentry: not sending", and /api/health reports enabled: false with the reason. Now I'll compile and check the Sentry tab.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

DSN이 있으면 헤더가 정상 표시로 돌아오고 /api/health가 enabled: true로 바뀝니다. 실제 Gemma 컴파일이 끝나기를 기다립니다.


English translation:

With a DSN the header goes back to the normal display and /api/health switches to enabled: true. Waiting for the real Gemma compile to finish.

Agent
Agent
Agent

UI에 trace id와 "전송 성공 1건"이 표시됩니다. 수신 서버에 실제로 도착한 trace id가 화면의 값과 같은지 대조합니다.


English translation:

The UI shows the trace id and "1 sent successfully". I'll compare the trace id that actually reached the receiver with the one on screen.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

Sentry 실제 span 전송을 구현했습니다. 다만 진짜 Sentry 프로젝트로는 보내보지 못했고, 로컬 수신 서버(가짜 DSN)로 전송 내용을 확인했습니다.

구현한 것
- 전송 구조 (server/sentryAgentTracer.ts): 컴파일 1건이 gen_ai.invoke_agent 트레이스가 됩니다. 자식 span은 아래와 같습니다.
- compiler.preflight
- Gemma 요청(gen_ai.chat, 시도마다 1개)
- compiler.assemble
- db.mongodb.upsert (실패 시 compiler.fallback_preset)
- 측정값: 한 헬퍼가 Sentry span과 앱 내 워터폴을 동시에 만들어서 두 곳의 시간이 같은 실측값입니다. 토큰은 Ollama가 보고한 값입니다.
- trace id: compileMeta.sentryTraceId는 Sentry가 실제로 추적했을 때만 진짜 ID이고, 아니면 빈 문자열입니다. 로컬 난수 생성은 없앴습니다.
- 정직한 표시:
- DSN이 없으면 Sentry를 초기화하지 않습니다.
- 헤더가 "Sentry: 전송 안 함"으로 바뀌고 Sentry 탭에 이유가 표시됩니다.
- /api/health가 enabled, 사유, SDK가 보고한 전송 성공·실패 횟수를 보고합니다.
- 개인정보: 사진, 파일명, 프롬프트는 span에 넣지 않고(sendDefaultPii: false), 종료 시 대기 중인 span을 flush합니다.

말씀하신 것과 다르게 한 점
- Sentry의 AI 화면은 gen_ai.* op로 에이전트를 인식합니다(공식 문서 확인). 그래서 Sentry op은 ai.run.gemma3가 아니라 gen_ai.chat입니다. ai.run.gemma3는 span 속성 app.step과 앱 내 워터폴에 남겼습니다.
- compiler.preflight span을 추가했습니다. 호출하지도 않은 사전 점검 실패가 LLM 요청 에러로 보이는 걸 막기 위해서입니다.

검증 (typecheck, 테스트 22/22, 빌드 통과)
- 실제 Gemma 컴파일 1건이 SDK 기본 HTTP 전송을 거쳐 수신 서버에 트랜잭션 1건으로 도착했습니다.
- 화면의 trace id와 수신된 id가 일치했습니다.
- 토큰은 실측값(837+1470)이고, 사진 바이트와 파일명은 없었습니다.
- UI에서 DSN 유무 두 상태를 모두 확인했습니다.
- 검증 중 버그 2건을 찾아 고쳤습니다. 둘 다 회귀 테스트가 있습니다.
- transport: undefined를 넘기면 기본 전송이 덮어써져서, DSN을 설정한 실제 서버는 시작 시 죽었습니다.
- 전송 실패 시 Sentry.flush가 예외를 던졌습니다.

한계
- 실제 Sentry 프로젝트의 AI Agents 화면에서 트레이스가 에이전트로 보이는지는 확인하지 못했습니다. SENTRY_DSN을 설정한 뒤 직접 확인하고, 제출 글에 넣을 스크린샷을 찍어야 합니다.
- Sentry SDK가 Ollama 요청에 http.client span(URL 포함)을 자동으로 추가합니다. OLLAMA_BASE_URL이 내부 주소면 그 주소가 Sentry에 올라갑니다.
- 헤더의 다른 표시(Atlas, DigitalOcean, realitycompiler.tech)는 여전히 실제 상태를 반영하지 않습니다.
- entire-session/devrelay-session.json에는 옛 내용이 남아 있습니다.
- 변경 사항은 커밋하지 않았고, ollama serve는 계속 켜져 있습니다. HANDOFF.md도 갱신했습니다.


English translation:

Real Sentry span sending is implemented. However, I haven't sent to a real Sentry project; I checked what is sent using a local receiver (a fake DSN).

What I implemented
- Trace structure (server/sentryAgentTracer.ts): one compile becomes one gen_ai.invoke_agent trace, with these child spans:
- compiler.preflight
- the Gemma request (gen_ai.chat, one per attempt)
- compiler.assemble
- db.mongodb.upsert (compiler.fallback_preset on failure)
- Measurements: one helper creates both the Sentry span and the in-app waterfall, so both show the same measured time. Tokens are the values Ollama reports.
- trace id: compileMeta.sentryTraceId is a real ID only when Sentry actually traced the compile; otherwise it's an empty string. The local random generation is gone.
- Honest display:
- Without a DSN, Sentry is not initialised.
- The header changes to "Sentry: not sending" and the Sentry tab shows the reason.
- /api/health reports enabled, the reason, and the delivery success/failure counts reported by the SDK.
- Privacy: photos, file names and prompts are never put on spans (sendDefaultPii: false), and pending spans are flushed on shutdown.

Where I did it differently from what you asked
- Sentry's AI views recognise agents by gen_ai.* ops (checked in the official docs). So the Sentry op is gen_ai.chat, not ai.run.gemma3. ai.run.gemma3 is kept in the span attribute app.step and in the in-app waterfall.
- I added a compiler.preflight span, so that a preflight failure, where the model was never called, doesn't look like an LLM request error.

Verification (typecheck, tests 22/22, build passing)
- One real Gemma compile went through the SDK's default HTTP transport and arrived at the receiver as one transaction.
- The trace id on screen matched the one received.
- The tokens were measured values (837+1470), and there were no photo bytes or file names.
- I checked both states in the UI, with and without a DSN.
- I found and fixed two bugs during verification. Both have regression tests.
- Passing transport: undefined overwrote the default transport, so a real server with a DSN crashed at startup.
- Sentry.flush threw when delivery failed.

Limits
- I couldn't check whether the trace shows up as an agent in a real Sentry project's AI Agents view. You need to set SENTRY_DSN, check it yourself, and take the screenshots for the submission post.
- The Sentry SDK automatically adds an http.client span (including the URL) for requests to Ollama. If OLLAMA_BASE_URL is an internal address, that address goes up to Sentry.
- The other indicators in the header (Atlas, DigitalOcean, realitycompiler.tech) still don't reflect the real state.
- entire-session/devrelay-session.json still has outdated content.
- The changes are not committed, and ollama serve is still running. I also updated HANDOFF.md.

Agent

2. "[Pasted: RoomGraph JSON / Sentry trace / MongoDB Atlas (4) / Entire CLI session] And on the page, I see why RoomGraph JSON is needed, but is there a reason for the others? On the dashboard."


RoomGraph JSON
Sentry trace
MongoDB Atlas (4)
Entire CLI session

And on the page, I see why RoomGraph JSON is needed, but is there a reason for the others? On the dashboard.

The Sentry and Atlas tabs show real data from the compile, so they stayed. The Entire tab did not: it was
hand-written text with no connection to Entire, and it said the CLI was "active" while reading nothing from it.
It came out of the app, together with three toolbar chips that were always shown on wide screens (DigitalOcean,
a domain I do not own, and "Entire CLI tracked"). Entire belongs in this section of the post, not in the interface.

Commit: 416d025 "Show the source photo at its own aspect ratio, say when there are no boxes, and drop the Entire tab"

Claude Code Session claude-sonnet-5-5
You

<pasted_content id="c8ca">
RoomGraph JSON
Sentry 트레이스
MongoDB Atlas (4)
Entire CLI 세션
</pasted_content id="c8ca">

그리고 페이지에서 RoomGraph JSON은 필요한거 알겠는데 나머지가 필요한 이유가 있어? 대시보드에서


English translation:

<pasted_content id="c8ca">
RoomGraph JSON
Sentry trace
MongoDB Atlas (4)
Entire CLI session
</pasted_content id="c8ca">

And on the page, I see why RoomGraph JSON is needed, but is there a reason for the others? On the dashboard.

Agent
Agent
Agent
Agent
Agent

RoomGraph JSON만 앱의 핵심 결과물이고, Entire 탭은 앱에 있을 이유가 없습니다. Sentry와 Atlas 탭은 실제 데이터를 보여주지만 필수는 아닙니다.

탭 보여주는 것 앱에서의 필요성
RoomGraph JSON 결과 JSON과 JSON/CSV 내보내기 필수입니다.
Sentry 트레이스 이 컴파일의 trace id, 모델, 지연 시간, 토큰, 스팬 워터폴, 전송 성공·실패 건수 선택입니다. 값은 실측이고 "지금 결과를 얼마나 믿을지" 판단하는 데 쓰입니다. 폴백이나 재시도도 여기서 보입니다. 다만 Sentry 카테고리가 보는 건 Sentry 화면 스크린샷이라 앱 안의 탭은 보조입니다.
MongoDB Atlas (4) 저장된 방 목록, 검색, 불러오기, 저장 선택입니다. 이전에 컴파일한 방을 다시 열 수 있는 기록 기능이고, 지금은 실제로 Atlas에 저장됩니다. 검색은 최근 30건을 JS로 거르는 방식이라 README의 "전문 검색" 표현은 사실과 다릅니다.
Entire CLI 세션 손으로 쓴 "체크포인트 #1~#3" 문구 필요 없고 사실과도 다릅니다.

Entire 탭이 문제인 이유
- 제목이 "Entire CLI Active"이고 entire status / entire checkpoint list로 추적한다고 쓰여 있지만, 탭은 Entire의 실제 데이터를 하나도 읽지 않습니다.
- 안에 든 체크포인트 문구는 정적 텍스트입니다. 참조하는 entire-session/devrelay-session.json도 앞서 확인했듯 직접 쓴 파일이지 Entire나 DevRelay가 내보낸 것이 아닙니다.
- Entire 카테고리는 에이전트 세션을 글에 포함하라고 요구하지 앱 화면에 넣으라고 하지 않습니다. 세션은 DEV 글의 "My Agent Session"에 진짜 링크로 넣는 것이 맞습니다.
- 심사에서 앱 안의 파트너 탭은 점수 항목이 아니고, 확인할 수 없는 문구는 신뢰도만 깎습니다.

권장: Entire 탭만 앱에서 지우고 Sentry와 Atlas는 그대로 둡니다. 탭 이름을 "컴파일 트레이스", "저장된 방"처럼 기능 이름으로 바꾸는 것도 생각해 볼 만합니다. 다만 이건 선택이고 필수는 아닙니다.

앞서 제안한 정리 작업(README, /api/health, 배포 파일의 확인 안 된 주장 제거)에 이 탭 삭제를 넣으면 한 번에 끝납니다. Entire 탭을 지울지, 이름 변경까지 할지 알려 주세요.


English translation:

Only RoomGraph JSON is a core output of the app, and the Entire tab has no reason to be in the app. The Sentry and Atlas tabs show real data but aren't essential.

Tab What it shows Does the app need it?
RoomGraph JSON The result JSON and JSON/CSV export Yes, essential.
Sentry trace This compile's trace id, model, latency, tokens, span waterfall, delivery success/failure counts Optional. The values are measured and help judge "how far to trust this result". Fallbacks and retries also show up here. But the Sentry category looks at screenshots of Sentry itself, so the in-app tab is secondary.
MongoDB Atlas (4) Saved rooms: list, search, load, save Optional. It's a history feature for reopening rooms you compiled before, and it really saves to Atlas now. Search filters the latest 30 in JS, so the README's "full-text search" wording isn't accurate.
Entire CLI session Hand-written "checkpoint #1–#3" text Not needed, and not accurate either.

Why the Entire tab is a problem
- Its title is "Entire CLI Active" and it says it tracks via entire status / entire checkpoint list, but the tab doesn't read a single piece of real data from Entire.
- The checkpoint text inside is static. The entire-session/devrelay-session.json it refers to is, as we found earlier, a hand-written file, not an export from Entire or DevRelay.
- The Entire category asks you to include the agent session in the post, not in the app. The session belongs in the DEV post's "My Agent Session", as a real link.
- In judging, partner tabs inside the app aren't scored, and claims that can't be verified only cost credibility.

Recommendation: remove only the Entire tab from the app and keep Sentry and Atlas. Renaming the tabs to feature names like "Compile trace" and "Saved rooms" is also worth considering, but that's optional.

If this tab removal goes into the cleanup I proposed earlier (removing unverified claims from the README, /api/health and the deploy files), it's all done in one pass. Tell me whether to remove the Entire tab, and whether to rename the others too.

3. "No, why is the floor plan in the corner of the photo?"

No, why is the floor plan in the corner of the photo?

The agent read "floor plan" as the object boxes drawn over the photo, which sat in its top-left corner, and my
next prompt went along with that. Before touching any code it asked Gemma for one object's box in three
coordinate conventions and drew the answers on the photo; none was accurate. It then ran the full request
again with pixel coordinates: the boxes spread out but landed on the wrong objects (the bed's box over the
window, a chair's box over a shelf). So the display was faithful and the model could not point at objects.
I chose the option that stops asking:

Go with A. Don't ask the model for bboxes; leave bbox2d null in the compile result so the overlay and the crop drop out of the photo. Also correct objects that went outside the room back inside (mark corrected objects as inferred, shown as such). Add tests too.

Translation: "Go with A. Don't ask the model for bboxes; leave bbox2d null in the compile result so the overlay and
the crop drop out of the photo. Also correct objects that went outside the room back inside (mark corrected
objects as inferred, shown as such). Add tests too."

Commit: b01a9f3 "Stop asking Gemma for photo boxes, keep objects inside the room, and survive long answers"

Claude Code Session claude-sonnet-5-5
You

아니 도면도가 왜 사진 구석탱이에 있어?


English translation:

No, why is the floor plan in the corner of the photo?

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

표시 버그가 아니라 모델이 그 좌표를 적은 것입니다. 그리고 좌표 표기를 바꿔도 해결되지 않았습니다. 원인을 확인하려고 세 가지를 직접 실험했습니다. 저장소 파일은 건드리지 않았습니다.

  1. 앱이 그리는 위치는 저장된 값 그대로입니다. 침대 박스는 저장값이 왼쪽 4%, 위 7%, 크기 28%×20%였고, 화면에서도 그 위치에 그려졌습니다.
  2. 침대만 따로 물으면 박스가 사진 전체에 퍼집니다. 같은 모델에 침대 하나만 물었을 때는 박스가 구석에 몰리지 않고 오른쪽 아래 방향을 가리켰습니다(정확하진 않습니다). 큰 스키마로 한꺼번에 뽑을 때만 구석에 몰렸습니다. 4B가 0~1000 "천분율" 표기를 제대로 못 다뤄서 작은 숫자를 쓴 것으로 보이며, 이 부분은 추정입니다.
  3. 좌표를 픽셀로 바꾸면 구석 몰림은 사라지지만 여전히 틀립니다. 같은 스키마에서 좌표를 픽셀로 바꿔 돌렸더니 박스가 사진 전체로 퍼졌습니다. 그런데 침대 박스가 창문과 방 가운데를 덮고, 의자 박스는 오른쪽 선반 위에, 스툴 박스는 침대 위에 있었습니다. 오른쪽 아래 침대 자리에는 박스가 없었습니다.

원인은 표기법이 아니라 Gemma3 4B가 물체의 위치를 짚는 일(grounding) 자체를 못 한다는 점입니다. 그래서 박스, 그리고 같은 능력에 기대는 평면도 위치(침대가 방 밖으로 나간 것)는 증거로 쓸 수 없습니다. 4B가 믿을 만한 쪽은 "무엇이 있나"라는 목록입니다.

선택지

안 내용 장점 한계
A 모델에게 박스를 요청하지 않음 컴파일 결과의 박스는 비워 두고, 사진 위 오버레이는 샘플에서만 보입니다. 틀린 근거를 보여주지 않고 출력 토큰과 시간도 줄어듭니다. 앞서 제안한 배치 보정(방 밖으로 나간 물체를 방 안으로)도 같이 적용합니다. 컴파일한 사진에는 근거 박스가 없습니다.
B gemma3:12b로 비교 위치 지정이 나아지는지 확인할 수 있습니다(약 8GB 다운로드, 허락 필요). 나아진다는 보장이 없고 더 느립니다.
C 박스만 다른 모델로 Qwen2.5-VL 같은 모델은 위치 지정을 잘합니다. Gemma 챌린지 취지에서 벗어나서 권하지 않습니다.

권장: A를 먼저 하고 B로 확인하는 순서입니다. 앞의 수정분(사진 비율, 바닥 물체 필터 등)은 아직 커밋하지 않았습니다.


English translation:

It's not a display bug: the model wrote those coordinates. And changing the coordinate notation didn't fix it. I ran three experiments to find the cause. I didn't touch any files in the repository.

  1. The app draws exactly the stored values. The bed box was stored as 4% from the left, 7% from the top, 28%×20% in size, and that's where it was drawn on screen.
  2. Asked about the bed alone, the boxes spread across the photo. When I asked the same model about just the bed, the box didn't bunch up in the corner; it pointed towards the bottom right (not accurately). It only bunched up in the corner when everything was extracted at once with the big schema. It looks like the 4B can't handle the 0–1000 "per mille" notation and wrote small numbers; that part is my guess.
  3. With pixel coordinates the corner bunching goes away, but they're still wrong. Running the same schema with pixel coordinates, the boxes spread across the whole photo. But the bed box covered the window and the middle of the room, the chair box sat on the shelf on the right, and the stool box was on the bed. There was no box over the bed in the bottom right.

The cause isn't the notation: Gemma3 4B can't point at where objects are (grounding) at all. So the boxes, and the floor-plan positions that rely on the same ability (the bed outside the room), can't be used as evidence. What the 4B is reliable for is the list of "what's there".

Options

Option What Pros Limits
A Don't ask the model for boxes Compiled results get empty boxes, and the overlay on the photo only shows for samples. It stops showing wrong evidence and cuts output tokens and time. The placement correction I proposed earlier (moving objects that went outside the room back inside) goes in too. Compiled photos have no evidence boxes.
B Compare with gemma3:12b Shows whether grounding gets better (about an 8 GB download, needs your permission). No guarantee it improves, and slower.
C A different model just for boxes Models like Qwen2.5-VL are good at grounding. Strays from the point of a Gemma challenge, so I don't recommend it.

Recommendation: do A first, then check with B. The earlier changes (photo aspect ratio, floor-object filter and so on) aren't committed yet.

Agent
You

A로 진행해줘. 모델에게 bbox를 요청하지 말고 컴파일 결과의 bbox2d는 null로 두어서 오버레이·크롭이 사진에서 빠지게 해줘. 방 밖으로 나간 물체를 방 안으로 보정하는 것도 같이 해줘(보정된 물체는 inferred로 내리고 표시). 테스트도 추가해줘.


English translation:

Go with A. Don't ask the model for bboxes; leave bbox2d null in the compile result so the overlay and the crop drop out of the photo. Also correct objects that went outside the room back inside (mark corrected objects as inferred, shown as such). Add tests too.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

임시 서버(Atlas와 Sentry가 꺼진 별도 스택)가 떴습니다. 이제 화면에서 같은 사진을 실제로 업로드해 end-to-end로 확인합니다. 약 1분 걸립니다.


English translation:

The temporary server (a separate stack with Atlas and Sentry off) is up. Now I'll actually upload the same photo in the UI to check it end to end. It takes about a minute.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

A로 진행해서 구현을 끝냈습니다. 모델에게 박스를 요청하지 않게 했고, 방 밖으로 나간 물체는 방 안으로 옮기게 했습니다.

1. 박스 제거
- Gemma에게 박스를 요청하는 스키마와 프롬프트를 지웠고, 컴파일 결과의 bbox2d는 항상 null입니다.
- 사진 패널에서 오버레이 토글이 사라지고 "이 사진에는 객체 박스가 없습니다" 안내가 나옵니다.
- 인스펙터의 크롭 자리에는 "사진 속 위치 없음" 문구가 나옵니다.
- 샘플 방은 손으로 만든 박스라 그대로 그려집니다.
- 이전에 Atlas에 저장된 옛 결과(박스 있음)는 그대로 읽히고, 기존 경고문이 붙습니다.

2. 방 밖 보정 (fitFootprintIntoRoom)
- 위치와 크기: 방 밖으로 나간 물체를 방 안으로 옮기고, 방보다 큰 물체는 줄입니다. 90° 회전된 물체는 돌린 변으로 계산합니다.
- 표시: 보정된 물체는 observed이면 inferred로 내려가고, 신뢰도 상한이 0.85가 됩니다.
- 불확실성과 사유: 위치 불확실성은 이동 거리 이상이 되고, 사유에 "Placed outside the room by the model; moved N cm…"이 적힙니다. occluded_prior는 그대로 유지됩니다.
- Sentry: 보정된 개수는 gemma.objects_moved_into_room으로 기록됩니다.
- 안 건드리는 것: 이미 방 안에 들어온 물체는 모델이 준 값 그대로입니다.

테스트: 30개로 늘었습니다. 박스 관련 테스트는 바꾸고 새로 추가했습니다(모델이 박스를 보내도 무시, 방 밖 이동과 강등, 방보다 큰 물체 축소, 회전 물체, 요청에 박스가 없음).

검증
- typecheck, 테스트 30개, 빌드를 통과했습니다.
- Atlas와 Sentry가 꺼진 임시 서버로 같은 사진을 화면에서 업로드해 확인했습니다. 47초 만에 컴파일됐고, 박스 없이 사진이 잘리지 않고 나왔으며 3D 물체가 벽 안에 들어왔습니다.
- 이 실행에서 5개 중 의자 1개가 방 밖에 있어 75cm 옮겨졌고 inferred가 됐습니다.
- 임시 서버와 설정 파일은 지웠고, Atlas와 Sentry에는 아무것도 쓰지 않았습니다.

알아둘 점
- 이 보정은 방 밖으로 나가는 불가능한 배치만 막고, 틀린 위치를 맞추지는 못합니다. 방 안에 있는 observed 물체도 여전히 모델의 추정입니다.
- 이번 실행에서는 침대가 180×210cm로 정상이었습니다. 모델은 실행마다 결과가 달라서, 같은 사진도 어떤 때는 침대를 방 밖에 놓습니다.
- 보정된 의자에는 ±75cm 점선 껍질이 그려져 벽 밖으로 삐져나와 보입니다. "±cm Uncertainty Shell" 토글로 끌 수 있습니다.
- DEV 초안의 "What broke" 항목과 UI의 Entire 체크포인트 #2 설명에 있던 "bbox 추출" 문구를 사실에 맞게 고쳤습니다.
- Atlas에 저장된 기존 문서(scene-b03d8dc3)는 옛 결과 그대로 두었습니다.

수정분은 아직 커밋하지 않았습니다.


English translation:

I went with A and finished the implementation. The model is no longer asked for boxes, and objects that went outside the room are moved back inside.

1. Boxes removed
- I deleted the schema and prompt parts that asked Gemma for boxes, and bbox2d in compile results is always null.
- The overlay toggle is gone from the photo panel, and it shows the note "This photo has no object boxes".
- Where the inspector showed a crop, it now says "No location in the photo".
- Sample rooms have hand-made boxes, so those are still drawn.
- Old results saved to Atlas earlier (with boxes) still load as they are, with the existing warning.

2. Out-of-room correction (fitFootprintIntoRoom)
- Position and size: objects outside the room are moved inside, and objects bigger than the room are shrunk. Objects rotated by 90° are computed with their sides swapped.
- Labelling: a corrected object that was observed goes down to inferred, with its confidence capped at 0.85.
- Uncertainty and reason: the position uncertainty becomes at least the distance moved, and the reason says "Placed outside the room by the model; moved N cm…". occluded_prior stays as it is.
- Sentry: the number of corrected objects is recorded as gemma.objects_moved_into_room.
- What it doesn't touch: objects already inside the room keep the values the model gave.

Tests: up to 30. I changed the box-related tests and added new ones (boxes ignored even if the model sends them, moving out-of-room objects and downgrading them, shrinking objects bigger than the room, rotated objects, no box in the request).

Verification
- Typecheck, the 30 tests and the build pass.
- I uploaded the same photo in the UI on a temporary server with Atlas and Sentry off. It compiled in 47 seconds, the photo came out uncropped with no boxes, and the 3D objects were inside the walls.
- In this run, 1 of 5 objects (a chair) was outside the room; it was moved 75 cm and became inferred.
- I deleted the temporary server and config file, and nothing was written to Atlas or Sentry.

Worth knowing
- This correction only prevents impossible placements outside the room; it doesn't make a wrong position right. observed objects inside the room are still the model's estimates.
- In this run the bed was a normal 180×210 cm. The model varies from run to run, so for the same photo it sometimes puts the bed outside the room.
- The corrected chair gets a ±75 cm dashed shell that sticks out past the wall. You can turn it off with the "±cm Uncertainty Shell" toggle.
- I corrected the "bbox extraction" wording in the DEV draft's "What broke" item and in the UI's description of Entire checkpoint #2.
- The existing document saved in Atlas (scene-b03d8dc3) is left as the old result.

The changes aren't committed yet.

Agent

4. "Setting aside the photo being cropped, don't you feel the positions look odd in the picture with the floor plan?"

Setting aside the photo being cropped, don't you feel the positions look odd in the picture with the floor plan?

The agent had just fixed the cropped photo panel and was explaining the box coordinates. I asked about the
layout itself, and it measured instead of agreeing: in my run 4 of 11 objects were outside the room the model had just described, and in two runs the
model put a bed starting 300 cm from the west wall of a 360 cm room, 220 to 240 cm outside it. That became
the repair step: move the object back inside, mark it inferred, widen its band and write the move into its
reason. The repair only stops impossible placements. It does not make a wrong position right.

Commit: b01a9f3 (same commit as moment 3)

Claude Code Session claude-sonnet-5-5
You

사진이 잘리는거랑 상관없이 도면이 있는 사진에 위치가 이상하다고 느끼지 않니?


English translation:

Setting aside the photo being cropped, don't you feel the positions look odd in the picture with the floor plan?

Agent
Agent

네, 저도 그렇게 봅니다. 앞서 크기와 박스 얘기만 하고 배치 문제는 짚지 않았는데, 배치가 더 심각합니다.

실측 (같은 사진, 바닥 평면도 기준)

올려주신 실행 수정 후 재실행
방 크기 360×310cm 360×330cm
침대 위치 x 300~600, y 230~430 x 300~580, y 240~450
방 밖으로 나간 물체 4/11개 2/5개
침대가 방 밖으로 나간 정도 동쪽 벽 밖 240cm, 남쪽 벽 밖 120cm 동쪽 벽 밖 220cm, 남쪽 벽 밖 120cm
물체가 덮은 바닥 비율 39% 10%
  • 침대: 두 번 모두 가로 360cm 방의 동쪽 끝(x=300)에서 시작하는 280~300cm 침대를 놓았습니다. 침대 대부분이 벽 밖에 있어서, 3D에서 침대가 방 바깥으로 삐져나와 보인 겁니다.
  • 방 안의 빈 공간: 재실행에서는 물체가 바닥의 10%만 덮습니다. 사진에서는 침대 하나가 방의 큰 부분을 차지합니다.
  • 창문 위치: 창문이 서쪽 벽에 있고 침대가 동남쪽에 있다는 대략적인 방향 관계는 사진과 비슷합니다. 하지만 cm 단위 좌표는 서로 맞지 않습니다.

원인
- 단일 사진에서 평면도 절대 좌표(cm)를 뽑는 건 4B 비전 모델에게 너무 어려운 일입니다. 스키마 검증만 통과하면 그대로 받아 씁니다.
- 방 밖으로 나간 개수는 Sentry span에 세어 기록할 뿐 위치를 고치지 않습니다. 그런데 화면에는 이 침대가 "관측됨 95%"로 표시됩니다. 이건 코드 쪽 문제이고, 검증 없이 확신하는 것처럼 보이게 만듭니다.

선택지

안 내용 장점 한계
A 코드로 보정 방보다 큰 물체는 줄이고, 벽 밖으로 나간 물체는 방 안으로 옮깁니다. 보정된 물체는 inferred로 낮추고 "위치를 방 안으로 조정함"을 표시합니다. 작고 결정적이라 테스트로 확인됩니다. 방 밖으로 안 나가게 할 뿐 실제 위치가 맞아지지는 않습니다.
B 질문 방식 변경 cm 좌표 대신 "먼 벽/왼쪽/오른쪽/가까운 쪽" 같은 거친 위치를 묻고, 코드가 좌표로 바꿉니다. 방 안에 들어오는 것이 구조상 보장됩니다. 프롬프트와 파서가 크게 바뀌고, 카메라 방향 가정이 필요합니다.
C 더 큰 모델 gemma3:12b(약 8GB)로 같은 사진을 비교해 볼 수 있습니다. 배치가 나아지는지 아직 모르고 2~3배 느릴 수 있습니다. 모델 다운로드 허락이 필요합니다.

권장: A를 먼저 적용합니다. 그다음 C로 12b를 비교하고, 그래도 부족하면 B를 검토하는 순서입니다. 지금 수정분은 아직 커밋하지 않았습니다.


English translation:

Yes, I see it that way too. Earlier I only talked about sizes and boxes and didn't point out the placement problem, but the placement is the more serious one.

Measured (same photo, on the floor plan)

The run you uploaded Rerun after the fixes
Room size 360×310 cm 360×330 cm
Bed position x 300–600, y 230–430 x 300–580, y 240–450
Objects outside the room 4 of 11 2 of 5
How far the bed is outside 240 cm past the east wall, 120 cm past the south wall 220 cm past the east wall, 120 cm past the south wall
Share of the floor covered by objects 39% 10%
  • Bed: both times it placed a 280–300 cm bed starting at the east end (x=300) of a 360 cm-wide room. Most of the bed is outside the wall, which is why the bed looked like it was sticking out of the room in 3D.
  • Empty space in the room: in the rerun, objects cover only 10% of the floor. In the photo, the bed alone takes up a large part of the room.
  • Window position: the rough relationship, window on the west wall and bed to the south-east, is similar to the photo. But the cm coordinates don't match each other.

Cause
- Extracting absolute floor-plan coordinates (cm) from a single photo is too hard for a 4B vision model. As long as the answer passes schema validation, it's used as is.
- The number of out-of-room objects is only counted on the Sentry span; the positions aren't fixed. Yet the screen shows this bed as "observed 95%". That's a problem on the code side, and it makes the result look confident without any check.

Options

Option What Pros Limits
A Correct it in code Shrink objects bigger than the room and move objects outside the walls back inside. Corrected objects are lowered to inferred and labelled "position adjusted into the room". Small and deterministic, so it can be tested. It only keeps objects inside the room; it doesn't make the real position right.
B Change how we ask Ask for coarse positions like "far wall / left / right / near side" instead of cm coordinates, and let code turn them into coordinates. Staying inside the room is guaranteed by construction. Big changes to the prompt and parser, and it needs an assumption about camera direction.
C A bigger model Compare the same photo with gemma3:12b (about 8 GB). Unknown whether placement improves, and it may be 2–3 times slower. Needs your permission to download the model.

Recommendation: apply A first. Then compare 12b with C, and if that's still not enough, consider B. The current changes aren't committed yet.

Agent

5. "Fix the problem where gemma3:4b's first attempt is rejected because of the ceiling height (over 600). Don't reject the whole answer for an out-of-range ceilingHeightCm; correct it into range and use it, and record how many times that happens. Add tests, and re-measure first-attempt success and time on the same photos."

Fix the problem where gemma3:4b's first attempt is rejected because of the ceiling height (over 600). Don't reject the whole answer for an out-of-range ceilingHeightCm; correct it into range and use it, and record how many times that happens. Add tests, and re-measure first-attempt success and time on the same photos p0 and p1.

The agent first printed what the model really wrote: 2700 and 2400, which are millimetres. Clamping to 600
would have turned a 270 cm ceiling into a 600 cm one, so a value from 1800 to 6000 is read as millimetres and
anything else out of range is clamped. Each correction is counted on the trace. The two test photos went from
107.8 s and 90.6 s (a second attempt each) to 46.2 s and 40.0 s on the first attempt. Putting the old range
check back made the new tests fail with the same error.

Commit: c97f4f0 "Correct an out-of-range ceiling height instead of rejecting the whole answer"

Claude Code Session
You

gemma3:4b 첫 시도가 천장 높이(600 초과) 때문에 거부되는 문제를 고쳐줘. 범위를 벗어난 ceilingHeightCm은 답 전체를 거부하지 말고 범위 안으로 보정해서 쓰고 그 횟수를 기록해줘. 테스트도 추가하고, 같은 사진 p0, p1로 첫 시도 성공과 시간을 다시 재줘.


English translation:

Fix the problem where gemma3:4b's first attempt is rejected because of the ceiling height (over 600). Don't reject the whole answer for an out-of-range ceilingHeightCm; correct it into range and use it, and record how many times that happens. Add tests, and re-measure first-attempt success and time on the same photos p0 and p1.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

코드 수정은 끝났고, 지금은 이 보정을 검증하는 테스트를 추가하는 중입니다. 이어서 같은 사진 p0, p1로 첫 시도 성공과 시간을 다시 잽니다.


English translation:

The code change is done, and I'm now adding tests that verify this correction. Next I'll re-measure first-attempt success and time on the same photos p0 and p1.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

천장 높이 때문에 답 전체가 거부되던 문제를 고쳤습니다. 같은 사진 p0, p1로 다시 재 보니 이제 두 장 모두 첫 시도에 성공하고 시간은 절반 이하로 줄었습니다.

원인
- gemma3:4b가 천장 높이를 밀리미터로 썼습니다. p0는 2700, p1은 2400이었고, 이는 270cm와 240cm입니다.
- 코드는 600cm 초과를 검증에서 거부했습니다. 천장 높이는 3D·2D 벽 높이에만 쓰는 값인데, 거부되면 통째로 한 번 더 돌았습니다.

수정 (server/gemmaScene.ts)
- 범위 보정: 180~600cm 밖의 값을 거부하지 않고 보정해서 씁니다.
- 1800~6000이면 밀리미터로 보고 10으로 나눕니다(2700은 270cm).
- 그 밖의 범위 밖 값은 180~600으로 자릅니다. 단순히 600으로 자르면 2700이 600cm가 되어 틀리기 때문에 단위 해석을 먼저 합니다.
- 기록: 보정할 때마다 Sentry 컴파일 스팬에 gemma.ceiling_height_adjusted(0 또는 1)와 gemma.ceiling_height_note(예: "2700 read as mm = 270 cm")가 남습니다. 기존 fields_normalized 카운트에도 포함됩니다.

재측정 (실제 모델 gemma3:4b, 같은 사진·같은 요청)

사진 수정 전 수정 후
p0 시도 2회, 107.8초, 출력 2872토큰 시도 1회, 46.2초, 1439토큰
p1 시도 2회, 90.6초, 출력 2473토큰 시도 1회, 40.0초, 1238토큰
  • p0를 한 번 더 돌려서 기록값을 직접 읽어 확인했습니다. 첫 시도 성공, 천장 높이 270cm, 보정 횟수 1, 메모 "2700 read as mm = 270 cm"입니다. p1은 240cm, 보정 1, "2400 read as mm = 240 cm"로 기록됐습니다.
  • 수정 전 p0는 방 밖 물체가 3개, 수정 후는 0개입니다. 하지만 이 차이는 이번 수정의 효과가 아닙니다. 수정 전에는 재시도에서 나온 답이고 수정 후는 첫 시도의 답이라, 서로 다른 답을 비교한 수치입니다.

테스트: 33개로 늘었습니다. 값 보정과 경계(1800, 6000, 1799, 6001, 180, 600, 음수), 그리고 천장 높이가 mm로 와도 재시도 없이 한 번에 통과하는 컴파일 테스트를 추가했습니다. 옛 범위 검사를 임시로 되돌려 보니 새 테스트 2개가 정확히 그 오류로 실패했고, 복구하니 통과했습니다. 타입체크와 빌드도 통과합니다.

남은 점
- 방 가로와 세로(widthCm, depthCm)에도 같은 종류의 엄격한 범위 검사(100~3000)가 있습니다. 이번 사진들에서는 걸리지 않아서 손대지 않았습니다. 이 값은 방 크기라 보정하면 틀린 값이 되기 쉬워서 별도로 판단하는 게 좋습니다.
- 수정분은 아직 커밋하지 않았습니다.


English translation:

Fixed the problem where the whole answer was rejected because of the ceiling height. Re-measuring on the same photos p0 and p1, both now succeed on the first attempt and the time is cut by more than half.

Cause
- gemma3:4b wrote the ceiling height in millimetres: 2700 for p0 and 2400 for p1, which are 270 cm and 240 cm.
- The code rejected anything over 600 cm in validation. The ceiling height is only used for the wall height in the 3D and 2D views, yet a rejection made the whole thing run again.

Fix (server/gemmaScene.ts)
- Range correction: values outside 180–600 cm are corrected and used instead of rejected.
- 1800 to 6000 is read as millimetres and divided by 10 (2700 becomes 270 cm).
- Any other out-of-range value is clamped to 180–600. Simply clamping to 600 would turn 2700 into 600 cm, which is wrong, so the unit is interpreted first.
- Record: every correction leaves gemma.ceiling_height_adjusted (0 or 1) and gemma.ceiling_height_note (e.g. "2700 read as mm = 270 cm") on the Sentry compile span. It's also included in the existing fields_normalized count.

Re-measured (real model gemma3:4b, same photos, same request)

Photo Before After
p0 2 attempts, 107.8 s, 2872 output tokens 1 attempt, 46.2 s, 1439 tokens
p1 2 attempts, 90.6 s, 2473 output tokens 1 attempt, 40.0 s, 1238 tokens
  • I ran p0 once more and read the recorded values directly: first-attempt success, ceiling height 270 cm, correction count 1, note "2700 read as mm = 270 cm". p1 was recorded as 240 cm, correction 1, "2400 read as mm = 240 cm".
  • Before the fix p0 had 3 objects outside the room, after it 0. But that difference isn't an effect of this fix: before, the answer came from the retry, and after, from the first attempt, so these numbers compare different answers.

Tests: up to 33. I added tests for the value correction and its boundaries (1800, 6000, 1799, 6001, 180, 600, negative), and a compile test that passes in one go, with no retry, when the ceiling height arrives in mm. Temporarily putting the old range check back made 2 of the new tests fail with exactly that error, and restoring it made them pass. Typecheck and the build pass too.

Remaining
- Room width and depth (widthCm, depthCm) have the same kind of strict range check (100–3000). These photos didn't hit it, so I left it alone. Those are room sizes, and correcting them easily produces wrong values, so it's better decided separately.
- The changes aren't committed yet.

Agent

6. "First push with --no-verify and check that origin has 0 entire refs. Then I allow the gemma4:e4b download (about 6.6 GB). Compare it with gemma3:4b on 3 to 5 photos and report the number of objects outside the room, the time, and the object recognition."

First push with --no-verify and check that origin has 0 entire refs. Then I allow the gemma4:e4b download (about 6.6 GB). Compare it with gemma3:4b on 3 to 5 photos and report the number of objects outside the room, the time, and the object recognition.

I had one real photo, so the agent found four CC0 room images on Wikimedia Commons and asked before
downloading them. One earlier candidate turned out to be a portrait and not a room, and it was deleted unused.
On five images Gemma 4 E4B put fewer objects outside the room (2 of 20 against 4 of 28), but it was about
2.4 times slower and failed on the busiest room, so gemma3:4b stays the default. The post says what the test
cannot show: five images, one run each, my own judgement of what was right.

Commit: bd3dbb5 "Add the model comparison, photo credits and agent-session slots to the DEV draft"

Claude Code Session claude-sonnet-5-5
You

먼저 --no-verify로 푸시하고 origin의 entire 참조 0개를 확인해줘. 그다음 gemma4:e4b(약 6.6GB) 다운로드를 허락할게. 사진 3~5장으로 gemma3:4b와 비교해서 방 밖 물체 수, 소요 시간, 객체 인식을 보고해줘.


English translation:

First push with --no-verify and check that origin has 0 entire refs. Then I allow the gemma4:e4b download (about 6.6 GB). Compare it with gemma3:4b on 3 to 5 photos and report the number of objects outside the room, the time, and the object recognition.

Agent
Agent
Agent
Agent

푸시를 마쳤고 origin의 entire 참조는 0개입니다. 이어서 허락하신 gemma4:e4b를 받습니다.


English translation:

The push is done and origin has 0 entire refs. Next I'm pulling gemma4:e4b, which you allowed.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

Raw 비교는 끝났습니다. 첫 답이 실패한 3건(gemma3 2건, gemma4 1건)이 앱에서는 재시도로 복구되는지 실제 파이프라인으로 확인합니다. 약 5~8분 걸립니다.


English translation:

The raw comparison is done. Now I'll check with the real pipeline whether the 3 cases where the first answer failed (2 for gemma3, 1 for gemma4) recover with the app's retry. It takes about 5 to 8 minutes.

Agent
Agent
Agent
Agent
Agent

결론: 이 5장 기준으로 gemma4:e4b가 낫다고 하기 어렵습니다. 기본 모델은 gemma3:4b를 유지하는 걸 권합니다. Gemma 4가 방 밖 배치는 적었지만, 2배 넘게 느렸고 복잡한 방에서는 실패했습니다.

푸시: --no-verify로 푸시했고(416d025, 원격에 올라가지 않은 커밋 0개), origin의 entire 참조는 git ls-remote와 GitHub API 둘 다 0개입니다.

비교 조건
- 사진 5장: ① 사용자 침실(images.jpeg), ② 기숙사 1인실, ③ 거실, ④ 식탁이 있는 거실, ⑤ 인테리어 3D 렌더. ②~⑤는 Wikimedia Commons CC0이고 ⑤는 사진이 아니라 렌더입니다.
- 앱과 같은 요청(같은 스키마·프롬프트·파서)으로 돌렸습니다. 기준 물체는 기본값인 문틀이고, 온도 0입니다.
- 실패한 경우는 실제 파이프라인(재시도 포함)으로 다시 돌렸습니다. Atlas와 Sentry는 껐습니다.

결과

gemma3:4b gemma4:e4b
첫 시도 성공 3/5 4/5
재시도 후 성공 5/5 4/5 (1건은 샘플 방 폴백)
소요 시간 한 번에 성공한 3장은 47.7~50.4초, 재시도한 2장은 107.8초와 90.6초 106.6~119.5초, 실패한 1장은 284.6초
출력 토큰(시도당) 약 1.2~1.4k 약 2.4~2.9k, 한 장은 상한 3500에 걸림
방 밖 물체(모델이 처음 낸 값) 6/28개(21%) 2/20개(10%)

같은 4장(①②③⑤)만 비교하면 방 밖 물체는 gemma3가 5/22개, gemma4가 2/20개입니다.

객체 인식 (제가 눈으로 확인한 판단입니다)
- ① 침실: gemma3는 침대, 의자, 선반, 식물, 스툴을 맞혔고(실제 6개 중 5개) 협탁 하나는 의심스럽습니다. gemma4는 침대와 의자만 맞혔고, 라벨이 한국어로 나왔습니다.
- ② 기숙사: 둘 다 침대, 책상, 의자, 옷장 4개를 모두 맞혔습니다.
- ③ 거실: gemma4가 낫습니다. 소파, 안락의자, TV장, 식물 3개를 잡았고 바닥 조명만 놓쳤습니다. gemma3는 식물과 조명을 놓치고 TV와 양초를 목록에 넣었습니다. gemma4의 라벨은 tv_console 같은 snake_case로 나왔습니다.
- ④ 식탁 거실: gemma3는 식탁, 의자, 책장을 맞혔지만 고양이를 "dog"로 불렀습니다. gemma4는 실패했습니다.
- ⑤ 렌더: 둘 다 소파, 의자, 테이블, 러그, 조명 5개를 맞혔습니다.

한계
- 사진 5장에 한 번씩 돌린 결과입니다. 한 장은 렌더이고, 인식 판단은 제 눈 기준입니다. 경향을 보는 용도이지 벤치마크가 아닙니다.
- gemma3의 첫 시도 실패 2건은 둘 다 천장 높이가 600을 넘어서 답 전체가 거부된 경우입니다. 이 값은 거의 안 쓰이는데도 한 번 더 돌게 되어 시간이 두 배로 늘었습니다.

권장
- 데모는 gemma3:4b로 하고, 비교표는 DEV 글의 "How I Built It"에 넣습니다. gemma4는 방 밖 배치가 적었지만 2배 느리고 복잡한 방에서 출력이 잘렸다고 정직하게 쓰면 됩니다.
- 천장 높이 검증을 완화하면 gemma3가 한 번에 통과하는 경우가 늘어서 시간이 줄어듭니다. 지금 5장 중 2장이 이 이유로 두 번 돌았습니다.
- 사진 크레딧은 글에 쓸 때 Wikimedia Commons의 파일 제목으로 표기하는 편이 좋습니다(CC0라 의무는 아닙니다).
- gemma4:e4b(6.6GB)는 지금 로컬에 남아 있고, ollama rm gemma4:e4b로 지울 수 있습니다.


English translation:

Conclusion: on these 5 photos it's hard to say gemma4:e4b is better. I recommend keeping gemma3:4b as the default model. Gemma 4 placed fewer objects outside the room, but it was more than twice as slow and failed on a complex room.

Push: pushed with --no-verify (416d025, 0 commits not on the remote), and origin has 0 entire refs according to both git ls-remote and the GitHub API.

Test conditions
- 5 photos: ① the user's bedroom (images.jpeg), ② a single dorm room, ③ a living room, ④ a living room with a dining table, ⑤ a 3D interior render. ②–⑤ are CC0 from Wikimedia Commons, and ⑤ is a render, not a photo.
- I ran the same request as the app (same schema, prompt and parser). The reference object is the default door frame, and temperature is 0.
- Failed cases were rerun through the real pipeline (with the retry). Atlas and Sentry were off.

Results

gemma3:4b gemma4:e4b
First-attempt success 3/5 4/5
Success after retry 5/5 4/5 (1 fell back to the sample room)
Time 47.7–50.4 s for the 3 that succeeded at once, 107.8 s and 90.6 s for the 2 retried 106.6–119.5 s, 284.6 s for the 1 that failed
Output tokens (per attempt) about 1.2–1.4k about 2.4–2.9k, one photo hit the 3500 limit
Objects outside the room (model's original values) 6 of 28 (21%) 2 of 20 (10%)

Comparing only the same 4 photos (①②③⑤), objects outside the room are 5 of 22 for gemma3 and 2 of 20 for gemma4.

Object recognition (my own judgement, by eye)
- ① Bedroom: gemma3 got the bed, chair, shelf, plant and stool (5 of the 6 real objects); one nightstand is doubtful. gemma4 only got the bed and chair, and its labels came out in Korean.
- ② Dorm room: both got all 4: bed, desk, chair and wardrobe.
- ③ Living room: gemma4 is better. It caught the sofa, armchair, TV console and 3 plants, missing only the floor lamp. gemma3 missed the plants and lamp and listed a TV and a candle. gemma4's labels came out in snake_case, like tv_console.
- ④ Living room with dining table: gemma3 got the dining table, chairs and bookcase, but called the cat a "dog". gemma4 failed.
- ⑤ Render: both got all 5: sofa, chair, table, rug and lamp.

Limits
- 5 photos, one run each. One is a render, and the recognition judgements are by my eye. This is for seeing a trend, not a benchmark.
- Both of gemma3's first-attempt failures were cases where the ceiling height was over 600 and the whole answer was rejected. That value is barely used, yet it caused a second run and doubled the time.

Recommendation
- Do the demo with gemma3:4b and put the comparison table in "How I Built It" in the DEV post. It's enough to say honestly that gemma4 placed fewer objects outside the room but was twice as slow and had its output cut off on a complex room.
- Relaxing the ceiling height validation would let gemma3 pass on the first try more often and cut the time. Right now 2 of the 5 photos ran twice for that reason.
- When crediting the photos in the post, it's better to use their Wikimedia Commons file titles (not required, since they're CC0).
- gemma4:e4b (6.6 GB) is still on your machine; you can remove it with ollama rm gemma4:e4b.

Agent

7. "No, I'm saying the image has to be visible, not a path! How did you write the post?" and "Shouldn't our project's images be visible?"

No, I'm saying the image has to be visible, not a path! How did you write the post?

Two mistakes of the agent's. The post linked its comparison figure through a raw.githubusercontent.com address
that returned 404 until the file was pushed, and the post had no picture of the app itself, only a figure of
other people's photos. The agent pushed (hooks skipped, so no Entire checkpoint reached the public
repository), opened every image address to check it, and added five screenshots of a real compile plus the
Sentry captures. It had offered a relative path first, which would have looked right in a preview and
broken on DEV.

Commit: d1ed312 "Show the app itself in the DEV draft: screenshots of a real compile and of Sentry"

Claude Code Session
You

아니 경로를 넣는게 아니라 이미지가 보여야된다니까? 너 글을 어떻게 작성한거야


English translation:

No, I'm saying the image has to be visible, not a path! How did you write the post?

Agent
Agent
Agent

푸시를 마쳤고 이미지 URL 두 개가 모두 200으로 열립니다. 글에서 실제로 보이는지 GitHub 렌더 화면으로 확인합니다.


English translation:

The push is done and both image URLs open with 200. I'll check in GitHub's rendered view that they actually show in the post.

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent

이제 비교 그림이 글에서 보입니다. GitHub에서 렌더된 초안으로 확인했고 그림이 1200×1670으로 정상 로드됐습니다.

글을 어떻게 썼는지, 그리고 제 실수
- 이미지를 raw.githubusercontent.com 외부 주소로 걸어 두었는데, 그 파일이 GitHub에 올라가 있지 않아서 404였습니다. 글 쓰는 단계에서 푸시가 필요하다는 점을 제가 먼저 챙기지 못했습니다. 체크리스트 주석에만 적어 두고 결과를 직접 확인하지 않았습니다.
- 상대 경로로 바꾸자는 제안은 DEV에서는 어차피 안 맞는 방법이라 하지 않는 게 맞았습니다.
- 이전 메시지에서 푸시 여부를 묻는 바람에 돌아가는 길이 됐습니다. 이번에는 이미지가 보여야 한다는 요청으로 이해하고 바로 푸시했습니다.

이번에 한 것
- 로컬 커밋 4개를 --no-verify로 푸시했습니다(416d025에서 1113c85로). 원격의 entire 참조는 0개 그대로입니다.
- model-comparison.jpg와 Sentry 캡처 URL이 모두 200으로 열립니다.
- 글의 이미지 주소는 feat/real-gemma-sentry-tracing 브랜치를 가리킵니다. 브랜치를 지우거나 이름을 바꾸면 깨지니, 게시 전에 커밋 SHA로 고정하거나 DEV 편집기에 직접 올리는 편이 안전합니다.

알아둘 점
- 앱의 파일 패널은 마크다운을 소스로 보여 줄 수 있어서 이미지가 안 보일 수 있습니다. 렌더된 모습은 GitHub의 DEV_SUBMISSION.md에서 보시면 됩니다.
- DEV 편집기에서 이 외부 주소가 어떻게 보이는지는 제가 테스트하지 못했습니다. 붙여 넣은 뒤 DEV 미리보기에서 한 번 확인하세요.
- Sentry 캡처 5장은 글에서 아직 참조하지 않아서 글에는 나오지 않습니다. 지금 글에 있는 이미지는 비교 그림 1장뿐입니다.

Sentry 캡처와 트레이스 수치를 Sentry 항목에 넣고 글에 이미지를 더 넣을지 정해 주세요.


English translation:

The comparison figure now shows in the post. I checked the draft as rendered on GitHub, and the figure loads properly at 1200×1670.

How I wrote the post, and my mistake
- I linked the image through an external raw.githubusercontent.com address, but the file wasn't on GitHub, so it was a 404. I didn't take care of the fact that it needed a push while writing the post. I only noted it in the checklist comment and never checked the result myself.
- I shouldn't have suggested switching to a relative path, since that doesn't work on DEV anyway.
- Asking in my previous message whether to push made it a detour. This time I took your request to mean the images have to be visible, and pushed right away.

What I did this time
- Pushed 4 local commits with --no-verify (416d025 to 1113c85). The remote still has 0 entire refs.
- The model-comparison.jpg and Sentry capture URLs all open with 200.
- The image addresses in the post point at the feat/real-gemma-sentry-tracing branch. They break if the branch is deleted or renamed, so before publishing it's safer to pin them to a commit SHA or upload the images in the DEV editor directly.

Worth knowing
- The app's file panel may show markdown as source, so images may not appear there. You can see the rendered version in DEV_SUBMISSION.md on GitHub.
- I couldn't test how these external addresses look in the DEV editor. Check once in DEV's preview after pasting.
- The 5 Sentry captures aren't referenced in the post yet, so they don't appear in it. Right now the only image in the post is the comparison figure.

Decide whether to put the Sentry captures and trace numbers into the Sentry section and add more images to the post.

You

우리 프로젝트 이미지가 보여야되는거 아님?


English translation:

Shouldn't our project's images be visible?

Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
1 message not shown
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
1 message not shown
Agent
Agent
1 message not shown
Agent
Agent
Agent
Agent
Agent
Agent
Agent
1 message not shown
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
Agent
1 message not shown
Agent
Agent
Agent
Agent
Agent
Agent

맞습니다. 글에 프로젝트 자체의 화면이 없었던 건 제 누락이었고, 이제 앱 화면 5장을 넣었습니다. GitHub에 렌더된 글에서 이미지 9장이 모두 정상 로드되는 것을 확인했습니다. 이전에는 CC0 사진 비교 그림 1장과 파일로만 있던 Sentry 캡처뿐이었습니다.

글에 들어간 앱 화면 (실제 gemma3:4b 컴파일 결과, CC0 기숙사 사진, Atlas에서 불러온 것)
- What I Built 맨 위: 3D 화면. 관측은 실선, 추정은 점선이고 ±cm 껍질이 보입니다.
- JSON 예시 아래: 실제 RoomGraph JSON 한 노드(침대, inferred, 0.85, bbox2d: null)와 인스펙터 화면. 인스펙터에는 모델이 방 밖에 놓은 침대를 코드가 20cm 안으로 옮기고 inferred로 내린 사유가 보입니다.
- Demo 끝 "What a compile gives you": 근거 사진 패널(박스를 그리지 않는다는 안내)과 이동 what-if 검증(책상·의자 10cm 겹침, 책상·협탁 0cm 간격에 ±46cm 불확실성).
- Sentry 캡처 3장: 정상 실행(46.67초 중 모델 호출이 99.8%)과 실패한 gemma4 실행(2.2분짜리 시도 두 번이 출력 한계에 걸려 4.45분 뒤 폴백), 그리고 6건 목록.

이번에 같이 바꾼 것
- Prize Categories: "Best Use of Sentry Agent Tracing"을 숨은 주석에서 글 본문으로 올렸습니다. 트레이스와 스크린샷이 이제 있기 때문입니다. MongoDB(컬렉션 스크린샷 없음), Entire(공유 세션 없음), Copilot(사용 안 함)은 계속 빠져 있습니다.
- 체크리스트·크레딧: 체크리스트와 CREDITS.md를 현재 상태에 맞게 고쳤습니다.
- 개인정보: 앱 이미지 5장에서 EXIF와 식별 문자열(이메일, 조직명, 클러스터 주소)이 0건이었습니다.

푸시와 상태
- --no-verify로 푸시했습니다(d1ed312). origin의 entire 참조는 0개입니다.
- 앱(API는 gemma3:4b 설정으로 포트 8787, 웹은 5180)과 Ollama 서버는 제가 켠 채로 남아 있습니다. 화면 확인 중 Ollama가 꺼져 있어서 ollama serve로 다시 켰습니다. .env의 GEMMA_MODEL은 여전히 gemma4:e4b이고, 제가 켠 API 프로세스만 환경변수로 gemma3를 쓰고 있습니다.

글에 아직 없는 것
- MongoDB: Atlas 컬렉션 스크린샷이 필요합니다.
- Entire: DevRelay로 저장한 세션 링크가 필요합니다.
- 첫 사용자: "My First User" 내용이 없습니다.
- 영상: 데모 영상 또는 GIF가 없습니다.
- 앱 이름 표시: 스크린샷의 장면 제목이 "p1 — Compiled RoomGraph"처럼 테스트 이름입니다. 마음에 안 드시면 새로 컴파일할 때 사진 파일 이름을 바꿔서 다시 찍을 수 있습니다.

다음으로 어느 것부터 진행할지 정해 주세요.


English translation:

You're right. Having no screens of the project itself in the post was my omission, and I've now added 5 app screens. I confirmed that all 9 images load properly in the post as rendered on GitHub. Before, there was only the comparison figure of CC0 photos, plus Sentry captures that existed only as files.

App screens in the post (a real gemma3:4b compile result of the CC0 dorm-room photo, loaded from Atlas)
- Top of What I Built: the 3D view. Observed objects are solid, inferred ones dashed, and the ±cm shells are visible.
- Below the JSON example: one node of the real RoomGraph JSON (the bed, inferred, 0.85, bbox2d: null) and the inspector. The inspector shows the reason: the model put the bed outside the room, and the code moved it 20 cm inside and lowered it to inferred.
- End of Demo, "What a compile gives you": the evidence photo panel (with the note that no boxes are drawn) and the moving what-if check (desk and chair overlapping by 10 cm, desk and nightstand at a 0 cm gap with ±46 cm uncertainty).
- 3 Sentry captures: a normal run (the model call is 99.8% of 46.67 s), the failed gemma4 run (two 2.2-minute attempts hit the output limit and it fell back after 4.45 minutes), and the list of 6 runs.

Also changed this time
- Prize Categories: moved "Best Use of Sentry Agent Tracing" out of the hidden comment into the post body, because the traces and screenshots exist now. MongoDB (no collection screenshot), Entire (no shared session) and Copilot (not used) are still left out.
- Checklist and credits: updated the checklist and CREDITS.md to the current state.
- Privacy: the 5 app images had 0 EXIF entries and 0 identifying strings (e-mail, organisation name, cluster address).

Push and state
- Pushed with --no-verify (d1ed312). origin has 0 entire refs.
- The app (API on port 8787 with the gemma3:4b setting, web on 5180) and the Ollama server are still running as I started them. Ollama was off while I was checking the screens, so I restarted it with ollama serve. GEMMA_MODEL in .env is still gemma4:e4b; only the API process I started uses gemma3, through an environment variable.

Not in the post yet
- MongoDB: needs a screenshot of the Atlas collection.
- Entire: needs a link to the session saved with DevRelay.
- First user: there's no "My First User" content.
- Video: there's no demo video or GIF.
- Scene title: the scene title in the screenshots is a test name like "p1 — Compiled RoomGraph". If you don't like it, I can recompile with a renamed photo file and capture again.

Tell me what to do next.

Limitations

  • I have tried five room images, one run each, one of them a 3D render. Gemma gets a broad inventory of the room right more often than the layout: positions and sizes are rough, and it mislabelled a cat as a dog and listed a TV and a candle as floor objects. That is not a benchmark, and the app puts no photo boxes on compiled rooms because the model could not place them.
  • Gemma's geometry from a single photo is rough, which is why every object carries a label and a ±cm band.
  • There is no pixel-level calibration. The scale comes from Gemma's estimate using the reference object, and the app shows px/cm: n/a rather than inventing a ratio.
  • The moving quote is a formula, not a learned model.

Prize Categories

Best Use of Gemma ($200). gemma3:4b runs locally through Ollama and is the only source of the objects, sizes and epistemic labels in a compiled RoomGraph. Its answer is requested with a JSON Schema, validated against the RoomGraph schema, retried once on failure, and replaced by a clearly labelled sample if it still fails. The code is in server/gemmaCompiler.ts and server/gemmaScene.ts.

Best Use of Sentry Agent Tracing ($100). Every compile is one gen_ai.invoke_agent trace: a span for the Ollama preflight, one gen_ai.chat span per Gemma request with the model name and Ollama's real input and output token counts, one for validation and one for the Atlas save. No prompt or photo is attached. The screenshots above are from real runs against a real Sentry project. A normal gemma3:4b compile is about 47 s with the model call 99.8% of it, and the gemma4:e4b run on the busiest room shows two attempts of about 2.2 minutes that each hit the 3.5k output-token limit before the app fell back to its sample room. The code is in server/sentryAgentTracer.ts.

Best Use of MongoDB Atlas ($100). Atlas is the data layer behind the app. Every compile is saved as one document in the roomgraphs collection of a real Atlas cluster, keyed by a unique sceneId, and the saved-rooms list in the app is read back from it; the three sample rooms are seeded there too. A text index over titles and labels exists, but the app's search still filters in code, so I do not claim Atlas search. Documents include the source photo, so storing in Atlas is opt-in through MONGODB_URI; without it the header says "memory only". The code is in server/atlasStore.ts.

Best Use of Entire ($100). Entire recorded the Claude Code sessions behind this build and linked them to commits: 14 of the repository's 15 commits carry an Entire-Checkpoint: trailer. The records are in a private repository. Seven moments of them are embedded under "My Agent Session", with the dialogue translated into English.

Top comments (2)

Collapse
 
preksha_barjatya profile image
Preksha barjatya •

Carrying the uncertainty through to the output (observed / inferred / occluded_prior, plus a ±cm band) and then running the what-if check twice, nominal and worst-case, is what makes this useful. A 0 cm gap that could really be negative becomes "go measure this one" instead of a false green tick. I also appreciated the Gemma 3 vs Gemma 4 comparison with its limits stated plainly, and removing the canned fallback data. Would it help to let the user type in one measured gap and re-run the check, so the ±46 cm shrinks as they confirm things?

Collapse
 
scs0209 profile image
Changsu Seong •

Thank you, you read it exactly the way I meant it. "Go measure this one" instead of a false green tick is the whole reason the check runs twice.

And yes, I think that's the right next step. It isn't built yet: right now the two ± bands simply add up per pair (here 20 + 26 = ±46 cm), so the app can tell you which gap to measure but can't take your measurement back.

The way I'd do it: a field next to each flagged pair where you type the tape-measured gap, and the check re-runs with that pair's band replaced by the tape's ±1–2 cm. Two things I'd want to get right. A measurement only tightens that one pair; the desk still has other neighbours whose gaps stay uncertain. And a measured number is a different kind of evidence from what the model "observed", so it should show up as its own state instead of being folded into the model's.

Thanks for the suggestion, and for reading it that closely.