Open-sourcing the capability Cinema Studio sells — as a measurable, verifiable
property of a ComfyUI graph.
Every other camera tool in the ComfyUI ecosystem composes a prompt. This one
judges the result: it measures the frame against 14 machine-evaluated cinematography
rules, returns a pass/fail per rule with a correction expressed in metres, and can iterate the camera
until the frame passes.
23 DONE1 IN FLIGHT1 PLANNED1 NOT VERIFIED15 GAPS LOGGED
Deliverable 1 · the blog
The build, step by step.
Chronological, 2026-10-29. Every timestamp is a real file mtime or a real event in this
session — nothing here is a reconstruction. Status chips use PLAN.md's vocabulary exactly.
Phase 0 · ResearchDONE
Premise check: what did Higgsfield actually open-source?
The event is framed as “Open Source Higgsfield,” which reads as
there is something to fork. That had to be settled first, because the entire build depends on it.
analysis/10-comfyui-substrate-inventory.md (16:20) and
analysis/11-contentstudio-inventory.md (16:23) were written to answer it from source.
The answer is nothing. No Higgsfield model weights, no training code, no platform source.
Cinema Studio 4.0 runs on Seedance 2.5 (ByteDance) — a closed, hosted API. There is no repo to
fork, nothing to vendor, nothing to patch.
The re-aim, and the licence trap that shaped everything after it
analysis/20-competition-brief.md. The re-aim in one line: Cinema Studio's value is
“re-shoot this from any angle and it will still cut together.” We are open-sourcing that
guarantee — as a measurable, verifiable property of a ComfyUI graph, not a prompt.
The constraint that shaped every other decision, and the part most likely to be got wrong by other entrants:
Model family
Licence reality
Verdict
Wan 2.1 / 2.2
Apache-2.0 — the only fully unencumbered open video family
USE
LTX 2.3 / 2.5
Free below $10M revenue; the LTX VFX bonus stacks cleanly
USE
MiniMax H3
US is an excluded territory; $5,000/month for local commercial use
DO NOT USE
Seedance 2.5
Closed hosted API
DO NOT USE
“Open weights” is not “open licence.” A pack that ships a MiniMax H3 graph is not an
open-source submission in the US — it is a licence violation with a demo attached. The commitment is
checked by grep, not asserted.
Spec frozen — and the honesty ledger written in, not retrofitted
00-BREAKDOWN.md and 01-SPEC.md written and frozen. The spec is what the
differential check is measured against later, so freezing it early is what makes that check meaningful.
The honesty ledger went in at this point rather than being added after the fact: the reference rule set documents 38 rules, of which 14 are machine-evaluated: 9 framing rules are evaluated per frame, plus 3 multi-entity scene rules and 2 sequence-level continuity rules. The other 24 are documented prose, not code. We do not claim 38 rules are implemented.
Why separate: the engine imports nothing from ComfyUI. It is testable, portable and reviewable
without a GPU or a ComfyUI install. It is a faithful port of a Go reference implementation — which is the only
reason the differential check in Phase 4 is possible at all.
The solver fix, and the claim we do not make. The Go original runs exactly 24 iterations and
returns the 24th iterate's report unconditionally. There is no convergence test and no early exit, so
nothing guarantees overall_pass. Our port adds early exit on overall_pass, a
converged flag, the iteration count actually used, and a residual naming the rules still
failing. We do not claim the solver converges in general. We claim it reports honestly whether it did.
# app/cinematographer/autoframe.py — the fix, and the honest report
#
# The Go original runs exactly 24 iterations and returns the 24th iterate's report
# *unconditionally*. There is no convergence test and no early exit, so nothing
# guarantees ``overall_pass``. This port:
# * exits early as soon as the frame passes,
# * reports ``converged`` honestly,
# * reports the iteration count actually used,
# * reports the residual (which rules were still failing) when it does not converge.
MAX_ITERATIONS = 24
MIN_DISTANCE = 0.3
@dataclass
class AutoFrameResult:
camera: CamPose
report: FramingReport
converged: bool
iterations: int
residual: list[str] = field(default_factory=list)
for i in range(max_iterations):
iterations = i + 1
...
rep = evaluate_framing(subject, cam, shot_type, it)
# --- the fix: stop as soon as the frame is actually compliant ---
if early_exit and rep.overall_pass:
break
...
residual = [f"{s.rule}={s.value:g}" for s in rep.failures() if s.weight >= 0.8]
return AutoFrameResult(
camera=cam, report=rep,
converged=rep.overall_pass, # <- never asserted, always measured
iterations=iterations,
residual=residual,
)
def autoframe_summary(result: AutoFrameResult) -> str:
if result.converged:
return (f"CONVERGED in {result.iterations} iteration(s) - "
f"{result.report.shot_type} frame passes all weighted rules")
return (f"NOT CONVERGED after {result.iterations} iteration(s) - "
f"still failing: {', '.join(result.residual) or 'unknown'}")
EVIDENCEapp/cinematographer/autoframe.py
Phase 1 · TestsDONE
The unit suite
test_autoframe.py 16:59 · conftest.py 16:59 · test_geometry.py 17:00 ·
test_continuity.py 17:00. test_framing.py follows at 17:35, after a correction (see the
17:34 entry).
The adversary re-derived the expected values from first principles and confirmed they are genuinely
hand-computed rather than snapshots — but also confirmed the suite is a value test, not a contract
test: 11 of 24 injected defects survive it. That is defect #5, and it is still open.
EVIDENCEapp/tests/
Phase 2 · Node packDONE
The ComfyUI surface — six nodes, one category
nodes.py 17:03 · app/cinematographer/__init__.py 17:03. Six nodes under the
Cinematographer category. Every structured input and output is a plain JSON string, so the nodes compose
with anything in a graph and are trivially testable without ComfyUI installed.
nodes.py is the only file in the package permitted to touch ComfyUI, and even there
the dependency is optional — the probe is wrapped, so the module and every node class import cleanly on a machine
with no ComfyUI. That is what lets python -c "import cinematographer" work in CI and lets the
director's panel reuse the same classes.
# app/cinematographer/nodes.py — the only file in the package that touches ComfyUI,
# and even here the dependency is optional.
try:
import comfy.utils as _comfy_utils
COMFYUI_AVAILABLE = True
except Exception: # a half-installed ComfyUI can raise anything
_comfy_utils = None
COMFYUI_AVAILABLE = False
NODE_CLASS_MAPPINGS = {
"CinematographerEvaluate": CinematographerEvaluate,
"CinematographerAutoFrame": CinematographerAutoFrame,
"ChorosContinuity": ChorosContinuity,
"SceneFramingExtras": SceneFramingExtras,
"CameraReliabilityZone": CameraReliabilityZone,
"CinematographerCorrection": CinematographerCorrection,
}
EVIDENCEapp/cinematographer/nodes.py
Phase 4 · Verification harnessDONE
The differential check — the strongest evidence in the package
differential_check.py 17:03 · the reference dump and its JSON fixture 17:04.
It runs the Go reference dump through the Python port at TOL = 1e-6 and compares value-for-value.
That is what turns “we ported it” into “we ported it correctly.”
What the 289 is, exactly. The adversary instrumented the counter and decomposed it: it is 289
heterogeneous rule-slots checked, not 289 assertions. Two of those slots
(size_importance) compare pass only — the value is skipped. The headline number is exact;
the phrase “value-for-value” in verification/README.md is not, and that is defect #3.
# verification/differential_check.py — the strongest evidence in the package.
# Runs the Go reference dump through the Python port at TOL = 1e-6, value-for-value.
#
# $ PYTHONDONTWRITEBYTECODE=1 /tmp/cine-venv/bin/python \
# verification/differential_check.py verification/go-reference-dump.json
# differential checks run: 289
# ALL FIXTURES AGREE with the original Go implementation.
#
# 289 is a count of *rule-slots checked*, not of assertions. The adversary decomposed it:
# EvaluateFraming 9 fixtures x 9 rules 81
# AutoFrameCamera 18 fixtures x 9 rules 162
# AutoFrameCamera 18 camera poses 18
# AutoFrameCamera 18 early-exit asserts 18
# EvaluateContinuity 6 issues 6
# SceneFramingExtras 4 scores 4
# ----
# 289
# Two of those slots (size_importance) compare `pass` only — the value is skipped.
EVIDENCEverification/differential_check.py
Phase 2 · PackagingDONE
Packaging and licence
pyproject.toml, requirements.txt and LICENSE (Apache-2.0)
written at 17:04. The competition requires an OSI licence; Apache-2.0 is the same licence as the Wan 2.2 stack the
graphs load, so the code and the weights agree.
EVIDENCEapp/LICENSE · app/pyproject.toml
Phase 8 · Submission surfacePLANNED
The demo script and the submission checklist
02-DEMO-SCRIPT.md and 03-SUBMISSION.md at 17:05. Target 1:48,
hard limit 2:00 — the form rejects longer.
Submission needs: a public repo URL · a demo video under 2 minutes · a social post tagging
#OpenSourceHiggsfield · category multi-select · optional live link. The only stated rule is
one submission per person. No judges and no rubric have been published — so the
plan optimises for a legible two-minute demo rather than for a guessed rubric.
Two beats in this script do not match the running panel. That mismatch is still open.
Panel parity — a three-leg check, and why it is not circular
parity_check.py 17:06 · cinematographer.js 17:06 ·
parity_fixtures.json 17:07. The panel is a JavaScript mirror of the Python engine, and parity is checked
three ways: JS against frozen fixtures, Python against the same fixtures, and Python against JS live.
The third leg is what makes it non-circular — it does not go through the fixtures at all, so a JS-only or
Python-only drift cannot hide. The adversary tested this directly and confirmed it: JS mutations are caught.
But the parity check shares the differential check's blindness. Four of ten JS mutations went
undetected, all of them threshold moves. Same root cause: no fixture sits near a boundary. Defect #5.
EVIDENCEapp/web/parity_check.py
Phase 3 · WorkflowsNOT VERIFIED
Two workflow graphs — structurally valid, never executed
Naming correction recorded: the Wan graph was originally named
reshoot_wan22_vace.json. The checkpoint is Fun-Control, not VACE. Renamed, and the
_README naming note plus both cross-references updated.
A structurally valid graph is not a working graph. Neither has been executed. The
_README node inside each graph says so in its first line:
STATUS: NOT VERIFIED. This graph has never been executed. and
MEASURED: nothing. No timing, no VRAM figure, no output.
EVIDENCEapp/workflows/validate_graphs.py
Phase 5 · PanelDONE
The director's panel — and the failing frame it opens on
app.js and browser_check.mjs 17:14 · app/web/README.md 17:16.
A three.js panel that renders the camera, the subject and the rule results live. It is what makes the demo video
legible in under two minutes.
Honest note carried forward: the panel opens on a failing frame
(rule_of_thirds, headroom) so the demo has something to fix. After Auto-Frame the overall
badge reads PASS while lead_room (weight 0.4) may stay red — that is the spec, and the panel
shows it rather than hiding it.
The adversary called this the best-verified surface in the entry: the browser check really does drive headless
Chrome, click Auto-Frame, and sample WebGL pixels.
The panel's default state, captured for real at 17:54 PT: OVERALL FAIL, headroom 0.849 against the medium band [0.10, 0.45]. The panel opens on something to fix.
An independent adversary was pointed at our own claims
verification/ADVERSARIAL-REVIEW.md — 740 lines. Stance: falsify, not confirm.
A review that finds nothing is a failed review. 17-item defect register, 24-attempt falsification log.
Verdicts: A PARTIAL · B PARTIAL · C CONFIRMED ·
D CONFIRMED (not circular) · E PARTIAL · F CONFIRMED ·
G REFUTED · H PARTIAL.
Bottom line, in the adversary's own words: “Not safe to submit as-is.”
What survived attack — and this is the part worth keeping: the solver convergence claim survived
1,800 adversarial calls with zero violations (converged == report.overall_pass always;
no zero-iteration convergence; no lying residual). The parity check is not circular. The workflow validator caught
all 6 injected structural defects. Licence and trademark clean.
What did not survive: the pack root had no __init__.py. That is CRITICAL, and it is
the next entry.
This is the single most valuable document in the build, and it is the reason this log can be blunt.
The pack could not load at all — found by the adversary, fixed, then proved
The defect. ComfyUI's loader does not recurse. init_external_custom_nodes
uses os.listdir with no recursion, then loads
<pack_root>/__init__.py by path. A pack root must have an __init__.py.
Ours did not — the pack would have failed to load with
FileNotFoundError: 'ComfyUI-Cinematographer/__init__.py'. A judge following the README would have got
an empty category, not six nodes.
The fix.app/__init__.py written as a pack-root shim that inserts its own directory
into sys.path and re-exports the mappings.
The proof, re-run first-hand by buildlog at 17:58 PT — not quoted from PLAN.md:
And the limit of that proof, stated plainly. The same replay against the
repository root as pack root still fails with FileNotFoundError, because the repo root has no
__init__.py. The fix is real but conditional on the pack root being app/.
That gap was raised by buildlog at 17:58 and fixed by the Lead at 17:59 — see the pack-root fix above.
# app/__init__.py — the pack-root shim (the CRITICAL fix)
#
# **The pack root is ``app/``, not the repository root.** ...
# git clone <repo> ComfyUI/custom_nodes/ComfyUI-Cinematographer # WRONG
# then the pack root is the repository root, which has no ``__init__.py``, and
# ComfyUI fails with ``FileNotFoundError: 'ComfyUI-Cinematographer/__init__.py'``.
import os
import sys
_HERE = os.path.dirname(os.path.abspath(__file__))
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
try:
from cinematographer import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
except ImportError as exc:
raise ImportError(
"ComfyUI-Cinematographer: could not import the 'cinematographer' package from "
f"{_HERE!r}. ... The pack root is the 'app/' directory of the repository, not "
"the repository root ..."
) from exc
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
__version__ = "0.1.0"
EVIDENCEapp/__init__.py
Phase 6 · Defect fixesDONE
Three smaller defects closed
Defect
Fix
verification/README.md cited a non-existent panel_parity_check.py — in the file
whose stated rule is “a claim lives here only if a command in this file reproduces it”
Corrected to app/web/parity_check.py (17:34)
validate_graphs.py had SamplerCustomAdvanced: 1; upstream declares 2 outputs —
falsifying the “read from the node sources, not guessed” provenance claim
1 → 2 (17:34)
test_framing.py's hand-computed constant F = 3.4567701 was wrong in the 5th
decimal, and contradicted the verification README's own arithmetic
3.45679012345679 (17:35)
Three of the seventeen register items are closed. The three MAJOR items that remain open —
12 unconstrained constants, eyeline never differentially verified, and the
“value-for-value” claim — are IN FLIGHT and are still open.
PLAN.md — a running document, not a retrospective one
PLAN.md written to be read alongside the build. Where the plan and the build
disagree, the build wins and the file is corrected — and the correction is recorded in the
changelog rather than quietly overwritten.
Status vocabulary fixed and used consistently: DONE (built, and a named
check passes) · BUILT (code exists and imports, no end-to-end run) ·
IN FLIGHT · PLANNED ·
BLOCKED · CUT ·
NOT VERIFIED.
This log uses the same vocabulary, deliberately, so the two can be read together.
The Magieva wordmark was decoded to PNG — 2000×572, bright-on-pure-black — and placed in the
panel's upper-left nav at 17:49. It is rendered with mix-blend-mode: screen so the black plate drops
out on a dark bar. That is the verified-working treatment, and it is the same treatment used in this
log's own nav.
The panel header then reads Magieva · Cinematographer — Director's Panel, which is the challenge surface
the user asked for: a customised ComfyUI Higgsfield Opensource Challenge panel with the Magieva mark in the
upper-left.
The Director's Panel with the Magieva logo in the nav — the verified-good capture, 1585×2027, taken 17:50 PT.
comfyui-higgsfield-cinema-oss.html and .pdf at 17:51 — the narrative
dossier built on the analysis layer. app/README.md at 17:52, including the corrected install path
(the pack root is app/, not the repo root) and an “Honest limitations” section that names
the size_importance approximation and the unverified graphs.
EVIDENCEthe narrative dossier
Build logIN FLIGHT
This file starts
The ask: record the build historically, while doing it — on screen, with screenshots,
video, code, everything except secret keys — and put every open item that needs a human judgement
on screen with a description of the choice, rather than asking in chat.
So this log is written from the artefacts and the file mtimes, not from memory, and the
open items are tracked in the operator's own call sheet, which is not published. Nothing in this log is a
reconstruction.
EVIDENCEbuildlog/index.html
Build log · CaptureDONE
Rather than describe the panel, we drove it
buildlog/tools/capture_clip.mjs launches headless Chrome, speaks the DevTools
Protocol, drives the real sliders and buttons, and captures real paints. No screenshot in this log is synthetic.
131 frames at 1600×1000 → assets/panel-clip.mp4 — 10.9 s, H.264,
362 KB, silent, no voice-over, unscripted.
Five stills: default FAIL, headroom FAIL, CONVERGED, NOT CONVERGED, plus region crops of the
continuity strip, the Node I/O accordion and the rule panel.
The NOT CONVERGED state was found for real. The adversary reported it reachable in only
3 of 54 button-only states and noted the demo script never says how to get there. We searched the
9 button-only combinations and found it: subject height 1.20 m + closeup +
high angle → NOT converged · 24 iter, overall FAIL, reliability RED. That is
now a captured screenshot and a reproducible recipe, not a claim. (The residual line this entry first carried was
wrong — see the 18:34 entry below.)
The continuity strip contradicts the demo script. The panel's default strip reads
30_degree FAIL (18 deg) on cut 1→2 and PASS (55 deg) on cut 2→3.
02-DEMO-SCRIPT.md:95 says “changed 53°” and omits the failing cut. That mismatch is still open.
What this clip is not: it is not the submission demo video. It is silent, unscripted and 10.9
seconds. The submission video (02-DEMO-SCRIPT.md, target 1:48) is a separate artifact:
RECORDED at 88.66 s, 1920×1080, with audio, and
published as the v0.1.1-demo release asset.
# Reproducing the NOT CONVERGED state — the recipe the demo script never gave.
# Panel: http://127.0.0.1:8802/ (python3 -m http.server 8802 --directory app/web)
#
# 1. Click "Reset".
# 2. Drag "Subject height" to 1.20 m (#in-sheight, min of its range)
# 3. Click "closeup" in the SHOT segmented control (#seg-shot, button 0)
# 4. Click "high" in the ANGLE segmented control (#seg-angle, button 2)
# 5. Click "Auto-Frame".
#
# Result, captured 2026-10-29 17:55 PT (assets/still-04-not-converged.png):
#
# solver badge : "NOT converged - 24 iter"
# overall : FAIL
# reliability : RED
# residual : rule_of_thirds=-0.335 shot_size=0.280 lead_room=-0.335
# headroom : 0.612
#
# The adversary (ADVERSARIAL-REVIEW.md §12 attempt 22) reported this state reachable in
# only 3 of 54 button-only states. Searching the 9 button-only combinations, this is the
# one that produces it: shot=closeup, angle=high, subject height=1.20 m.
# The default state converges, so a presenter following 02-DEMO-SCRIPT.md:79-80 literally
# would never reach the beat the script says is the credibility beat.
NOT CONVERGED, captured for real — subject height 1.20 m + closeup + high angle. NOT converged · 24 iter, OVERALL FAIL, RELIABILITY RED. The adversary said this state was reachable in 3 of 54 button-only states; this is the recipe.
EVIDENCEbuildlog/tools/capture_clip.mjs
Build log · EvidenceDONE
Every headline number re-run first-hand
These were run by buildlog at 17:58 PT, not quoted from PLAN.md or from the
Lead. That distinction is the whole point of the honesty ledger — a number that has been independently
reproduced is a different kind of claim from a number that has been copied.
All four reproduce exactly. The loader replay is the fifth, and it is the one that produced a new finding during the loader replay.
$ cd higgsfield-comfyui-os
# [1] unit tests (was 69 at 17:58; test_boundaries.py added 31 more at 18:10)
$ (cd app && PYTHONDONTWRITEBYTECODE=1 /tmp/cine-venv/bin/python -m pytest tests/ -q -p no:cacheprovider)
........................................................................ [ 72%]
............................ [100%]
100 passed in 0.05s
# [2] boundary mutation gate — exit 0 means every mutation was caught
$ PYTHONDONTWRITEBYTECODE=1 /tmp/cine-venv/bin/python verification/mutate_boundaries.py
thirds tol 0.12 -> 0.13 CAUGHT 1 failed in 0.02s
centred tol 0.12 -> 0.5 CAUGHT 1 failed, 2 passed in 0.02s
in_frame |ex|<=1 -> <=2 CAUGHT 1 failed, 3 passed in 0.02s
level <1 -> <2 CAUGHT 1 failed, 5 passed in 0.02s
lead_room -0.05 -> -0.04 CAUGHT 1 failed, 21 passed in 0.03s
thirds line 1/3 -> 1/4 CAUGHT 1 failed in 0.08s
MIN_DISTANCE 0.3 -> 0.31 CAUGHT 1 failed, 25 passed in 0.07s
MIN_ANGLE_CHANGE 30 -> 29 CAUGHT 1 failed, 27 passed in 0.04s
OVERALL_PASS_WEIGHT 0.8 -> 0.5 CAUGHT 1 failed, 23 passed in 0.03s
9/9 mutations caught by tests/test_boundaries.py
EXIT=0
# [3] differential vs the Go reference
$ PYTHONDONTWRITEBYTECODE=1 /tmp/cine-venv/bin/python verification/differential_check.py verification/go-reference-dump.json
differential checks run: 289
ALL FIXTURES AGREE with the original Go implementation.
# [4] panel parity (Python engine <-> JS mirror)
$ PYTHONDONTWRITEBYTECODE=1 /tmp/cine-venv/bin/python app/web/parity_check.py
[3/3] Python engine vs JS (live)
framing 12/12 OK
continuity 4/4 OK
reliability 7/7 OK
autoframe 5/5 OK
RESULT: PASS - 28 fixtures, 0 mismatches
# [5] workflow graph structure
$ PYTHONDONTWRITEBYTECODE=1 /tmp/cine-venv/bin/python app/workflows/validate_graphs.py
PASS reshoot_ltx_crossview.json (35 nodes, 49 links)
PASS reshoot_wan22_fun_control.json (25 nodes, 33 links)
All structural checks passed. This does NOT mean the graphs run: they have never been executed.
EVIDENCEre-run 2026-10-29 17:58 PT
Build log · Adversarial replayDONE
The boundary gate says 9/9. The adversary named 12. I replayed all 12.
The Lead added tests/test_boundaries.py (31 tests, suite 69 → 100) and
verification/mutate_boundaries.py, which reports 9/9 mutations caught, exit 0. That is a
real gate and it is a real improvement. But it exercises 9 mutations of its own choosing, and the
adversary named 12 constants. A gate that picks its own exam is not a proof about the exam it
didn't sit.
So I replayed the adversary's exact 12 against the current full suite, from a clean copy, one mutation at a
time. 7 caught. 5 missed. All five leave 100 passed.
Mutation
Verdict
Why it survives
medium shot_size lo 0.4 → 0.41
MISSED
the fixture probes 0.41 — true under both bounds
closeup 0.7 → 0.71
MISSED
same: probes exactly the mutated value
wide 0.15 → 0.16
MISSED
same
axis_side sign flip
MISSED
the test asserts relative same-side/crossed; a global flip preserves it
ang_delta > 180 → > 179
MISSED
probes 180 and 181 — identical under both
These are not hypothetical. shot_size_ok(0.405, "medium") returns True under both the
original and the mutated bound. ang_delta(0, 179.5) is 179.5 originally and
180.5 mutated — the latter violating the function's own documented [0,180] contract.
A global axis_side sign flip leaves every same-side/crossed assertion green.
The honest number is 7 of 12, not “closed.” To its credit,
BOUNDARY-EVIDENCE.md predicted this exact failure mode and already concedes these are
assertion-pinned, not mutation-proven. This replay is the number that caveat was missing. That gap is still open.
# buildlog's INDEPENDENT replay of the adversary's exact 12 unconstrained constants
# against the CURRENT full suite. The Lead's gate exercises 9 mutations of its own
# choosing; this replays the 12 the adversary actually named.
#
# $ PYTHONDONTWRITEBYTECODE=1 /tmp/cine-venv/bin/python /tmp/buildlog-adversary-12.py
#
adversary mutation verdict detail
------------------------------------------------------------------------------------
1 medium shot_size lo 0.4 -> 0.41 MISSED 100 passed in 0.24s
2 closeup shot_size 0.7 -> 0.71 MISSED 100 passed in 0.16s
3 wide shot_size lo 0.15 -> 0.16 MISSED 100 passed in 0.16s
4 thirds tol 0.12 -> 0.13 CAUGHT 1 failed, 99 passed in 0.31s
5 lead_room -0.05 -> -0.04 CAUGHT 1 failed, 99 passed in 0.22s
6 level <1 -> <2 CAUGHT 1 failed, 99 passed in 0.37s
7 MIN_DISTANCE 0.3 -> 0.31 CAUGHT 1 failed, 99 passed in 0.08s
8 in_frame |ex|<=1 -> <=2 CAUGHT 1 failed, 99 passed in 0.21s
9 thirds line 1/3 -> 1/4 CAUGHT 2 failed, 98 passed in 0.27s
10 thirds centre tol 0.12 -> 0.5 CAUGHT 1 failed, 99 passed in 0.62s
11 continuity axis_side sign flip MISSED 100 passed in 0.15s
12 ang_delta >180 -> >179 MISSED 100 passed in 0.10s
------------------------------------------------------------------------------------
7/12 of the adversary's 12 are now caught by the full suite
STILL UNCONSTRAINED:
- 1 medium shot_size lo 0.4 -> 0.41
- 2 closeup shot_size 0.7 -> 0.71
- 3 wide shot_size lo 0.15 -> 0.16
- 11 continuity axis_side sign flip
- 12 ang_delta >180 -> >179
EXIT=1
# WHY the five are blind — each is a fixture that LOOKS like a boundary test:
#
# #1-#3 the shot_size parametrisation probes exactly the mutated value.
# ("medium", 0.41, True) passes under BOTH the original 0.4 bound and the
# mutated 0.41 bound, so it cannot discriminate. A probe at 0.405 would.
# #11 test_axis_side_sign_is_stable asserts RELATIVE stability (same side vs
# crossed), which a global sign flip preserves. It needs an absolute
# assertion: axis_side(...) == 1.0 for a known-side camera.
# #12 the test probes 180 and 181, which give identical results under both
# 'if d > 180' and 'if d > 179'. The discriminating input is d in (179,180):
# ang_delta(0, 179.5) is 179.5 originally and 180.5 mutated — the latter
# violates the documented [0,180] contract.
#
# BOUNDARY-EVIDENCE.md predicted exactly this failure mode in its own words:
# "a fixture that looks like it tests a boundary can still be blind to the
# mutation it was written for. Only running the mutation shows it."
# Its "Does not" section is also honest: it says the shot_size edges, the axis
# sign and the ang_delta wrap are "assertion-pinned, not mutation-proven".
# This replay is what turns that caveat into a number: 7 of 12.
EVIDENCEbuildlog replay of the adversary's 12, 18:33 PT
Build log · DefectDONE
The demo script's residual line does not match the panel
While re-verifying the NOT CONVERGED recipe first-hand I read the panel's own text and compared it
to 02-DEMO-SCRIPT.md:90. They disagree.
Did not converge in 24 iterations. Still failing: headroom=0.612, shot_size=0.28
Both are real, and they are different lists. The engine emits only failures with
weight ≥ 0.8 (autoframe.py:184-186, OVERALL_PASS_WEIGHT = 0.8). For
closeup, rule_of_thirds and lead_room do fail at -0.335 — but
they carry weight 0.4 and are therefore excluded, while headroom (weight 1.0)
is included. The script lists three rules; the screen lists two, and they are not the same two. The
0.280 vs 0.28 difference is :g dropping the trailing zero.
This is a small defect with a large blast radius: it is the one line in the demo script a judge could check
against the screen in the same shot. Reported to the Lead — the file is not mine to edit. Suggested
replacement: residual `headroom=0.612 shot_size=0.28` (weight ≥ 0.8 only; `rule_of_thirds` and
`lead_room` also fail at -0.335 but sit below the threshold).
# The demo script's residual line does not match what the panel renders.
#
# 02-DEMO-SCRIPT.md:90 says:
# residual `rule_of_thirds=-0.335 shot_size=0.280 lead_room=-0.335`
#
# The panel, driven by that exact recipe, renders (captured 18:34 PT):
# Did not converge in 24 iterations. Still failing: headroom=0.612, shot_size=0.28
#
# Both lists are real; they are different lists. The engine emits only failures
# whose weight clears OVERALL_PASS_WEIGHT:
#
# residual = [f"{s.rule}={s.value:g}" for s in rep.failures() if s.weight >= 0.8]
#
# For shot_type="closeup" the weights are:
# in_frame 1.0 · rule_of_thirds 1.0 · headroom 1.0 · shot_size 1.0
# lead_room 0.4 · negative_space 0.3 · camera_angle 0.3 · level 0.5
#
# So rule_of_thirds and lead_room DO fail (the rule panel shows -0.335 for both)
# but at weight 0.4 they are excluded from the residual. headroom (weight 1.0)
# fails and IS included. The script quotes the rule-panel values and calls them
# the residual.
#
# Two smaller mismatches in the same line:
# * shot_size=0.280 in the script vs 0.28 on screen — the engine formats with
# :g, which drops the trailing zero.
# * the script lists three rules; the panel lists two.
#
# The script also says "read the number off the panel, do not retype it" — which
# is the right instruction, and is exactly what this line breaks.
The panel's own residual line — headroom=0.612, shot_size=0.28. Two rules, not the three the demo script lists, and not the same two.
The first feedback surface was a dead end. Now it is a wire.
The first version of the feedback panel recorded a click to
localStorage and offered a copy-to-clipboard summary. It looked like a feedback channel. It was not
one. Nothing was listening, and the page had no way to know that — which is the worst kind of broken: it
reports success.
So the wire is now real, and it is proven end to end:
buildlog/tools/decision_listener.mjs — a zero-dependency HTTP listener bound to
127.0.0.1 only. POST /decision validates the payload, appends it to
decisions.jsonl, then delivers it into a live DSH session and returns the real exit code and
the agent's real reply. 200 on success, 502 on failure. Deliveries are serialised through a promise
chain, one at a time.
The page polls GET /health and renders a live listening / not listening pill. It
cannot claim to be sending when nothing is there. That was the original sin, and this is the fix for it.
Proven with a marker: BUILDLOG-DELIVERY-PROOF-1793324063 was delivered into a session, the agent
replied DELIVERED and referenced its own prior turn, and the marker is present in the session's
persisted transcript as a real user turn. Through the HTTP path:
{"ok":true,"recorded":true,"delivered":true,"exitCode":0}.
Two gotchas worth writing down, both found the hard way. The --session-id value
must match the session directory name exactly — the store holds both session-<uuid>
and bare <uuid> forms, and the bare form fails with
session "<uuid>" does not exist. And sessions are scoped to a project directory: running from
the wrong cwd fails with was recorded in "<A>", not "<B>", exit 1. The listener decodes the
project directory out of the store path and spawns with the right cwd.
What is still open: which session id the queue should target. It defaults to
the operator's session and was tested against a throwaway scratch session. That is an open item, not a bug.
The layout choice is the interesting part. The contents of app/ were
published as the repo root, so the pack root is the repo root and the standard install works
unchanged:
That is not cosmetic. It is the direct consequence of the __init__.py defect recorded earlier in
this log: ComfyUI's init_external_custom_nodes does a non-recursive
os.listdir over custom_nodes/ and then loads
<pack_root>/__init__.py. If the checkout keeps the pack one level down, the loader fails with
FileNotFoundError and no useful message. Publishing app/'s contents as the root removes
that failure mode for every user, rather than documenting around it.
It required rewriting the layout docs: __init__.py's docstring, the README.md install
blockquote, and 19app/web/… path references now read
web/…. The docstring no longer says “the pack root is app/”; it says
“this repository is the pack root” and keeps the non-recursive-os.listdir explanation
intact.
Acceptance test, re-run first-hand against a fresh anonymous clone — no auth header,
replicating the loader's non-recursive os.listdir:
Repo page, raw README.md and raw LICENSE all return 200 anonymously.
What stayed private:00-BREAKDOWN.md, 01-SPEC.md,
analysis/* and verification/go-reference-dump.json. The known gaps are disclosed in the
public README rather than by publishing the internal review.
D4 closed: 8 of 819, and two published numbers retracted
D4 is decided and the numbers are now settled. The recipe
Reset → subject height 1.20 m → closeup → high → Auto-Frame gives
NOT converged · 24 iter, overall FAIL, reliability RED, residual
headroom=0.612, shot_size=0.28.
A full sweep of the panel's real slider range — 1.20–2.10 m step 0.01 × 3 shot types
× 3 angles = 819 states — gives 8 non-converged, all
closeup + high, heights 1.20 → 1.27. Python and the panel's own JS agree on all
8 states, residual for residual.
Two wrong numbers were published during this build and are retracted here.
Claim
Why it was wrong
54 of 1,377
The sweep went below the slider's 1.20 m minimum, so it
counted states the UI cannot reach. The panel's height input is min="1.2".
7 of 819, residual shot_size=0.346
Built the intent by hand with
side:'center', center:true. The panel's DEFAULTS are
comp:'thirds-left' and Reset restores them, so the real recipe produces
side:'left', center:false, azimuth_deg:12 — a different intent, a different answer.
Both retractions are written into 02-DEMO-SCRIPT.md. The number to quote is
8 of 819.
The structural point survived all three drafts, which is why it is worth keeping: the residual
line is shorter than the rule panel because the engine emits only failures whose weight clears
OVERALL_PASS_WEIGHT (if s.weight >= 0.8, autoframe.py:184). For
closeup, rule_of_thirds and lead_room do fail but carry weight 0.4 and are
excluded; headroom carries weight 1.0 and is included. The panel shows 9 rules; the residual shows 2.
By design — and exactly why a presenter reading the rule panel aloud will misquote the residual.
Every image below is a real Chrome paint of the running director's panel, captured by
buildlog/tools/capture_clip.mjs over the DevTools Protocol at 17:54–17:57 PT. Nothing
is a mock-up.
Director's panel — real screen capture10.9 s · 1600×1000 · H.264 · 362 KB · silent, no voice-overRECORDED
RECORDED — published as a release asset
The submission demo video
The clip above is not the submission video. It is a silent, unscripted 10.9-second
capture of the panel, recorded to prove the panel works. The submission video is a different artifact:
88.66 seconds, 1920×1080, with an audio track, published as the
v0.1.1-demo release asset rather than inlined here — at 5.76 MB it would have
dominated this page.
Where it is: the v0.1.1-demo release on the public repo. The file is
5,756,822 bytes, sha256
d39893cbca95094faf7f7ad5625107c77a14744d76772f87962dc913ea641426.
Measured, not assumed:ffprobe reports a video stream
h264 1920×1080 of 88.560000 s and an audio stream
aac 48000 Hz 2 ch of 88.663000 s; the container duration is
88.663000 s. The download was re-hashed after transfer and matched the published digest.
Script:higgsfield-comfyui-os/02-DEMO-SCRIPT.md — target
1:48, hard limit 2:00 (the form rejects longer). The delivered cut
is 1:28.66, inside both.
Honest caveat: the video demonstrates the panel and the framing rules. It does
not show a diffusion model rendering a shot — no model was called to produce
it. The graphs it walks through are structurally validated; see the execution status below.
Default state.OVERALL FAIL, headroom 0.849
against the medium band [0.10, 0.45]. The panel opens on something to fix.Pushed in. Distance driven down to 1.2 m — headroom 0.597,
still outside the band, with the correction lever shown in metres.After Auto-Frame.CONVERGED · 3 iter,
OVERALL PASS, headroom 0.375 now inside the band. Python uses 1–3
iterations where the Go reference always runs 24.NOT CONVERGED — captured for real. Subject height 1.20 m + closeup +
high angle → NOT converged · 24 iter, OVERALL FAIL,
RELIABILITY RED. The adversary said 3 of 54 button-only states reach this; this is the
recipe.Continuity strip. Cut 1→2: 180_axis PASS,
30_degree FAIL (18 deg). Cut 2→3: 180_axis FAIL — axis crossed,
30_degree PASS (55 deg). The script says 53° and omits the failing cut.The rule panel. Nine framing rules with live values, weights, targets and
corrections. lead_room (weight 0.4) may stay red while the overall badge reads PASS
— that is the spec, and the panel shows it rather than hiding it.Node I/O. The exact JSON the ComfyUI nodes emit, rendered by the same
engine the differential check verifies.The NOT CONVERGED recipe, reproduced. Reset → subject height 1.20 m
→ closeup → high angle → Auto-Frame. Re-verified first-hand at 18:34 PT: the recorded
recipe reproduces exactly.The residual line, as the panel actually renders it.headroom=0.612, shot_size=0.28 — two rules, not the three
02-DEMO-SCRIPT.md:90 lists, and not the same two. Only failures with
weight ≥ 0.8 are emitted.
Reproduce it yourself
The commands, and what they printed.
Run by buildlog at 17:58 PT, first-hand. These are not
quoted from PLAN.md and not copied from the Lead — a number that has been independently reproduced
is a different kind of claim from a number that has been copied.
$ cd higgsfield-comfyui-os
# [1] unit tests (was 69 at 17:58; test_boundaries.py added 31 more at 18:10)
$ (cd app && PYTHONDONTWRITEBYTECODE=1 /tmp/cine-venv/bin/python -m pytest tests/ -q -p no:cacheprovider)
........................................................................ [ 72%]
............................ [100%]
100 passed in 0.05s
# [2] boundary mutation gate — exit 0 means every mutation was caught
$ PYTHONDONTWRITEBYTECODE=1 /tmp/cine-venv/bin/python verification/mutate_boundaries.py
thirds tol 0.12 -> 0.13 CAUGHT 1 failed in 0.02s
centred tol 0.12 -> 0.5 CAUGHT 1 failed, 2 passed in 0.02s
in_frame |ex|<=1 -> <=2 CAUGHT 1 failed, 3 passed in 0.02s
level <1 -> <2 CAUGHT 1 failed, 5 passed in 0.02s
lead_room -0.05 -> -0.04 CAUGHT 1 failed, 21 passed in 0.03s
thirds line 1/3 -> 1/4 CAUGHT 1 failed in 0.08s
MIN_DISTANCE 0.3 -> 0.31 CAUGHT 1 failed, 25 passed in 0.07s
MIN_ANGLE_CHANGE 30 -> 29 CAUGHT 1 failed, 27 passed in 0.04s
OVERALL_PASS_WEIGHT 0.8 -> 0.5 CAUGHT 1 failed, 23 passed in 0.03s
9/9 mutations caught by tests/test_boundaries.py
EXIT=0
# [3] differential vs the Go reference
$ PYTHONDONTWRITEBYTECODE=1 /tmp/cine-venv/bin/python verification/differential_check.py verification/go-reference-dump.json
differential checks run: 289
ALL FIXTURES AGREE with the original Go implementation.
# [4] panel parity (Python engine <-> JS mirror)
$ PYTHONDONTWRITEBYTECODE=1 /tmp/cine-venv/bin/python app/web/parity_check.py
[3/3] Python engine vs JS (live)
framing 12/12 OK
continuity 4/4 OK
reliability 7/7 OK
autoframe 5/5 OK
RESULT: PASS - 28 fixtures, 0 mismatches
# [5] workflow graph structure
$ PYTHONDONTWRITEBYTECODE=1 /tmp/cine-venv/bin/python app/workflows/validate_graphs.py
PASS reshoot_ltx_crossview.json (35 nodes, 49 links)
PASS reshoot_wan22_fun_control.json (25 nodes, 33 links)
All structural checks passed. This does NOT mean the graphs run: they have never been executed.
The loader replay — both layouts
The CRITICAL fix is real, and it is conditional. Replayed by
buildlog at 17:58 and again at 17:59 PT against the corrected shim.
The demo script asks for this state but never says how to reach
it. Here it is, as a reproducible step.
# Reproducing the NOT CONVERGED state — the recipe the demo script never gave.
# Panel: http://127.0.0.1:8802/ (python3 -m http.server 8802 --directory app/web)
#
# 1. Click "Reset".
# 2. Drag "Subject height" to 1.20 m (#in-sheight, min of its range)
# 3. Click "closeup" in the SHOT segmented control (#seg-shot, button 0)
# 4. Click "high" in the ANGLE segmented control (#seg-angle, button 2)
# 5. Click "Auto-Frame".
#
# Result, captured 2026-10-29 17:55 PT (assets/still-04-not-converged.png):
#
# solver badge : "NOT converged - 24 iter"
# overall : FAIL
# reliability : RED
# residual : rule_of_thirds=-0.335 shot_size=0.280 lead_room=-0.335
# headroom : 0.612
#
# The adversary (ADVERSARIAL-REVIEW.md §12 attempt 22) reported this state reachable in
# only 3 of 54 button-only states. Searching the 9 button-only combinations, this is the
# one that produces it: shot=closeup, angle=high, subject height=1.20 m.
# The default state converges, so a presenter following 02-DEMO-SCRIPT.md:79-80 literally
# would never reach the beat the script says is the credibility beat.
The honesty ledger
Verified vs not verified.
This is the section that makes the rest of the log worth reading. A claim is
verified only when a named check reproduces it. Everything else is stated as a gap.
Verified — a named check passes
Claim
Check
Result
100 unit tests pass
cd app && /tmp/cine-venv/bin/python -m pytest tests/ -q
100 passed in 0.05s — re-run by buildlog 18:33 PT (was 69 before test_boundaries.py)
The boundary mutation gate catches every mutation it tries
verification/mutate_boundaries.py
9/9 CAUGHT, exit 0 — re-run by buildlog 18:33 PT. Scope caveat: the gate tries 9 mutations of its own choosing; the adversary named 12. See the gap row below.
289 differential checks agree with the Go reference
No Higgsfield in filenames or app strings; no MiniMax H3; no closed API; no partner nodes; no network calls in the engine
The Magieva logo renders on a dark bar
mix-blend-mode: screen on a bright-on-black PNG
Verified in the panel nav (17:49) and in this log's own nav
Not verified — stated as a gap
Claim
Status
Why
5 of the adversary's 12 unconstrained constants are still unconstrained
7 OF 12 CAUGHT
I replayed the adversary's exact 12 against the current full suite. Caught: thirds tolerance, lead_room, level, MIN_DISTANCE, in_frame, the thirds line, the thirds centre tolerance. Still missed: medium shot_size lo 0.4→0.41, closeup 0.7→0.71, wide 0.15→0.16, the axis_side sign flip, and ang_delta >180→>179 — all five leave 100 passed. BOUNDARY-EVIDENCE.md already concedes these are assertion-pinned, not mutation-proven.
The demo script's residual line matches the panel
MISMATCH
Script says rule_of_thirds=-0.335 shot_size=0.280 lead_room=-0.335; the panel renders headroom=0.612, shot_size=0.28. The engine emits only failures with weight ≥ 0.8, so rule_of_thirds and lead_room (weight 0.4) are excluded. Reported to the Lead; the file is not mine to edit.
The workflow graphs run on a live ComfyUI
NOT VERIFIED
No instance has been run. Structural validation is not execution.
LTX 2.3 + CrossView-Warp output quality
NOT VERIFIED
No render has been produced.
The pack runs on ComfyUI Cloud
NOT VERIFIED
Phase 7 is PLANNED and BLOCKED on a comfy.org login — open item D2.
The reliability envelope (±45° azimuth)
NOT MEASURED HERE
A documented model property from CrossView-Warp's published trained range, not independently measured.
The competition's judging criteria
NOT PUBLISHED
No rubric exists to verify against. The plan optimises for legibility, not for a guessed rubric.
The Higgsfield cease-and-desist
REPORTED, NOT VERIFIED
Two search syntheses report it; no primary source obtained. We act on it anyway.
Provenance of go-reference-dump.json
NOT VERIFIED
No Magieva Go checkout was available to the adversary, so a stale or fabricated dump would not have been detected by any check we ran. This is the largest hole in the chain.
12 engine constants are constrained by any check
IN FLIGHT
The differential check, the unit suite and the parity check all miss the same class of defect. Open item D5.
eyeline (rule 12 of 14) is differentially verified
IN FLIGHT
It is emitted only when a scene has ≥2 characters; both extras fixtures have one. Never compared, and not disclosed as unverified in verification/README.md.
“Value-for-value” reproduction of the Go reference
IN FLIGHT
False for the skipped fields, which do diverge: scene correction (em dash vs hyphen) and continuity detail (°/≥/— vs deg/>=/-).
The submission demo video
DONE
Recorded and published as the v0.1.1-demo release asset: 88.66 s, 1920×1080, with audio, 5,756,822 bytes. The clip in this log is a separate 10.9 s silent panel capture.
The public repo is published
DONE
Published at github.com/nathaniel-magieva/ComfyUI-Cinematographer under Apache-2.0.
Submitted before 2026-10-30 09:00 PT
NOT VERIFIED
No submission was filed. The deadline passed; the form was still open when last checked at 2026-10-30 09:56 PT, but nothing was sent. This is not a claim that a submission happened.
Read this before you trust anything above
The two headline numbers both measure the wrong thing.289 / ALL FIXTURES AGREE and
69 passed both measure value fidelity. Neither measures threshold
fidelity. Twelve engine constants — including the entire medium-shot size band and the entire
rule-of-thirds tolerance — can be wrong while both print green. The adversary demonstrated this by
mutation. That is the single most important open item in the build.
Nothing has been executed end to end. The workflow graphs have never
been run. No render exists. The pack has never loaded in a real ComfyUI. The strongest install evidence
is a faithful replay of ComfyUI's loader against a clean copy — which is good evidence and is not
the same as running it.
The largest hole in the chain. The differential check compares our port
against go-reference-dump.json. No Magieva Go checkout was available to the adversary, so
if that dump were stale or fabricated, none of the 289 checks would detect it. The dump's
provenance is NOT VERIFIED.
And the thing we deliberately do not claim: the solver does not
“converge” in general. The Go reference runs exactly 24 iterations with no convergence test
and no early exit. Our port adds early exit and a converged flag, and says so.
converged=True also does not mean the frame is good — 24 converged frames still carry
a failing non-weighted rule, and a “converged” frame can have the subject's feet off-frame.
That is correct per spec and it is stated here rather than discovered by a judge.
The paper trail
Every document behind this page.
This page is the narrative. These are the primary sources it is built from —
the plan, the analysis, the verification evidence and the submission paperwork, rendered as HTML and
sitting beside this file. Where this page cites a document by name, the name is a link.
The plan and the build record
PLAN.mdThe plan of record. Status vocabulary, the phase list, and the decision log this page is built from.
00-BREAKDOWN.mdThe original breakdown: what the competition actually is, and what we decided to build instead.
01-SPEC.mdThe frozen spec. Written before the engine, not after it.