문서 목차
이 페이지에서목차

/pixel-loop loop — 디자인이랑 같아질 때까지 자동 수정

화면이 Figma 상태 프레임과 같아질 때까지 고치기 → 다시 그리기 → 스크립트로 픽셀 비교를 반복하고, 반복마다 점수 · 남은 차이 영역 · 고칠 자리 판정을 남깁니다. 새 화면을 시안대로 만들거나 어긋난 화면을 다시 맞출 때 쓰세요 — 고치지 않고 판정만 하려면 /cc-pixel-loop:verify.

항목내용
실행 명령/cc-pixel-loop:loop
분류Flutter
난이도●●● 높음
MCP 서버figma, marionette, dart

한마디로#

Figma 디자인과 앱 화면을 같은 크기 · 같은 배율로 찍어 프로그램이 비교하고, 같아질 때까지 코드를 고치는 반복 작업입니다. "시안 보고 → 화면 고치고 → 다시 찍어서 비교" 를 사람이 손으로 수십 번 하던 일을 AI 가 돌립니다. 같은지 다른지는 계산이 정하고, AI 는 다른 곳을 어디서 고칠지 가리고 고치는 일을 합니다.

누가·언제 쓰나요#

  • 새 화면을 Figma 디자인 그대로 만들어야 할 때 (create 모드)
  • 이미 만든 화면이 시간이 지나며 디자인과 어긋났을 때 (repair 모드)
  • "디자인이랑 똑같이 맞춰주세요" 같은 요청이 있을 때

⚠️ 주의: 이 명령은 실제 소스 코드를 고칩니다. 변경사항이 없는 깨끗한 상태이거나 별도 작업 브랜치에서 돌리세요.

무엇을 해주나요#

같아질 때까지 반복한 뒤 .claude/docs/pixel-loop/<run_id>/ 폴더에 결과를 남깁니다:

  • compare/ — 비교 실행 폴더(cc-pixel-loop:visual-verify 와 같은 모양): Figma 이미지 · 앱 화면 · 비교 이미지 · 보고서
  • diff/0N/ — 반복마다의 비교 보고서와 점수
  • judgement — 차이마다 "어디서 고치나(앱 코드 · 스타일 · CoUI 버전 차이 · 알려진 차이)" 판정
  • summary.md — 최종 요약(못 맞춘 차이가 남았다면 그 내용도)

어떻게 쓰나요#

# 새 화면을 디자인대로 — 섹션의 상태 프레임 전부를 라이트 · 다크로 (기본 원천: 테스트 렌더)
/pixel-loop loop https://www.figma.com/design/AbCd/App?node-id=12-340 --app <앱 경로>

# 어긋난 화면 다시 맞추기
/pixel-loop loop <섹션 url> --mode=repair --app <앱 경로>

# 실행 중인 앱으로(기기) — 동작까지 보며 맞출 때. 완료 판정의 근거로는 쓰지 않는다
/pixel-loop loop <프레임 url> --app <앱 경로> --source=device --route=/home --target=ios

주요 옵션:

  • figma-url (필수) — 섹션(그 안의 상태 프레임 전부) 또는 상태 프레임 하나의 주소
  • --mode — create(새 화면) 또는 repair(어긋난 화면 보정). 기본 create
  • --source — render(기본 — 기기 없이 테스트로 그린다) 또는 device(실행 중인 앱)
  • --app — 앱 경로(서체 · CoUI 판 · 리터럴 덧대기 검사가 읽는다)
  • --exclude-theme — 내지 않는 밝기를 이유와 함께 빼기(예: "dark:라이트 전용 제품"). 기본은 라이트 · 다크 둘
  • --iteration-cap — 최대 반복 횟수. 기본 6 (정식 이름은 이 하나입니다. 허용 오차 안에 iteration_cap=8 처럼 끼워 적은 형태도 같은 값으로 읽어 줍니다)

안에서 무슨 일이 벌어지나요#

다섯 단계를 한 바퀴로 묶어 같아질 때까지 반복합니다.

  1. 읽기(Read) — Figma 에서 상태 프레임과 그 크기 · 테마 · 서체를 읽고, 같은 배율의 사진을 내려받습니다. Figma 를 읽지 못하면 짐작하지 않고 그 자리에서 실패로 끝냅니다.
  2. 고치기(Write) — 고치기 전에 차이마다 어디서 고칠지 먼저 가립니다. CoUI 쪽 차이(판이 달라서 · 이미 알려져서)는 앱에서 고치지 않고, 앱 코드 · 스타일 차이만 CoUI 부품과 토큰으로 고칩니다. 숫자를 덧대 맞추는 수정은 고친 것으로 세지 않습니다.
  3. 다시 그리기(Reload) — 테스트 렌더면 화면을 다시 그리고, 실행 중인 앱이면 핫 리로드합니다(실패하면 앱을 다시 띄우되 한 실행에 통틀어 2번까지).
  4. 찍기(Capture) — 같은 크기 · 같은 배율로 앱 화면을 찍습니다.
  5. 비교(Compare) — 프로그램이 두 사진을 비교해 남은 차이를 점수 하나로 적습니다. 통과가 아니면 2번으로 돌아갑니다.

언제 멈추나요?

  • Figma 를 읽지 못하면 → 아무것도 고치지 않고 그 사실을 summary.md 에 적고 종료
  • 비교가 통과하면(CoUI 쪽 차이는 근거와 함께 면제) → 완료
  • 최대 반복 횟수에 도달하면 → 남은 차이를 summary.md 에 적고 종료
  • 고쳐도 나아지지 않으면 → 종료. 점수가 2번 연속 줄지 않았거나, 최근 3번 안에 나왔던 "차이 조합"이 또 나온 경우(색 고치면 여백이 깨지고 여백 고치면 색이 깨지는 제자리 왕복)
  • 실행 중인 앱을 2번 다시 띄워도 화면을 못 잡으면 → 그 사실을 summary.md 에 적고 종료

어떤 경우든 답을 기다리며 멈춰 서 있지 않습니다. 결과는 항상 summary.md 에 기계가 읽을 수 있는 상태값으로 먼저 적고 종료하며, 사람이 직접 부른 경우에만 그 뒤에 추가로 물어봅니다.

중간에 끊긴 실행을 같은 run_id 로 다시 부르면 이미 끝난 반복 다음 번호부터 이어가고, 써 버린 반복 횟수도 그대로 이어서 셉니다.

repair 모드에서는 고치기 전 비교를 한 번 더 남겨, 요약에서 수정 전후를 볼 수 있게 합니다.


⚙️ 상세 옵션·실행 명세 (개발자 / AI 에이전트용)

비교 · 판정 · 기본값의 정본은 cc-pixel-loop:visual-verify 다 — 이 커맨드는 그 비교를 반복마다 돌리고, 반복 사이에 코드를 고친다.

Parameters#

ParameterRequiredDescriptionDefault
figma-url✅섹션 또는 상태 프레임 URL (node-id)—
--mode❌create (new screen) or repair (drift fix)create
--source❌render — 위젯북 use_case 상태를 프레임 크기 장치로 테스트 캡처(기기 불필요, Flutter Web 포함) · device — 실행 중인 앱의 Marionette 스크린샷render
--app✅앱(워크스페이스) 루트—
--testconditionalrender 원천의 캡처 테스트 파일(visual-verify 3절). 없으면 Phase 1 이 cc-blueprint:widgetbook-wiring 4b 의 모양으로 만든다(같은 mock 시나리오)—
--exclude-theme❌'<theme>:<이유>' — 밝기는 늘 라이트 · 다크 둘이고, 빼는 것은 이유와 함께만—
--ignore-frame · --mask-node❌visual-verify 의 plan 옵션 그대로(화면이 아닌 프레임 · 계속 바뀌는 노드, 이유와 함께)—
--routeconditionaldevice 원천의 앱 안 경로 (e.g. /home)—
--target❌device 원천의 기기: ios, android, macos, linux, windows, autoauto
--tolerance❌pixel=N,color_delta_e=N,text_pixel=N,text_delta_e=Nvisual-verify 기본값
--budget❌ratio=N,region=Nvisual-verify 기본값
--iteration-cap❌Max iterations before bailing out. This spelling is canonical.6
--run-id❌Reuse an existing .claude/docs/pixel-loop/<run_id>/ folder — resumes at max(N)+1 with the consumed budget carried forwardnew YYYYMMDD-HHMM
--non-interactive❌Never ask anything: write summary.md and return. auto = on whenever another command or a dispatched worker invoked this one (e.g. run.md Step 7.3)auto

Iteration cap — one spelling, one parser. A cap written inside the --tolerance block (--tolerance pixel=2,color_delta_e=3,iteration_cap=8) is a key the comparison does not accept; a parser that discards it silently makes the loop unbounded. Rules, in order:

  1. --iteration-cap=N is the canonical flag.
  2. iteration_cap=N appearing inside --tolerance is accepted as an alias, normalized to --iteration-cap=N, and removed before the tolerance reaches figma_compare.py (which rejects unknown keys). The normalization is logged.
  3. If both appear, the explicit flag wins.
  4. An unparsable or out-of-range cap is never "unbounded" — the default 6 applies and the substitution is written into summary.md. (../../cc-dev/rules/orchestration-graph.md §3.2, null-as-pass: an unread limit must not read as "no limit".)

Phase Flow#

Substrate: inline / sequential — unchanged, and un-parallelizable by construction. Read → Write → Reload → Capture → Compare is a strict data dependency: the next patch cannot be written before the previous capture is compared. A run holds one render target (the capture test, or one device / VM-Service session) for its whole duration, and nothing inside a run is fanned out. Notation, loop-contract fields, gate tri-state and the substrate row are defined once in ../../cc-dev/rules/orchestration-graph.md (§1, §2, §3, §4) and are cited, not restated. Port / simulator / device handles come from ../../cc-dev/skills/parallel-test-env/SKILL.md (§4.3).

Normative declaration of this flow. The numbered list below it is a derived, non-normative view (../../cc-dev/rules/orchestration-graph.md §6 — if the two disagree, the block wins):

PL0    ACT   Read — compare-frames + plan + Figma exports, once per run   writes:compare/frames-*.json,compare/plan.json,compare/figma/**
PL0g   GATE  Figma read                                       verdict:whoami && get_metadata && compare-frames returned && plan.comparisons > 0  undet:fail  fail:PL9
PLc    LOOP  converge cycle (pixelloop.loop-converge-core)    contract:L-PL1
PL1    ACT   Write — judge regions, then patch app/style-slot only   writes:lib/**,compare/judgement.json  tier:standard
PL1g   GATE  no literal patch                                 verdict:literals.findings == 0  undet:fail  fail:PLc
PL2    LOOP  reload ladder (pixelloop.reload-recovery-ladder) contract:L-PL2
PL3    ACT   Capture — capture test or nav.yaml replay → compare/render/<label>.png   writes:compare/render/**,nav.yaml
PL3g   GATE  every comparable render saved                    verdict:render/<label>.png exists for every comparable label  undet:fail  fail:PLc
PL4    ACT   Compare — figma_compare diff (each label) + summary   writes:compare/reports/**,diff/0N/**
PL5    GATE  progress guard (pixelloop.oscillation-guard)     verdict:!stalled(N) && !oscillating(N)  undet:fail  fail:PL9
PL7    GATE  converged                                        verdict:summary.status == "pass"  undet:fail  fail:PLc
PL9    ACT   summary.md status + return                       writes:summary.md   # 이 흐름의 HALT 래퍼 — 묻지 않는다
S7.3   LOOP  caller — run.md Step 7.3 design verification     contract:L-7.3  extern:true

PL0 --> PL0g --> PLc --> PL1 --> PL1g --> PL2 --> PL3 --> PL3g --> PL4 --> PL5 --> PL7 --> PL9
S7.3 ..> PL0                                    # advisory: 직접 호출도 정상 진입점이다
PL0g ~~> PL9  on:!figmaRead                     record:summary.md status:"undetermined:figma-unreadable"+the failing call   # 스크린샷에서 짐작하지 않는다
PL1g ==> PLc  on:literals.findings > 0          bound:6  invalidates:PL1,PL1g   # 그 패치는 되돌리고 반복 예산에서 센다
PL3g ==> PL2  on:render.missing && source=="device"  bound:2  invalidates:PL3,PL3g   # 기기 — 재시작 사다리, bound 는 run 전체 합산
PL3g ==> PLc  on:render.missing && source=="render"  bound:6  invalidates:PL1,PL1g,PL2,PL3,PL3g   # 캡처 테스트 실패는 코드 · 테스트 오류 — 다음 Write 가 고친다(반복 예산에서 센다)
PL2  ~~> PL9  on:restartsUsed==2 && !vmAlive     record:summary.md status:"not-converged:reload-exhausted"+VM error
PL5  ~~> PL9  on:stalled||oscillating            record:summary.md status:"not-converged:stalled|oscillating"
PL7  ==> PLc  on:summary.status != "pass"        bound:6  invalidates:PL1,PL1g,PL2,PL3,PL3g,PL4,PL5,PL7   # = --iteration-cap 기본값
PLc  ~~> PL9  on:iterations==iterationCap        record:summary.md status:"not-converged:cap-exhausted"
PL7  ~~> PL9  on:channels==[]                    record:summary.md status:"undetermined:no-comparable-channel"   # 대조할 수 있는 비교가 하나도 없다
PL7  ~~> PL9  on:comparablePass && designSideOnly  record:summary.md status:"undetermined:design-side"+the labels   # 남은 것은 시안 쪽 판정 불가뿐 — 코드로 고칠 수 없다

Reading it: PL0g is the first gate — a run that could not read Figma reaches PL9 before any patch is written. PL9 is the only sink and it always writes summary.md first, so every terminal failure (fail:PL9) is a HALT with a durable, machine-readable record. PL7's failure is the ordinary case (not converged yet) and is the feedback edge; PL5 sits before it so a stalled or oscillating run cannot spend the rest of the budget. PL1g makes a literal patch a wasted iteration rather than progress — the patch is reverted and the cycle starts over, charged to the same cap. PL7 reads summary.status, so an undetermined comparison (figma_compare.py exit 3) can never read as convergence.

A comparable label is one whose Figma side exists — its source is not unthemable and its export or clone was downloaded. A label that is not comparable is not a Figma read failure (PL0g passes) and not a capture failure (PL3g does not wait for it): it stays undetermined in the summary. When every comparable label passes and only design-side undetermined labels remain (figma-unthemable, unnamed-frame, a clone that could not be made), the loop stops with undetermined:design-side instead of spending the cap on something no patch can fix — the caller routes it to the designer (../../cc-dev/commands/run.md Step 7.3 ⑤b).

  1. Read — the official Figma MCP (../README.md → Prerequisite), once per run: whoami → create_session → cc-blueprint 의 compare-frames.figma.js (섹션 또는 프레임) → figma_compare.py plan → download_assets(png, defaultScale) for every planned label (다른 밝기는 dark-frames.figma.js 복제본 — 받은 뒤 반드시 remove) → fonts · coui. get_design_context (load figma:figma-design-to-code first) is read once for the spec Write needs — its reference code is React/HTML to translate into CoUI, not to copy. If whoami, get_metadata, compare-frames or plan fails, PL0g fails: status: undetermined:figma-unreadable, and no spec is reconstructed from a screenshot. A clone or download that fails for one comparison leaves only that comparison undetermined — it is not a Figma read failure. If the capture test is missing, Phase 1 writes it in the shape of cc-blueprint:widgetbook-wiring 4b (the same mock scenarios as the widgetbook state knob).
  2. Write — 남은 영역마다 고칠 자리를 먼저 compare/judgement.json 에 적는다(visual-verify 5절 — known · version · style-slot · app). known · version 은 고치지 않는다. style-slot · app 만 CoUI 부품과 토큰으로 고치고, 반복마다 가장 작은 패치를 고른다. 그다음 figma_compare.py literals --base <loop 시작 커밋> — 덧대기가 있으면 PL1g 가 그 패치를 되돌린다.
  3. Reload — render 원천은 할 일이 없다(다음 Capture 가 테스트를 다시 돌린다). device 원천은 Dart MCP hot_reload, 필요하면 hot_restart(run 의 재시작 예산 2 — Failure Recovery).
  4. Capture — render: 캡처 테스트(CO_GOLDEN_MODE=capture flutter test <test>)를 돌리고 figma_compare.py ingest --run compare --captures <CO_GOLDEN_OUTPUT>/images/<suite> 로 plan 의 label 마다 compare/render/<label>.png 에 옮긴다(장치 · 시나리오 이름은 plan 의 devices · captureScenario). device: marionette.connect → nav.yaml 재생 → take_screenshots(기기 배율로 — plan 의 --scale 을 기기 배율로 만든다).
  5. Compare — label 마다 figma_compare.py diff --run compare --label <label>, 그다음 summary --check-issues. compare/reports/ 를 diff/0N/ 에 복사하고 diff/0N.yaml 에 점수를 적는다. 통과가 아니면 2번으로.

Convergence#

Every iteration writes three machine-readable fields into diff/0N.yaml — the per-region detail stays in diff/0N/<label>.report.json:

fieldmeaning
delta_idssorted set of <label>/<region id> for regions still open after judgement — e.g. 12-345__light/node:12:357. Region ids are node ids from the Figma frame, so the same defect keeps the same id across iterations. The identity of what is still wrong.
scoresummary.json 의 openScore — 면제를 뺀 남은 차이 넓이(논리 px²). figma_compare.py 가 계산한다. unknown if any comparison was undetermined — never 0.
channels[visual] when every comparable label produced a report, [] otherwise.

The number is computed by the script, not estimated by the model, so it is comparable across iterations of the same run. score is the loop's prog: quantity.

Contract L-PL1 (pixelloop.loop-converge-core) — field definitions: SoT §2. Values live here.

inv:      exactly one render target is live for the whole run (the capture test, or one device /
          VM-Service session) and every capture reads that one; iteration N does not end before
          diff/0N.yaml is written (no unrecorded iteration)
prog:     score (summary.openScore), strictly decreasing per iteration
          no-prog: never re-apply the same patch to the same delta_ids — the run terminates per
          term:, it does not spend the remaining budget on the same attempt. A literal patch is
          reverted (PL1g) and counts as an iteration without progress
term:     converged      = summary.status == "pass" && channels !== []
          || designSide      = every comparable label passes && only design-side undetermined remain
          || stalled(N)      = N>=3 && score(N) >= score(N-1) && score(N-1) >= score(N-2)
          || oscillating(N)  = N>=2 && delta_ids(N) ∈ { delta_ids(N-1), delta_ids(N-2), delta_ids(N-3) }
          || iterations === iterationCap
          score === unknown counts as "did not improve" — never as convergence
budget:   6 iterations (--iteration-cap; alias rules in Parameters) · 2 app restarts per run (L-PL2)
exhaust:  write summary.md (status block below) with residual delta_ids + score history, then return.
          No AskUserQuestion: a dispatched worker or CI has nobody to ask (SoT §2)
resume:   the existing .claude/docs/pixel-loop/<run_id>/ folder — resume at max(N in diff/0N.yaml)+1
          and carry the consumed budget forward (iterations spent = count(diff/*.yaml); re-entry
          never resets the cap). Idempotent on re-entry: compare/figma/** and plan.json are re-read
          from disk (Figma is not called again), nav.yaml replayed, diff/ is append-only
log:      one line per iteration including the score —
          "[3/6] Compare  ✗ score 41→17 (3 open regions, channels=visual)"
          plus one terminal status line; dropped regions are named, not summarized as "some"

Why the guard watches two things. A period-2 ping-pong (fix colour → break padding → fix padding → break colour) yields alternating diffs, so consecutive iterations never match. stalled() catches it by magnitude (the score stops falling) and oscillating() by identity (the same delta_ids set reappears within the last 3 iterations); one set repeating at distance 2 is enough.

Example#

/pixel-loop loop https://www.figma.com/design/AbCd/App?node-id=12-340 --app ../app --mode=create

Console transcript:

[0/6] Read     ✓ compare-frames 12:340 (frames 4) · plan 8 (4 × light/dark) · figma 8/8 · fonts pass · coui coui_flutter 0.163.0
[1/6] Judge    ✓ 5 regions → app 3 · style-slot 1 · version 1 (coui:card)
[1/6] Write    ✓ patched lib/src/presentation/page/home_page.dart (+21, -9) · literals 0
[1/6] Capture  ✓ capture test → render 8/8
[1/6] Compare  ✗ score 341.5 → 96.0 · 2 open regions (channels=visual)
[2/6] Write    ✓ patched ... · literals 0
[2/6] Compare  ✓ score 96.0 → 0.0 · summary pass (excused 1: version coui:card)
[2/6] Exit     ✓ summary.md status: converged (iterations 2/6, restarts 0/2)

Repair Mode#

/pixel-loop loop <figma-url> --mode=repair --app <앱 경로>

Repair mode runs one Compare before any Write and keeps it as diff/00/, so the summary shows before/after. The agent does not assume the existing widget tree is wrong — judgement decides whether a region is the app's to fix at all.

Failure Recovery#

Contract L-PL2 (pixelloop.reload-recovery-ladder) — field definitions: SoT §2. Values live here. The render source has no ladder: a failed capture test is a build or test error, fixed by the next Write. The ladder below is the device source's.

inv:      at iteration end the app answers vm_service getVM and the Marionette session sits on --route
prog:     rung = 0 hot_reload → 1 hot_restart → 2 relaunch (`flutter run`), strictly increasing
          no-prog: a rung is never retried at the same rung → go up one rung (SoT §2 default ladder)
term:     getVM answers && nav.yaml replay reaches --route && every planned render is written
budget:   2 restarts per run — rungs 1 and 2 combined, counted across all iterations of the run,
          not per iteration (6 iterations do not buy 12 restarts)
exhaust:  summary.md status:"not-converged:reload-exhausted" + the raw Dart VM error, then return
resume:   nav.yaml (the recorded route recipe) + restarts in summary.md's status block + max(N) in diff/
log:      "reload #2: rung1 hot_restart ok (restarts 1/2)" — the restart counter on every line

If hot reload starts failing mid-loop (e.g. a structural change broke the isolate), the command:

  1. Issues hot_restart (rung 1) and increments restarts. At most 2 restarts per run.
  2. Re-connects Marionette (a restart drops the VM Service session) and replays nav.yaml rather than re-deriving the route.
  3. Rung 2 relaunches the app (flutter run) — charged to the same 2-restart budget.
  4. Once both restarts are spent and the VM is still unreachable, writes summary.md with status: not-converged:reload-exhausted plus the raw Dart VM error and returns.

Recorded on the first successful capture and rewritten only when the route itself changes:

route: /home
vm_service_hint: reconnect                    # fresh URI after every restart — never cached
steps:
  - { tool: tap,       key: home_tab }        # ValueKey first; text matching only as a last resort
  - { tool: scroll_to, key: featured_section }
  - { tool: enter_text, key: search_field, text: "" }
settle_ms: 300
recorded_at_iteration: 1

Keys come from the same ValueKey discipline as ../skills/pixel-loop-guide/SKILL.md (Phase 4) — a recipe built from text matching is not replayable. If the replay does not reach route, the capture gate (PL3g) fails and re-enters the ladder; it is never treated as a successful capture.

Output#

.claude/docs/pixel-loop/<run_id>/
├── compare/                  # visual-verify 실행 폴더 — plan · figma · render · reports · judgement · summary
├── nav.yaml                  # device 원천만 — route recipe
├── diff/
│   ├── 00/ (repair) · 0N/    # 그 반복의 reports
│   └── 0N.yaml               # delta_ids + score + channels
└── summary.md                # status block (below) + residual regions

summary.md status block — the exit contract#

Written on every exit path, before returning, as the first block of the file:

status: not-converged:oscillating
# converged
# | not-converged:{cap-exhausted, stalled, oscillating, reload-exhausted}
# | undetermined:{figma-unreadable, no-comparable-channel, design-side}
run_id: 20260801-1412
source: render                # render | device
iterations: 4                 # spent, of iteration_cap
iteration_cap: 6              # effective value
iteration_cap_source: flag    # flag | tolerance-alias | default (unparsable → default, never unbounded)
restarts: 1                   # of 2 (device source)
score_history: [341.5, 96.0, 96.0, 96.0]
residual_delta_ids: [12-345__dark/node:12:357]
literal_patches_reverted: 1
compare_summary: compare/summary.json
channels: [visual]

undetermined:figma-unreadable is not a design diff: nothing was compared and no patch was written. A caller treats it like missing tooling (../../cc-dev/commands/run.md Step 0 · 7.3) — never as convergence, never as a screen to repair. undetermined:design-side is the same for the caller: the comparable part converged, the rest needs the designer (bind the colours, name the frames).

Callers branch on status, never on the prose below it — ../../cc-dev/commands/run.md Step 7.3 (L-7.3) consumes this and records its own verdict there, per ../../cc-dev/rules/orchestration-graph.md §3 (a missing or unreadable status is undetermined, and undetermined fails). A re-entry with --run-id reads iterations and restarts back out of this block, which is why both counters are written even on a converged run.

검색어를 입력하면 문서를 찾습니다.

↑ ↓ 이동 · Enter 열기Esc 닫기