The analysis
03 — The differentiator, and the rule-count honesty problem
Scope: what makes the entry novel, how the novelty claim was made falsifiable, and the first of the three honesty problems — the gap between the 38 rules the rulebook documents and the 14 rules the code actually evaluates.
Source root: as 01-premise-and-reaim.md §0.
Method: static reading of the spec, the engine source, the prior teardown, and the recon. The gating-rule arithmetic in §6.4 was recomputed from app/cinematographer/geometry.py during the writing of this file; the command and output are recorded there.
1. The novelty claim, in the form it was frozen
The spec states the claim in a form that can be checked:
"Nothing in the ComfyUI ecosystem evaluates a frame geometrically and returns a correction in metres." —
01-SPEC.md:18
And the constructive half:
"We give you a judgement, not a preset: per rule, a measured value, a pass/fail, and the camera lever that fixes it — plus a solver that moves the camera for you, and a continuity engine that checks the 180° line across cuts." —
01-SPEC.md:27-29
The entry targets the Most Innovative category, which the breakdown defines as "novel custom node packs or uses of ComfyUI" (00-BREAKDOWN.md:28-30).
1.1 Why the claim is phrased as "geometrically"
The word geometrically is doing work. It excludes the perceptual half of the rulebook, which needs a vision model and is not deterministic (01-SPEC.md:306-307). It also makes the claim checkable by arithmetic: if the claim were "our frames look better", no one could falsify it.
The demo script makes the falsifiability the entire proof:
"That's not a model's opinion. That's arithmetic you can check by hand." —
02-DEMO-SCRIPT.md:54-55
2. The neighbours, and what each one gives you
The spec's neighbour table (01-SPEC.md:21-25):
| Existing | What it gives you |
|---|---|
ComfyUI-CameraReference3D | 17 camera-move presets + Plücker export |
WanCameraEmbedding | 9 camera presets |
comfy-reshoot's reliability zone | A hard-coded green/yellow/red indicator |
The recon's fuller inventory is in 01-premise-and-reaim.md §6.1. The one that matters most is CameraReference3D, which the recon calls "The strongest 'Most Innovative' candidate." (analysis/20-competition-brief.md:410).
2.1 The precise distinction
| A preset node | This pack | |
|---|---|---|
| Input | A move name (dolly_in, orbit_left) | A camera pose + subject geometry |
| Output | A trajectory / Plücker embedding | A measured value, a pass/fail, a correction in metres |
| Answers "is this frame good?" | No | Yes |
| Answers "how far do I move?" | No | Yes, in metres |
| Falsifiable by hand? | No | Yes |
| Composes with an arbitrary pose? | Only the presets it ships | Any pose |
| Checks across a cut? | No | Yes (180_axis, 30_degree) |
The distinction is not "ours is better". It is "ours answers a different question". A preset answers where; this answers whether and how much.
2.2 The claim the entry does not make
The submission checklist forbids the unfalsifiable version:
| "Beats Higgsfield's Cinema Studio" | ⛔ unfalsifiable; do not claim it | —
03-SUBMISSION.md:144
The entry never ran Cinema Studio and never produced a render on either side (client-reports/higgsfield-cinema-studio-4.0/analysis/00-SYNTHESIS.md:155). A comparison claim would be unsupported, so it is not made.
3. The 14 rules, enumerated from the code
The engine's rule surface is defined by three constants:
# app/cinematographer/framing.py:42-52
FRAMING_RULES = (
"in_frame", "rule_of_thirds", "headroom", "shot_size", "lead_room",
"negative_space", "camera_angle", "level", "direct_address",
)
# app/cinematographer/continuity.py:31
CONTINUITY_RULES = ("180_axis", "30_degree")
The scene rules are appended by scene_framing_extras() (app/cinematographer/scene.py:155): size_importance (:196), foreground_occlusion (:220), eyeline (:240).
9 + 3 + 2 = 14.
3.1 The nine per-shot rules
Each row's "measured value" is what the rule returns; each "lever" is the correction string the node surfaces. Line numbers are the add(...) call that appends the score.
| # | Rule key | Type | Measurement | Correction lever | Code |
|---|---|---|---|---|---|
| 1 | in_frame | bool | Subject's eye anchor in front of camera and |ndcX|≤1, |ndcY|≤1 | "re-aim/pull back: subject's face is off-frame or behind camera" | framing.py:104-111 |
| 2 | rule_of_thirds | value | Distance from eye anchor ndcX to ±1/3; pass ≤0.12. If intent.center, pass is |ndcX|≤0.12 instead | "offset subject to a third: shift camera x / aim" | framing.py:113-135 |
| 3 | headroom | value | 1 − ndcY(top); band by shot type — closeup [0.05,0.30], medium [0.10,0.45], wide [0.20,0.70] | too tight → "lower the camera target or pull back"; too much → "raise the camera target or push in" | framing.py:137-147 |
| 4 | shot_size | value | (ndcY(top) − ndcY(base)) / 2; closeup ≥0.7, medium 0.4–0.75, wide 0.15–0.45 | "dolly/zoom to hit <type>" | framing.py:149-160 |
| 5 | lead_room | value | −ndcX if subject faces screen-right else +ndcX; pass ≥ −0.05 | "flip composition: leave space where the subject faces" | framing.py:162-171 |
| 6 | negative_space | value | 1 − subArea; always passes (informational) | — | framing.py:173-177 |
| 7 | camera_angle | value | camEyeY − subjectEyeY; class low/high/eye by sign + pitch; always passes (informational) | — | framing.py:179-189 |
| 8 | level | value | |roll − intent.dutch| < 1 | "match the intended camera roll" | framing.py:191-202 |
| 9 | direct_address | value | Angle between subject gaze and camera→subject axis; <8° = down the lens, <25° = near-lens; always passes (informational) | — | framing.py:204-222 |
The spec's table (01-SPEC.md:46-56) matches the code row for row. The headroom band function is geometry.py:357-362; the shot_size classifier is geometry.py:365-371.
3.2 The three scene rules
| # | Rule key | Weight | Measurement | Code |
|---|---|---|---|---|
| 10 | size_importance | 0.4 | The beat's key subject should be the largest element; flags when a minor prop or character out-sizes it | scene.py:196-208 |
| 11 | foreground_occlusion | 0.0 | A nearer element hugging a frame edge that does not cover the face reads as depth / intimacy / voyeurism. Informational | scene.py:220-232 |
| 12 | eyeline | 0.4 | The primary subject should look toward the other (<45°) | scene.py:240-249 |
size_importance carries the entry's most explicit simplification caveat:
"Simplification, stated plainly. The Go original derives entity half-extents from Magieva's full dimension resolver (
ResolveEntityDimensions). This port uses documented role-derived defaults instead, overridable per entity. That changes absolute areas but not the comparison the rules make, which is what matters. Do not read a size_importance ratio from this port as identical to the Go service's." —app/cinematographer/scene.py:15-20
The differential check honours that caveat mechanically: it skips the size_importance value comparison and compares only pass/fail (verification/differential_check.py:241-244).
3.3 The two continuity rules
| # | Rule key | Measurement | Code |
|---|---|---|---|
| 13 | 180_axis | The camera must stay on one side of the action line; a flip between consecutive shots is a break | continuity.py:114-125 |
| 14 | 30_degree | Consecutive shots of the same subject must change the camera angle by ≥30°, or the cut reads as a jump | continuity.py:127-143 |
The threshold is a named constant: MIN_ANGLE_CHANGE_DEG = 30.0 (continuity.py:34).
3.4 The four informational rules
Four of the fourteen always pass. They describe the frame's register rather than judging it:
"Four of them (`
negative_space,camera_angle,direct_address, andforeground_occlusioninscene.py) are *informational* — they always pass and exist to describe the frame's register rather than to judge it." —app/cinematographer/framing.py:7-10`
The README states the same count:
"Four of these are informational — they describe the frame's register (isolated, heroic, down-the-lens) rather than judging it. The other ten carry weights, and a frame passes only when every rule at weight ≥ 0.8 passes." —
app/README.md:82-84
INFORMATIONAL_RULES is declared as a constant for the three per-shot ones (framing.py:55); foreground_occlusion is informational by virtue of its weight=0.0 (scene.py:226).
3.5 The weights and the overall-pass rule
RULE_WEIGHTS (geometry.py:313-346) mirrors the Go source's ruleWeights (geometry.py:312). The overall verdict is:
# app/cinematographer/framing.py:224-227
rep.overall_pass = all(
s.passed for s in rep.scores if s.weight >= OVERALL_PASS_WEIGHT
)
with OVERALL_PASS_WEIGHT = 0.8 (geometry.py:350).
| rule | closeup | medium | wide |
|---|---|---|---|
in_frame | 1 | 1 | 1 |
rule_of_thirds | 1.0 | 0.8 | 0.6 |
headroom | 1.0 | 0.7 | 0.4 |
shot_size | 1.0 | 1.0 | 1.0 |
lead_room | 0.4 | 0.4 | 0.3 |
negative_space | 0.3 | 0.3 | 0.4 |
camera_angle | 0.3 | 0.3 | 0.3 |
level | 0.5 | 0.5 | 0.5 |
(Source: geometry.py:313-346; the spec's table at 01-SPEC.md:62-71 matches.)
4. The rule-count honesty problem
4.1 What the prior report claimed
The prior teardown of Cinema Studio 4.0 — the package this entry's strategy descends from — states the number twice, as a headline:
"The Cinematographer companion is a machine-applicable rulebook of 38 cinematography rules, each tied to its emotional effect and split into deterministic geometry (
EvaluateFramingininternal/contentstudio/cinematography.go— measured value, pass/fail, and the camera lever to fix it) and perceptual vision-model judgement." —client-reports/higgsfield-cinema-studio-4.0/analysis/00-SYNTHESIS.md:173-177
"The Cinematographer gives you 38 rules with measurements and correction levers — judgements." —
client-reports/higgsfield-cinema-studio-4.0/analysis/00-SYNTHESIS.md:185-187
The prior report also describes the growth mechanism:
"A nightly CronJob asks Claude for new rules and appends them — the rulebook grew from 32 to 38 rules in three days." —
client-reports/higgsfield-cinema-studio-4.0/analysis/00-SYNTHESIS.md:180-182
4.2 What the code says
The recon's ContentStudio inventory, which read the Go source, found:
"a deterministic cinematography rulebook with 38 documented rules of which 14 are machine-evaluated in Go" —
analysis/11-contentstudio-inventory.md:7
So both numbers are true of different things:
| Number | What it counts | Where it lives |
|---|---|---|
| 38 | Rules documented in the rulebook | docs/features/cinematographer/cinematography-bible.md:39-89 (38 rows) and cmd/cinematographer-homework/main.go:37-49 (38 embedded baseline names) |
| 14 | Rules machine-evaluated in the Go source | EvaluateFraming (9, cinematography.go:146-224), SceneFramingExtras (3, cinematography_scene.go:101-142), EvaluateContinuity (2, cinematography_continuity.go:72,79) |
The recon's own verdict on the phrase:
"'38 rules' is a documented count, not an enforced count." —
analysis/11-contentstudio-inventory.md:206
The 24-rule gap is the perceptual (PER) half — rules that need a vision model and are not deterministic (01-SPEC.md:306-307).
4.2a A third number: the bible's own implementation column
The recon found a third count, which neither the prior report nor the entry mentions:
"The bible's own
Implcolumn marks 8 ✅" —analysis/11-contentstudio-inventory.md:206, citingcinematography-bible.md:42-73
So the rulebook contains three different counts of itself:
| Count | Source | What it measures |
|---|---|---|
| 38 | The bible's row count and the homework job's embedded baseline | Rules documented |
| 14 | The Go source | Rules machine-evaluated |
| 8 | The bible's own Impl column | Rules the rulebook admits are implemented |
The rulebook's own self-report (8) is lower than the code's actual count (14). The entry quotes 14 — the code count — which is the right one to quote, but it does not mention that the rulebook's own column says 8. A judge who reads the bible would find a number the entry never mentions.
4.2b The nightly-CronJob attribution is false
The prior teardown explains the rulebook's growth with a mechanism:
"A nightly CronJob asks Claude for new rules and appends them — the rulebook grew from 32 to 38 rules in three days." —
client-reports/higgsfield-cinema-studio-4.0/analysis/00-SYNTHESIS.md:180-182
The recon checked this and found the causal attribution false. The CronJob is real and production-scheduled (37 3 * * *, unsuspended, validated at cronjobs.yaml:2696-2767, values-production.yaml:2619-2622, cronjob-dispositions.yaml:95-105). The 32→35→38 growth is real (cinematography-bible.md:91-95; commits 6bc87e97a04 06-29 v1.0, 6ffe7dcc508 06-30 v1.1 +3, 7c3a1140358 06-30 v1.2, 40faedacb88 07-01 v1.3 +3). But the growth was hand-authored, not CronJob output:
| Fact | Evidence |
|---|---|
The job's first commit is 2026-07-01 08:07 (1b33e12bb40) — after the v1.3 commit | analysis/11-contentstudio-inventory.md:209 |
| The job never succeeded until 2026-09-02 | analysis/11-contentstudio-inventory.md:209 |
| Its first successful run added rules 39–41, not 33–38 | cronjob-dispositions.yaml:95-105 |
The embedded baseline has been 38 since the job's first commit (verified across five commits: 1b33e12bb40, dcf7c43739c, d51f8c9fba2, 7a5ab01b6a6, ff6a0e27c11) | analysis/11-contentstudio-inventory.md:209 |
So the rulebook's 32→38 growth is real but was authored by hand in three days; the CronJob's actual contribution to date is 38→41.
This is a fourth correction of the same kind as the "38 rules" correction: the prior report attributed a real outcome to a mechanism that did not produce it. The entry's spec corrected the count (§4.3) but did not correct the mechanism, and the entry's package does not mention it anywhere.
The recon's verdict on the whole claim:
"MIXED: CronJob TRUE; growth timeline TRUE; causal attribution FALSE" —
analysis/11-contentstudio-inventory.md:209
4.2c The PER half has its own defect
The recon also found a defect in the perceptual half that the entry does not mention:
"The PER half (
framing_critic.go) is not billed internally and names a model literal (gpt-4o-vision) that differs from the model sent (gpt-4o)." —analysis/11-contentstudio-inventory.md:390
That is outside the entry's scope — the entry ships DET only (01-SPEC.md:306-307) — but it is relevant to the "24 rules are perceptual" framing: the perceptual half is not merely unevaluated, it is mislabelled in its own source.
4.3 The correction, and why it was made during planning
The spec records the correction and treats it as an asset:
"The internal rulebook documents 38 rules. Only 14 are machine-evaluated in the Go source. We port the 14 and say so. (This honesty is a feature: the prior report's headline claim of '38 rules' does not survive contact with the code, and we will not repeat it.)" —
01-SPEC.md:38-40
The README repeats it with the reasoning:
"Our internal rulebook documents 38 cinematography rules. Only 14 are machine-evaluated. We port the 14 and say so, because a rule you cannot measure is a rule you cannot enforce." —
app/README.md:72-73
That last clause is the argument. The number 38 is not a lie about the rulebook; it is a lie about what the software does. The entry's whole thesis is that a measured value beats an assertion, so claiming 38 unevaluated rules would contradict the thesis.
4.4 Where "fourteen" is enforced
The number is a gate, not a preference. Four independent places enforce it:
| Place | Enforcement |
|---|---|
| Spec non-goals | "Claiming 38 rules, or claiming the solver converges when it does not." (01-SPEC.md:311) |
| Submission checklist | "No claim of '38 rules' anywhere. It is 14." (03-SUBMISSION.md:127) |
| Honesty ledger | "38 cinematography rules" → ⛔ false — 14 are machine-evaluated (03-SUBMISSION.md:141) |
| Demo script | "Do not claim 38 rules. Say fourteen. […] Saying fourteen is a credibility asset, not a weakness." (02-DEMO-SCRIPT.md:123-124) |
And the demo script puts it in the spoken VO:
"Fourteen rules, all measured. Apache-2.0." —
02-DEMO-SCRIPT.md:106
4.5 The residual risk: is 14 itself right?
The entry says 14 because the recon counted 14 machine-evaluated rules in the Go source (analysis/11-contentstudio-inventory.md:7, :206). That count is inherited, not re-derived by the entry.
| Question | Status |
|---|---|
| Are there exactly 14 machine-evaluated rules in the Go source? | NOT VERIFIED by this package. The count comes from the recon's inventory, which names the three functions and their line ranges (cinematography.go:146-224, cinematography_scene.go:101-142, cinematography_continuity.go:72,79). The entry's own engine has exactly 14 rule keys, which is a port count, not a count of the Go source. |
| Are the 24 unevaluated rules really all perceptual? | NOT VERIFIED. The recon says the split is DET vs PER; the entry repeats it. The recon separately found the PER half names a model literal that differs from the model sent (analysis/11-contentstudio-inventory.md:390). |
| Does the nightly CronJob still append rules? | The CronJob is real and scheduled (37 3 * * *, unsuspended). Its first successful run added rules 39–41 on 2026-09-02 (analysis/11-contentstudio-inventory.md:209). Whether it has run since is NOT VERIFIED. |
| Is the rulebook still 38 rules? | NOT VERIFIED, and probably not. The CronJob's first success took it to 41. The prior report's "38" was already stale when it was written. |
The fourth row is the sharpest. A rulebook that grows on a nightly schedule makes any fixed rule count stale. The entry's "14" is stable because it counts code, not documentation — which is exactly why the code count is the honest one to quote. But the entry should not be read as asserting that the rulebook contains 38 rules today; the recon's evidence suggests it contains at least 41.
4.6 The precedent: this failure mode has happened once already, in the same lineage
The rule-count problem is easier to dismiss as a local slip than it deserves to be, because the prior teardown was itself adversarially verified and its verifier found the same class of defect in its own headline:
"29/29 timestamped quotes resolve correctly… But it ships three critical defects and thirteen material ones, and two of the critical ones are in the headline: the executive summary states a contested claim as established fact, and the report's single most quoted number is invented." —
client-reports/higgsfield-cinema-studio-4.0/verification/VERIFICATION-REPORT.md:14-20
The invented number was the credit-cost figure:
"FAIL. The figure is not derivable." — the 1,000–2,000 credit figure requires assuming all 14 generations were 10–30 s 1080p Cinema Studio video —
client-reports/higgsfield-cinema-studio-4.0/verification/needle-checks.md:79-120
The verifier applied 24 corrections — 3 critical, 13 material, 8 minor (client-reports/higgsfield-cinema-studio-4.0/README.md:118-122).
Three instances, one failure mode:
| Report | The real observation | The number that did not survive | Found by |
|---|---|---|---|
| The prior teardown | Generations were run; costs were incurred | "1,000–2,000 credits" — not derivable | Its own adversarial verifier |
| This entry | The rulebook documents 38 rules | "38 rules" — only 14 are evaluated | The recon, during planning |
| This entry | The rulebook grew 32→38 | Attributed to the nightly CronJob — the job did not cause it (§4.2b) | The recon — but the entry never carried the correction into its own documents |
A real outcome with a number attached that does not survive contact with the source. In every case an adversarial pass, not the author, found it.
This cuts both ways for the entry, and both directions are worth stating:
- For it: the entry's decision to correct "38 rules" during planning, before writing code, is exactly the discipline the prior teardown's verifier had to impose after publication. The entry applied the lesson early.
- Against it: the entry applied the lesson to the count and not to the mechanism. The CronJob attribution (§4.2b) is the same error class, produced by the same reading, and it is absent from every entry document. A verification pass finds roughly one headline-number defect per report, and this entry has had exactly one such pass.
5. The continuity differentiator
5.1 The competitor's gap
The prior teardown's finding, which the entry's continuity module quotes:
"Cinema Studio has no continuity enforcement at all, while Higgsfield's own consistency guide concedes 'AI video distortion is not a quality problem. It is a memory problem… each new generation is a fresh context.' Choros answers that deterministically." —
client-reports/higgsfield-cinema-studio-4.0/analysis/00-SYNTHESIS.md:189-191
The entry's continuity.py docstring carries the same quote:
"This is the direct answer to the failure Higgsfield's own consistency guide concedes: 'AI video distortion is not a quality problem. It is a memory problem... each new generation is a fresh context.'" —
app/cinematographer/continuity.py:14-16
5.2 What the engine actually checks
Two rules, both comparing consecutive shots rather than one frame (continuity.py:6-12):
| Rule | Mechanism | Code |
|---|---|---|
180_axis | Compute which side of the subject's facing line (the action line) the camera is on, for each shot; a sign flip between consecutive shots is a break | axis_side() at continuity.py:89-97; comparison at :115-125 |
30_degree | Compute each camera's bearing around the subject; the smallest absolute angle between consecutive bearings must be ≥30° | cam_azimuth_deg() at :84-86, ang_delta() at :100-105, comparison at :127-143 |
The axis test is a sign test, not a threshold test:
# app/cinematographer/continuity.py:115-117
side_a = axis_side(a.base, a.yaw_deg, a.camera.eye)
side_b = axis_side(b.base, b.yaw_deg, b.camera.eye)
axis_pass = side_a == side_b
That is the correct formulation of the 180° rule and it is worth noting: the rule is about consistency of side, not about an angle. A camera can move 170° around the subject and still be on the same side.
5.3 The eyeline split, and why it is honest
Eyeline is a continuity-flavoured rule that lives in scene.py, not continuity.py. The module says why:
"Eyeline is a third continuity rule in the rulebook, but it needs two subjects per shot and lives per-shot in `
scene.py— the Go source says so explicitly (cinematography_continuity.go:57-58), and we keep that split." —app/cinematographer/continuity.py:18-20`
That is a case where the port preserved an awkward upstream design decision rather than tidying it, and documented the reason. It is the right call for a differential-fidelity port: moving eyeline would have made the port diverge from the Go source for no functional gain.
5.4 The demo beat
The demo script's continuity section shows both rules firing (02-DEMO-SCRIPT.md:86-95):
180_axis FAIL — axis crossed
30_degree PASS — changed 53°
The Go reference dump contains a fixture that produces exactly this pattern: continuity_go_test has ('180_axis', True, 1, 2), ('30_degree', False, 1, 2), ('180_axis', False, 2, 3), ('30_degree', True, 2, 3) (verification/go-reference-dump.json, continuity_go_test). So the demo's on-screen values are reproducible from the frozen Go fixture — but the 53° figure in the script is illustrative, not read from that fixture. See 06-unverified-and-method.md §5.
6. The reliability zone: computed, not hard-coded
6.1 The claim
The spec's neighbour table calls comfy-reshoot's reliability indicator "a hard-coded green/yellow/red indicator" (01-SPEC.md:25). The entry's version is computed:
"`
comfy-reshootshows a green/yellow/red reliability indicator. Ours computes it from the view-steering model's training envelope, so the user can see *which axis* left the envelope and by how much." —app/cinematographer/reliability.py:3-5`
6.2 The envelope
Three named constants (reliability.py:21-28):
| Constant | Value | Meaning |
|---|---|---|
GREEN_AZIMUTH_DEG | 45.0 | Within the trained range |
YELLOW_AZIMUTH_DEG | 60.0 | Extrapolating |
YELLOW_ELEVATION_DEG | 30.0 | Elevation limit for green/yellow |
The grading logic (reliability.py:75-103) returns green if both axes are inside, yellow if azimuth is inside 60° and elevation inside 30°, and red otherwise — with a reason string naming the axis that left the envelope (reliability.py:93-102).
6.3 The honesty in the module
The module states that the envelope is a model property, not a measurement:
"The envelope comes from CrossView-Warp (Cseti, Apache-2.0), an LTX 2.3 IC-LoRA whose trained range is documented as green within +/-45 degrees of azimuth." —
app/cinematographer/reliability.py:7-9
And the README repeats the caveat:
"The reliability envelope (±45° azimuth) is CrossView-Warp's documented trained range. It is a model property, not a law of physics." —
app/README.md:134-135
The verification README marks it explicitly unverified:
| The reliability envelope (±45° azimuth) | Documented model property, taken from CrossView-Warp's published trained range. Not independently measured here. | —
verification/README.md:123
6.4 A finding the build did not make explicit: the gating-rule arithmetic
This is the most consequential thing this file surfaces, and it was not stated anywhere in the entry's own documentation.
The overall verdict is computed only over rules with weight >= 0.8 (framing.py:224-227, geometry.py:350). Applying that threshold to RULE_WEIGHTS (geometry.py:313-346) gives the set of rules that can ever fail a frame:
| Shot type | Rules that gate overall_pass |
Count |
|---|---|---|
closeup | in_frame, rule_of_thirds, headroom, shot_size | 4 |
medium | in_frame, rule_of_thirds, shot_size | 3 |
wide | in_frame, shot_size | 2 |
Recomputed during the writing of this file:
$ cd higgsfield-comfyui-os && PYTHONDONTWRITEBYTECODE=1 python3 -c "
import sys; sys.path.insert(0,'app')
from cinematographer.geometry import RULE_WEIGHTS, OVERALL_PASS_WEIGHT
for st,w in RULE_WEIGHTS.items():
gating=[k for k,v in w.items() if v>=OVERALL_PASS_WEIGHT]
print(f'{st:8s} gating rules ({len(gating)}): {gating}')
"
closeup gating rules (4): ['in_frame', 'rule_of_thirds', 'headroom', 'shot_size']
medium gating rules (3): ['in_frame', 'rule_of_thirds', 'shot_size']
wide gating rules (2): ['in_frame', 'shot_size']
The consequence for the demo: headroom has weight 0.7 in a medium shot and 0.4 in a wide shot. It gates the overall verdict only in a closeup.
The demo script's most important 22 seconds is a medium-shot headroom failure:
"Watch. Headroom is 0.83. The medium-shot band is 0.10 to 0.45. So it fails, and it tells me to raise the target or push in. I do that — and it passes." —
02-DEMO-SCRIPT.md:53-55
"Do not rush this section. If the viewer believes only one thing, it must be this." —
02-DEMO-SCRIPT.md:64
The README's headline example is the same one:
"None of them will tell you 'your headroom is 0.83, the medium-shot band is 0.10–0.45, raise the target or push in.'" —
app/README.md:25-26
So the demo's central proof shows a rule-level FAIL that does not change the frame's overall verdict. The panel README documents the general form of this behaviour for lead_room:
"After Auto-Frame the overall badge reads PASS while
lead_room(weight 0.4) may still be red. That is the spec, not a bug: overall pass requires only the rules withweight >= 0.8." —app/web/README.md:201-202
But it does not note that headroom at 0.7 is in the same category, and that the demo's chosen example is therefore a rule that fails without failing the frame.
This is not a defect — the weight table is a faithful port of the Go source (geometry.py:312), and the panel's behaviour is correct. It is a presentation risk: a judge who notices it may read the demo as showing a failure that the product itself does not treat as one. The honest fix is one sentence in the demo script ("headroom is a weighted rule — in a close-up it fails the frame outright; in a medium shot it is advisory"), and it was not written.
The finding is recorded as a defect in the package README.md.
6.5 The second-order finding: the informational rules are 4/14
Combining §3.4 and §6.4: of the 14 rules, four never fail (informational) and eight more can fail but cannot fail the frame in at least one shot type. Only four rules — in_frame, rule_of_thirds, headroom, shot_size — can ever gate the overall verdict, and only in a closeup.
| Category | Rules | Count |
|---|---|---|
| Always pass (informational) | negative_space, camera_angle, direct_address, foreground_occlusion | 4 |
| Can fail, never gate | lead_room, level, size_importance, eyeline | 4 |
| Gate only in some shot types | rule_of_thirds (not wide), headroom (closeup only) | 2 |
| Gate in every shot type | in_frame, shot_size | 2 |
| Cross-cut only | 180_axis, 30_degree | 2 |
"14 rules" is the right number for the rule surface. It is not the number of rules that can fail a frame. The entry never makes that distinction, and it should.
7. What the differentiator claim does not establish
| Not established | Why |
|---|---|
| That the rules are correct cinematography | They are a faithful port of one internal implementation. The differential check proves fidelity, not validity. |
| That the numbers match a real camera | The geometry is a pinhole projection with a documented eye anchor at 0.92 * height (geometry.py:15-16). It is a model, not a measurement. |
| That a judge will find it novel | The novelty argument is a survey of the ecosystem, not a proof of absence. |
| That Cinema Studio's frames are worse | Never compared. Never rendered. (03-SUBMISSION.md:144) |
| That the 14 rules cover what a working DP uses | The rulebook's other 24 rules exist precisely because someone thought they mattered. |
That size_importance's absolute values are right | Explicitly disclaimed (scene.py:15-20); the differential check skips the value (differential_check.py:241-244). |
| That the reliability envelope is correct | It is CrossView-Warp's published trained range, not a measurement (verification/README.md:123). |
8. Decision register — the differentiator phase
| # | Decision | Evidence |
|---|---|---|
| N1 | Claim novelty on evaluation, not on presets | 01-SPEC.md:18, :27-29 |
| N2 | Make the claim falsifiable by arithmetic | 01-SPEC.md:31-32; 02-DEMO-SCRIPT.md:54-55 |
| N3 | Do not claim to beat Cinema Studio | 03-SUBMISSION.md:144 |
| N4 | Say fourteen, not 38 | 01-SPEC.md:38-40; 03-SUBMISSION.md:127, :141 |
| N5 | Ship DET only; exclude PER | 01-SPEC.md:306-307 |
| N6 | Keep the eyeline split where the Go source put it | continuity.py:18-20 |
| N7 | Compute the reliability zone rather than hard-code it | reliability.py:3-5 |
| N8 | State the reliability envelope as a model property | app/README.md:134-135; verification/README.md:123 |
| N9 | Disclaim size_importance's absolute values | scene.py:15-20; differential_check.py:241-244 |
9. Evidence index for this file
| Claim | Primary source |
|---|---|
| The novelty claim | 01-SPEC.md:18, :27-29 |
| The neighbour table | 01-SPEC.md:21-25 |
CameraReference3D is the strongest rival | analysis/20-competition-brief.md:410 |
| The 9 per-shot rules | framing.py:42-52, :102-222 |
| The 3 scene rules | scene.py:155-249 |
| The 2 continuity rules | continuity.py:31, :108-144 |
| The 4 informational rules | framing.py:7-10, :55; scene.py:226 |
| The weights table | geometry.py:313-346 |
OVERALL_PASS_WEIGHT = 0.8 | geometry.py:350 |
| The overall-pass expression | framing.py:224-227 |
| The prior "38 rules" claim | client-reports/higgsfield-cinema-studio-4.0/analysis/00-SYNTHESIS.md:173-177, :185-187 |
| 38 documented / 14 evaluated | analysis/11-contentstudio-inventory.md:7, :206 |
| The 38 rule rows and the 38 embedded baseline names | cinematography-bible.md:39-89; cmd/cinematographer-homework/main.go:37-49 |
| The three Go functions and their line ranges | cinematography.go:146-224; cinematography_scene.go:101-142; cinematography_continuity.go:72,79 |
The bible's own Impl column marks 8 | analysis/11-contentstudio-inventory.md:206; cinematography-bible.md:42-73 |
| The correction | 01-SPEC.md:38-40; app/README.md:72-73 |
| The "fourteen" gates | 01-SPEC.md:311; 03-SUBMISSION.md:127, :141; 02-DEMO-SCRIPT.md:123-124 |
| The nightly CronJob is real and scheduled | analysis/11-contentstudio-inventory.md:209; cronjobs.yaml:2696-2767; values-production.yaml:2619-2622 |
| The CronJob attribution is false | analysis/11-contentstudio-inventory.md:209; cronjob-dispositions.yaml:95-105 |
| The 32→38 growth was hand-authored | cinematography-bible.md:91-95; commits 6bc87e97a04, 6ffe7dcc508, 7c3a1140358, 40faedacb88 |
| The CronJob's first success added 39–41 | analysis/11-contentstudio-inventory.md:209 |
| The PER half's model-literal defect | analysis/11-contentstudio-inventory.md:390 |
| No continuity in Cinema Studio | client-reports/higgsfield-cinema-studio-4.0/analysis/00-SYNTHESIS.md:189-191 |
| The axis test is a sign test | continuity.py:115-117 |
| The eyeline split | continuity.py:18-20 |
| The reliability constants | reliability.py:21-28 |
| The reliability caveat | app/README.md:134-135; verification/README.md:123 |
| The gating-rule arithmetic | Recomputed from geometry.py:313-346, :350; see §6.4 |
| The demo's medium-shot headroom example | 02-DEMO-SCRIPT.md:53-55, :64; app/README.md:25-26 |
The panel's lead_room note | app/web/README.md:201-202 |
The size_importance disclaimer | scene.py:15-20; differential_check.py:241-244 |