port: free the canvas items I leaked, and refute the blend measurement's reach sentence

The RenderingServer rewrite leaked 5 CanvasItem RIDs per run -- exactly the number
of paint-order runs on the main menu. Items created through the server are not
owned by the node; a child Node2D would have been collected for me, and the reason
for not using one is the reason this had to be paid for. Freed in _exit_tree.

Nothing failed. verify-capture was green on every screen and the pictures were
right; the leak line sat next to "N ObjectDB instances were leaked at exit", which
BLOCKED.md records as engine-side and not ours after releasing every reference the
port owns moved the count not at all. A new leak line beside a known-benign one is
close to perfect camouflage. What distinguished it was the number: 5 is a count of
my bands.

Refutation, and it lands. ui-blend-mode-measured.md closes "every element on the
two screens the port ships is in the table except the two above and pteff10".
Checked against my export, counting the summary table's prose rows as coverage:
exactly right for main_menu, and on EXTRAS it misses four -- ptframe4, pteff21,
pteff22, pteff23. Five, not one.

They are not an arbitrary four. They are precisely the elements the port now
measures as the worst on that screen, ptframe4 at 16.19x the frame mean. Every
measured row still stands and the port has transcribed all of them; what is wrong
is the reach, and it is the difference between "one loose end" and the open ask in
BLOCKED.md, which now says so.

Secondary, offered as such: "every button" in the summary row is a class
generalisation in a document that warns its reader to take the table as per-element
facts. No button appears in the EXTRAS draw log at all. Very likely right, nothing
of mine depends on it.
This commit is contained in:
Sylpheed port agent
2026-08-31 06:47:21 +00:00
parent 0ca6d254f8
commit 49a6333166
3 changed files with 76 additions and 1 deletions

View File

@@ -179,6 +179,14 @@ indistinguishable from a measured one in a month.
different pose rather than a re-read: they may simply not have been drawing in the
frames that were captured.
🔴 **And the measurement's own reach sentence does not say so.** It reads *"every
element on the two screens the port ships is in the table except the two above and
`pteff10`"*. Checked element by element, counting the summary table's prose rows as
coverage: that is **exactly right for `main_menu`** and misses **four** on EXTRAS —
these four. A reader of that page would take the coverage for complete but for one
unidentifiable quad. On EXTRAS a quarter of what the port draws is unmeasured, and
it is the quarter that is visibly wrong.
## ✅ ANSWERED SAME DAY, and the answer is a negative — HANDOFF `5a7f34d`, port `HEAD` `6af06bd`
**Asked: what blend mode do `ptframe1` and `ptframe2` use on the main menu?**

View File

@@ -9,7 +9,7 @@ dies, which is what this file is for.
<!-- INDEX: generated by tools/port/index-decisions -- do not hand-edit -->
333 sections. Search this before re-deriving anything.
334 sections. Search this before re-deriving anything.
* [P0 — the exporter, 2026-08-28](#p0--the-exporter-2026-08-28)
* [P1 — Godot draws the screen, 2026-08-28](#p1--godot-draws-the-screen-2026-08-28)
@@ -344,6 +344,7 @@ dies, which is what this file is for.
* [The blend is measured, so the port draws it — main_menu 13.21 → 10.67](#the-blend-is-measured-so-the-port-draws-it--main_menu-1321--1067)
* [🔴 Refuted: my "no fully-opaque pixel" sharpener](#refuted-my-no-fully-opaque-pixel-sharpener)
* [The sweeps: a measured blend, a corroborated identification, and a confound in my own evidence](#the-sweeps-a-measured-blend-a-corroborated-identification-and-a-confound-in-my-own-evidence)
* [A leak I introduced, and a reach sentence that understates its own gap by four elements](#a-leak-i-introduced-and-a-reach-sentence-that-understates-its-own-gap-by-four-elements)
<!-- /INDEX -->
## P0 — the exporter, 2026-08-28
@@ -16238,3 +16239,56 @@ menu", which is exactly the question `loop_leaf_why` left open and would have
contradicted the pixels for no reason. **A quad parked off-screen at x=1521 is
still a draw call.** The log settles the *blend*; it does not settle *visibility*,
and those two came in the same artefact.
## A leak I introduced, and a reach sentence that understates its own gap by four elements
### 🔴 The `RenderingServer` rewrite leaked five canvas items per run
Every run printed `5 RIDs of type "CanvasItem" were leaked` — exactly the number of
paint-order runs on the main menu. **Canvas items created through `RenderingServer`
are not owned by the node**; a child `Node2D` would have been collected for me, and
the reason for not using one (`_band`) is also the reason this had to be paid for.
Freed in `_exit_tree`.
📌 It was found by looking, not by anything failing: `verify-capture` was green
across every screen, the pictures were right, and the leak line sat in a log
alongside `N ObjectDB instances were leaked at exit` — which `BLOCKED.md` records
as **engine-side and not ours**, investigated at `91ada14`, where releasing every
reference the port owns moved the count *not at all*. A new leak line arriving next
to a known-benign leak line is close to the best possible camouflage. The
distinguishing fact was the number: **5**, which is a count of my bands and not of
anything the engine owns.
### 🔴 Refutation: the measurement's reach is right for one screen and wrong for the other
`ui-blend-mode-measured.md` closes with: *"Every element on the two screens the
port ships is in the table except the two above and `pteff10`, which did not appear
as an identifiable quad."*
Checked element by element against my own export — counting an element as covered
if it appears in the per-draw log **or** in the summary table's prose rows (which
name `pteff05`, "every button" and "both rotated sweep strips"):
| screen | in neither |
|---|---|
| `main_menu` | `pteff10` — ✅ exactly as claimed |
| **`extras`** | `pteff10`, **`ptframe4`, `pteff21`, `pteff22`, `pteff23`** |
🔴 **Five, not one.** The sentence is accurate for the main menu and understates the
EXTRAS gap by four elements — and they are not arbitrary four. **They are precisely
the elements the port now measures as the worst on that screen**: `ptframe4` at
16.19× the frame mean, with `pteff21`/`22`/`23` immediately behind it.
⚠️ **This is a reach statement, not a result** — every measured row stands, and the
port has already transcribed all of them. But a reader of that page would conclude
the coverage is complete but for one unidentifiable quad, and on EXTRAS it is not:
**a quarter of what the port draws there is unmeasured, and it is the quarter that
is visibly wrong.** That is the difference between "one loose end" and "the open
ask in `BLOCKED.md`".
📌 A smaller observation, offered as one: *"every button"* in the summary row is a
**class** generalisation, in a document whose own warning is to read the table as
per-element facts. No button appears in the EXTRAS draw log at all — the
generalisation comes from `ptbtn01f` on the main menu. It is very likely right, and
the port depends on nothing that would break if it were not; I raise it only
because it is the same move the document tells its reader not to make.

View File

@@ -582,6 +582,19 @@ func _add_quad(ci: RID, tex: Texture2D, rect: Rect2, colour: Color) -> void:
## pose. A capture taken with `--script=wait` would have shown that as a plausible
## wrong answer rather than as an error. These items are filled synchronously
## inside `_draw()` instead, so there is no second node to keep in step.
## 🔴 CANVAS ITEMS MADE THROUGH `RenderingServer` ARE NOT OWNED BY THE NODE, and
## the first version of this file did not free them: Godot printed
## `5 RIDs of type "CanvasItem" were leaked` on every exit -- exactly the number of
## paint-order runs on the main menu. A node-owned child would have been collected
## for me; the reason for using the server directly is in `_band`, and this is its
## price. `_exit_tree` rather than `NOTIFICATION_PREDELETE` because the items are
## parented to this node's canvas item, which goes when the node leaves the tree.
func _exit_tree() -> void:
for ci: RID in _bands:
RenderingServer.free_rid(ci)
_bands.clear()
func _band(i: int, additive: bool) -> RID:
while _bands.size() <= i:
var ci := RenderingServer.canvas_item_create()