entry: farmer environment / farmer scenevar · no chroma key anywhere on this branch
Depth from a flat plate, and a night that can be tweened
Environments take a different road entirely. There is no magenta, no grid, no slicing. A plate is cut into layers along bounding boxes the agent chose by looking at it, the holes left behind are filled, and the result is checked by recompositing it and diffing against the original. The lighting branch does something more unusual: one generated night plate yields both the baked variant and a pair of maps that reproduce it at any strength.
-
05a
Cutting the plate
farmer/layers.py:302The input is a plate PNG and a JSON list of named regions. Every region gets a full-plate-resolution RGBA layer, cropped to its bbox plus a four-pixel margin, named
{z:02d}-{name}.pngso the directory listing sorts into draw order. The union of all cut regions becomes one hole, and that hole is filled once, in the base plate.Figure 11 — the plane factory. Layer geometry, z values and parallax factors are read from scenes/lab/layers/planes.jsonas committed. Note that those entries carry norefinekey, whichcut_planes()always writes today — the committed manifest predates that parameter.The lab scene's committed planes, from layers/planes.json Plane z parallax bbox (x, y, w, h) stored layer whiteboard-presenter2 0.45 462, 380, 285, 390 293 × 398 telescope-desk2 0.45 1070, 565, 300, 225 308 × 233 gpu-racks3 0.6 695, 380, 405, 390 413 × 398 students-desks4 0.8 175, 595, 610, 215 618 × 223 railing5 1.0 278, 728, 1095, 95 1103 × 103 Every stored layer is exactly eight pixels wider and taller than its bbox: the crop margin is
FEATHER_PX * 2 = 4on each side, so the feathered ramp is not clipped off. The manifest records the crop origin separately from the bbox, which is whatcomposite_check()uses to put each layer back.A deliberate portability choiceplanes.jsonstores the source plate as a path relative to the working directory, or just the filename when it sits outside — never an absolute machine path. The consequence is stated in the error message:composite_check()raisessource plate … not found — pass source= explicitly (planes.json stores a portable, not absolute, path)
, and the CLI always passes it.“The inpainted base plate behind large cut regions is a smooth blur, not a plausible reconstruction — fine when hidden behind planes, visible if a plane moves far.”
README.md, honest limitations -
adopt scene → farmer scenevar lab night
-
05b
One generation, two products
farmer/scenes.py:32The variant pass is an edit-with-reference generation against the canonical day plate, carrying four keep-clauses verbatim in the prompt. What makes it more than a second image is what happens afterwards: the day and night plates are divided pixel by pixel into a multiply map and a screen map, which together reproduce the night plate — and, at intermediate strengths, every dusk between them.
Figure 12 — one generation, a baked plate and a rig. The blur that makes the maps tweenable is also what costs fidelity: the module says so directly — exact reproduction is traded for that smoothness and measured by the caller
. The caller does measure it, and the numbers are stored in the scene's identity.The keep-clauses
Every scene adopted into the registry carries a style contract whose default keep-clauses go into the variant prompt verbatim (
registry.py:168–173):- clause 1
- keep the exact camera angle, framing, and canvas composition
- clause 2
- keep every wall, window, floor plane, and piece of furniture in its exact position and shape
- clause 3
- keep all object silhouettes, proportions, and outlines pixel-registered to the original
- clause 4
- change only the lighting, palette, and mood described
Three variant descriptions ship as presets in
VARIANT_DELTAS—night,duskanddawn. Any other variant name falls back tothe scene as its <name> variant
unless--deltasupplies the description.Why this is worth the arithmeticA baked night plate is one asset. A multiply map and a screen map are a rig: the same pair drives a sunset that animates over thirty seconds, a lightning flash, or a lamp being switched off in a room — all by moving a single scalar, with no further generation. The 50% column in the strip is not a generated dusk. It is arithmetic between two images that already existed.