15fb4422dc
The dust brush filled its hole with a blur of the hole's own rim — the light beside it, carried in. Right for a speck on a wall, and visibly thin over anything with structure: a stroke across a shirt came back as the average of a shirt. The fill is now a learned one. engine/inpaint.ts runs MI-GAN (andraniksargsyan/migan, the same `migan_pipeline_v2.onnx` inpaint-web ships) through onnxruntime-web on the visitor's own machine: 27MB of weights in public/models, WebGPU where the graph fits the provider, the processor otherwise, one warm-up pass so the first repair is not the one that pays for the kernels. It is handed the masked crop and nothing else — the model's mask polarity is 255 KEEP / 0 HOLE, the opposite of the obvious guess, and the other way round repaints the frame and leaves the hole (measured). Step 6e of the renderer cuts the crop at the resolution the model will actually run at (512 on its longest side — the graph is fully convolutional and would take a whole frame, and ten seconds over one), builds the mask from the brush's own circles, and draws the answer into the fill surface at its own place in the box. The composite, the feathered mask and the untouched frame around it are unchanged, so nothing outside the mask moves — and when the model cannot be had the blurred fill is still built first and still stands. The preview gets this for free: renderPhoto was already async and the preview was already debounced. Node probe: the model + polarity + read/write path run; heal-check.mjs keeps measuring the fallback and now asserts the wiring and the polarity. Browser: engine/inpaint.ts in a real Chromium returns a patch (hole moves, kept pixels within 0.02 of a level), and the built app renders a 42-spot stroke in 4.7s on single-threaded wasm with no fallback warning — 1.15s and the warning when the model is taken away.