Appendix A: how to run it
The code is dtp/ one directory up (a uv project, import dtp) – a library, with no command line. One picture, one route, one subproject at work/<name>/; one stage per stepNN-<stage>/, with one main.py in it. The conventions are all in AGENTS.md one level up.
One main.py per step
mkdir -p work/pic/phase1-layers/step03-segment
cp dtp/templates/segment/main.py work/pic/phase1-layers/step03-segment/ # eight templates: segment lineart complete lines draft paint gauge trace
$EDITOR work/pic/phase1-layers/step03-segment/main.py # edit the two blocks of constants at the top: upstream and knobs
uv run python work/pic/phase1-layers/step03-segment/main.py
Every template’s head is the same:
HERE = Path(__file__).resolve().parent # this step
ROOT = HERE.parents[1] # work/<name>
SRC = ROOT / "input/origin.png"
cfg = table.load(ROOT / "parts.py")
# ---- upstream (edit these; None where there is none)
SEG = ROOT / "phase1-layers" / "step01-segment"
# ---- knobs
SCALE = 4
main() is this stage’s wiring: read the upstream artifacts (dtp.io), call the library’s pure functions, write the artifacts, and write the table of numbers with io.write_readme(). What there is not: no “take the latest upstream by default” (the upstream is a constant), no --k=v (the knobs are constants), no step numberer (a person runs mkdir), and no “retire” (a person runs mv ... -legacy-wrong-1).
The routes: layered is segment -> lineart -> segment (with lines as walls) -> complete -> lines -> draft (which a person looks at) -> paint -> gauge; one-shot is segment -> trace. Each step is in “Ten minutes”.
The environment scripts, dtp/tools/
uv run python dtp/tools/setup_harness.py build the Krita harness (once; changing the library needs no rerun)
uv run python dtp/tools/selfcheck.py 31 self-checks against a real Krita
uv run python dtp/tools/comfy.py start ComfyUI (127.0.0.1:8188); stop it with fuser -k 8188/tcp
uv run python dtp/tools/probe.py [name] run one capability probe; with no name, list them all (appendix C)
uv run python dtp/tools/gauge.py <final.png> [--src source] measure one picture offhand (normalized to 2508px, anchored to the source)
uv run python dtp/tools/gui.py <step dir> replay that step's Krita job on a real desktop (it reads the step's job.json)
uv run pytest the library's unit tests
The constants at the top of each main.py
Two blocks: upstream (which steps this one reads – paths relative to the subproject root, None where there is none) and knobs. The main text describes what each one does; these are their names.
| stage | upstream | knobs |
|---|---|---|
segment | WALLS (a lineart step, whose kept lines become walls), ASSIGN_FROM (a segment step whose ASSIGN is inherited) | SCALE, ALPHA_MIN, VOTE (vote_thr, split_below, grow_tol, grow_sep, grow_seed, grow_dist), SMOOTH, EPS, EXPAND |
lineart | SEG, OVERRIDES | KINDS, SOURCE, CT, AIK, BOX |
complete | SEG, SEETHROUGH_FROM (a step whose model output is reused; None runs the model fresh) | ST (resolution, steps, seed, group_offload), MERGE (margin, smooth, feather, black_lum, shape_expand, eps, ct_smooth) |
lines | SEG, COMP, LINEART (comparison only; None skips it), OVERRIDES | SOURCE, CT, AIK, DROP_MADEUP, DROP_MADEUP_LEN |
draft | SEG, COMP, LINES | SCALE, SHAPE (smooth, eps, expand, fit_err, fit_sigma), SIL_EXPAND, JOB (bg, sil), RUN_JOB |
paint | SEG, COMP, DRAFT | SCALE, T_SOFT, LINE_DROP, CHECKPOINTS, RUN_JOB |
gauge | PAINT (a paint or trace step), SEG, REF_PAINT, REF_FINAL | DIST |
trace | SEG | SCALE, PLAN (expand, smooth, eps, detail_t), RUN_JOB, GAUGE_AFTER |
The knobs are constants at the top of main.py (each template lists what used to be run()’s keyword arguments one per constant, with comments); change them and run again and that is a new run, with this run’s parameters recorded at the head of its README. Each step’s table of knobs is in its own chapter: segment (ch. 1), lineart (ch. 2), completion (ch. 3), per-layer lineart (ch. 4), the draft (ch. 5), painting (ch. 6), the criteria (ch. 7) and one-shot (ch. 8). A knob needs evidence that it took effect – once, not one knob in the whole chain ever reached the code (ch. 17, case 38); now a knob is in the very file being run, and the README holds its value.
Machine-level settings live in dtp.toml (optional) or DTP_*: comfy_dir, comfy_url, harness_home and seethrough_dir. Apart from dtp.settings and the Krita process boundary (dtp.jobs.run reading DTP_JOB), the library reads no environment variables; the multiplier and the seed are constants in each main.py.
Experiments
Trying an idea without changing the library: open a step and write its main.py however you like. The conventions: copy the plan files from a baseline step, change the plan for one group only, use RUN_JOB = False for the 1x simulated preview alone (which takes seconds), then RUN_JOB = True for a full painting, and finally a gauge step whose PAINT points at it and whose REF_PAINT points at the baseline, comparing on the same rulers. When it works it sinks into dtp/ as a pure function with tests, and the main.py stays in the step as the record (rule 3 of AGENTS.md).
Running a long job in the background
The last section of “Ten minutes”: in the background, with absolute paths, logging to disk; kill by PID, by port or with pkill -x, never pkill -f.