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()