From 49a633316627942491600a41b46acc1944e7e04c Mon Sep 17 00:00:00 2001 From: Sylpheed port agent Date: Mon, 31 Aug 2026 06:47:21 +0000 Subject: [PATCH] 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. --- docs/port/BLOCKED.md | 8 ++++++ docs/port/DECISIONS.md | 56 ++++++++++++++++++++++++++++++++++++- port/scripts/screen_view.gd | 13 +++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/docs/port/BLOCKED.md b/docs/port/BLOCKED.md index e2426389..12373f42 100644 --- a/docs/port/BLOCKED.md +++ b/docs/port/BLOCKED.md @@ -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?** diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index 3ddf34f8..a399f75b 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -9,7 +9,7 @@ dies, which is what this file is for. -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) ## 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. diff --git a/port/scripts/screen_view.gd b/port/scripts/screen_view.gd index c5b95ef2..ab252406 100644 --- a/port/scripts/screen_view.gd +++ b/port/scripts/screen_view.gd @@ -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()