Skip to content

pythontk v0.9.22

Latest

Choose a tag to compare

@m3trik m3trik released this 17 Aug 03:55
  • 2026-08-16 — process_batch no longer runs the output-template post-pass when the retry leaves failures. The new still_failed early return is correct as a failure signal, but it is also consumer-visible: on a PARTIALLY failed batch the workflow post-pass now produces no packed/converted siblings at all, where it previously ran over whatever succeeded. Recorded here because the behaviour change was not covered by the entry that introduced the new error reporting.

  • 2026-08-16 — The compositor could pick a CONTENT colour as the background and silently destroy the maps it reported as fixed. _solid_background ranked corner colours by how many corners they touch and broke ties with max(), which returns the first maximum — i.e. scan order. A padding-off export routinely runs an island into the corners, so a large island can hold two corners and tie the true background; whichever the scan reached first won. Losing that coin-flip inverts every mask: the composite fills the islands instead of the background, and because the wrong colour is then RECORDED, the second pass "succeeds" through the known-bg shortcut and process_batch returns RETRIED — success — over destroyed maps. Candidates are now ranked by the area they actually cover, which cannot tie that way; every existing gate (>=2 corners pooled per layer, _BG_MIN_SHARE of every layer) is unchanged. Reproduced end to end before fixing: two 16x16 flat Roughness layers with islands run to the UV edge (A cols 0-5, B cols 10-15) tie 4 corners each; the island colour won, retry_failed filled the ISLANDS, and AB_Roughness.png came out entirely background with process_batch returning RETRIED. It also contaminated the batch — _seed_masks ORs masks across types, so the inverted mask drove the union to full coverage and degraded a normal Base_Color pair to last-layer-wins, erasing one object's island. Note this was a regression in FAILURE MODE too: the pre-change code left self.masks empty and returned MASK_FAILURE, a loud failure the user could act on. Pinned by three tests — a unit-level tie (the shipped corner-tolerance case mirrored with the content strip on the LEFT so it is scanned first) plus the two end-to-end cases above; all three fail on the old tie-break and pass on the new one. The remaining half of this area (the known-bg retry has no self-check, so a wrong recorded colour still cannot be caught) is logged in .claude/BACKLOG.md. Suite: 3208 tests, 3193 passed, 0 failed.

  • 2026-08-16 — Scoped cleanup primitives: TaskFactory.stage_deferred_context(key, cm) and CoreUtils.teardown_guard(logger, what) (core_utils/task_factory.py, core_utils/_core_utils.py). stage_deferred_context enters a context manager now and stages its __exit__ as the keyed, LIFO, exception-isolated deferred restore — the bridge that lets a snapshot -> mutate -> restore scope be written ONCE (a plain with for scripts) and handed to the exporter when the write must still see the mutation; a contextlib.ExitStack composes several under one key. teardown_guard is the one teardown policy: a restore that raises is logged, never re-raised (never masks the body's error, never silently swallowed). Verified: test_task_factory 24, test_core_utils 8 green.

  • 2026-08-16 — MapCompositor: masks are the union over every map type, a solid background survives islands in the corners, 16-bit sources no longer load as solid white, and edge-to-edge sources get one diagnosis (core_utils/engines/textures/map_compositor.py). Live-reproduced on a two-set Painter export (padding off): every combined map came out ~85% default background. Three stacked defects. (1) Painter writes Height as I;16 and the compositor's implicit .convert("RGBA") / get_background hit Pillow's I;16 -> L/RGBA clip — a mid-grey (~32767) height map read as all-255. Layers in I / I;16* / F are now reduced once at batch entry through ImgUtils.convert_i_to_l (÷257 rescale), retiring the mode-I -> .convert("RGB") special case (the same clip). (2) Masks were seeded from ONE type — whichever composited first with four identical corners — and keyed off that colour. That was Height, whose mid-grey is both its bg AND its undisplaced surface, so the masks covered 5%/12% of islands that are really 51%/39%; meanwhile Base_Color's genuine black bg was never detected because islands touch two corners. _seed_masks now takes every type: alpha > 0 for transparent bgs, pixel != bg for opaque solid ones (_solid_background: majority of corners across all layers + >=10% dominance), OR-combined per layer — content that is flat in one map is textured in another. Masks are seeded lazily in the retry phase (a clean batch never pays), and layers whose masks overlap >5% of their union get a warning naming the cause (object baked into both sets / shared UV space / bg that is content) since the composite there is last-wins. (3) The second composite pass re-probed corners on the filled layers, so a correctly-masked island in a corner made the retried type fail silently (its return value was dropped); retry_failed now records the fill colour per type and _composite_type honours it, and leftovers are reported and fail the batch. When no type has a detectable bg at all — fully dilated exports — the run reports that once, naming the types, instead of "Composite failed" per layer. 11 failing-first / rewritten tests; test_map_compositor 67/67; end-to-end fidelity on the real set: output == source inside each layer's mask (98.5-100% outside the shared-object overlap).

  • 2026-08-16 — StepToggle gains a stale policy and its ramp starts at the ideal (core_utils/step_toggle.py). stale="home" (default, on/off toggles) keeps the old behavior — a press after the timeout returns home; stale="restart" (an action whose home is only an undo, e.g. framing) begins a fresh cycle at step 1 instead, so the key always acts after a pause and home stays reachable only by completing the cycle inside the timeout. Set per instance or per press (advance(stale=...)); unknown policies raise. began_cycle is now true on any stale press that lands on step 1 (the point to re-snapshot the payload). StepToggle.scales defaults spread to 0.0: the first step is exactly 1.0 however long the cycle (scales(2) -> [1.0, 1.45]) — a softened start read as the key undershooting in both DCC frame macros; spread > 0 still opts in. 5 tests added, 1 rewritten; test_step_toggle 30/30.