# The instrument gained two environment reads — 2026-09-21

**What this note is about, for a reader who scrolled straight to it.** `renderlib.py` is the
render instrument of the 30-series/24 GB ladder: the ComfyUI client, the timing, the tracing,
the capping and the fencing that every render leg of this ladder runs. It is **byte-identical
across every leg by a fence** (`tests/provenance_test.py`), and that fence is the whole basis on
which one board's seconds may be divided by another's — "same harness" is a digest, not a
sentence.

**On 2026-09-21 that file changed for the first time**, in all five copies at once, to add a
fifth leg: an NVIDIA GeForce RTX 5090 Laptop GPU (`bench/render-5090-laptop-2026-09-21/`).

**The change, in full.** Two module constants stopped being literals:

```python
COMFY_ROOT   = os.environ.get("RENDER_BENCH_COMFY_ROOT",   "/workshop/ComfyUI")
COMFY_PYTHON = os.environ.get("RENDER_BENCH_COMFY_PYTHON", f"{COMFY_ROOT}/venv/bin/python")
```

**Why it was needed.** The laptop leg has to point at a *second*, pinned ComfyUI checkout — the
render tables pin ComfyUI 0.21.1 and that box's live checkout is 0.11.1 — and that box's
virtualenv is `.venv`, not `venv`. Neither could be expressed with a literal.

**What it does NOT change, which is the load-bearing claim.** *With both variables unset the
behaviour is byte-for-byte what it was.* Every leg before this one ran with them unset and
still does; no sibling leg's scripts set them. The defaults are the two strings that were
previously hardcoded, character for character.

**The digests, so the claim above is checkable rather than assertable.**

| | sha256 of `renderlib.py` |
|---|---|
| before, and in every leg from 2026-09-16 to 2026-09-20 | `7873957253e59807bc3e235d0e825e1ddf5adf4b5a3f63bec7045af5adaecfa5` |
| after, and in all five legs from 2026-09-21 | `38c30d711a709be6356606da1ff9416abe3f5b72ce425e78cd7f6a8605045831` |

`tests/renderlib_test.py` changed in the same commit and in the same five places, because the
fence's own comment says an unchanged instrument keeps an unchanged pin set — so a changed one
gets a changed pin set, everywhere. The new class is
`TheComfyInstallIsNamedByTheEnvironment`, and its first test is the one that matters: the
defaults are exactly the literals they replaced.

**The alternative that was considered and rejected:** `ln -s .venv venv` in the pinned
checkout. It works, it costs nothing today, and it leaves the next box to rediscover the same
trap from a stack trace instead of from a parameter.

**This leg has no prior figures.** It is the leg the change was made for, and every number it produces will have been measured under the new digest.

---

# Addendum — the cap is read from two fields now (2026-09-21, later the same day)

**What this addendum is about, for a reader who scrolled straight to it.** The same file, the same
fence, a second change on the same date — and this one is a defect fix rather than a new capability.
`renderlib.py` is the render instrument of the 30-series/24 GB ladder, byte-identical across all
five legs by `tests/provenance_test.py`. **Window: 2026-09-21T17:28Z (the failure) to
2026-09-21T18:0xZ (the fix), UTC.** Against the earlier block on this page: that one ADDED two
environment reads and changed no reading; this one changes HOW A CAP IS READ, and it is additive in
the direction that matters — `power.limit` is still preferred, so every figure any leg measured
before today came off the same code path it always did.

**What happened.** At 2026-09-21T17:28Z the laptop leg's three render arms — `inventory`, `r1`,
`r4` — each died inside `assert_cap_agrees`:

```
renderlib.CapRefused: the cards report different power limits:
                      {'GPU-edff232c-7dbf-2bac-07fb-921a7c9eecff': None}
```

on a box with **one** card. A card cannot disagree with itself. `measured_caps()` read exactly one
nvidia-smi field, `power.limit`; NVML reports a power-MANAGEMENT limit only once one has been SET,
and on that board none ever had been — nor can be: `sudo nvidia-smi -pl 175` answers *"Changing
power management limit is not supported for GPU: 00000000:01:00.0"*. So the field read `[N/A]`, the
parser correctly turned that into `None`, and the agreement test read a single `None` as a
disagreement. The language leg of the same bench had solved this at recon with a two-field reader;
the fix had never reached this file.

**The change, in full.**

* `enforced.power.limit` joins `SMI_FIELDS` and `NUMERIC`, so a row can carry it at all.
* `CAP_FIELD_PREFERRED` / `CAP_FIELD_FALLBACK` and a new `cap_reading(row)` -> `(watts, field)`.
  `power.limit` wins when it answers. That order is a **provenance rule**, not a taste: it is the
  field every earlier leg read and the field an operator's `-pl` paste writes, so a box where both
  answer stays byte-comparable with the 3090, the 3090 Ti and the two 3080s.
* `measured_caps()` reads through it; `measured_cap_fields()` is new and records WHERE each card's
  cap came from; `cap_block()` carries `per_card_field` and `field` into every result file.
* `reduce_trace()` reduces `enforced.power.limit` over the trace beside `power.limit`. On a board
  running NVIDIA Dynamic Boost that limit MOVES while an arm renders, and min/median/max over the
  trace is then the only honest cap cell there is.

**What it does NOT change.** Nothing about a board that answers `power.limit`. The preferred field
is unchanged, the parse of `[N/A]` is unchanged, and a two-card disagreement still refuses — there
is a test for exactly that, so the fix cannot have been bought by widening what counts as agreement.

**The digests, so the claim above is checkable rather than assertable.**

| | sha256 of `renderlib.py` |
|---|---|
| before, and in every leg from 2026-09-16 to 2026-09-20 | `7873957253e59807bc3e235d0e825e1ddf5adf4b5a3f63bec7045af5adaecfa5` |
| after the environment-reads change, 2026-09-21 earlier | `38c30d711a709be6356606da1ff9416abe3f5b72ce425e78cd7f6a8605045831` |
| after this cap-reader change, and in all five legs | `11248d984d550c753cd33a89aae6ce52901fc7a52afac780ac38062e4cdbb6f9` |

`tests/renderlib_test.py` changed in the same commit and in the same five places
(sha256 `d76be8ecc41a2222df113746a4052fdde02d668906460e85a0e8286ea8286fa7`); the new class is
`TheCapIsReadFromTwoFieldsAndTheFieldIsRecorded`, eight tests, and the first of them pins that
`power.limit` still wins.

**The alternative that was considered and rejected:** special-casing the laptop leg — a
`RENDER_BENCH_CAP_FIELD` override that only that leg would set. It would have kept four digests
frozen, and it would have left the next board that answers `[N/A]` to rediscover the same refusal
from a stack trace. A reader that is correct only on the box that wrote it is not a reader.
