port: draw the measured additive blend -- main_menu 13.21 -> 10.67
The Decoder logged RB_BLENDCONTROL0 per draw in Canary on both screens. 0x01010101 is src=ONE dst=ONE, additive. That makes the blend a transcription rather than my proposal, and they withdrew the "any blend you choose is authored" instruction explicitly. Their control is what licenses the change: one pixel shader, 0xE59B2B3DA4AA9008, runs with BOTH blend states on the main menu -- 12 additive draws and 18 alpha-over. The frames and ptbase share a shader; only the blend register differs. authored/rendering.json gains additive_elements per screen. Every id is a measured draw and the reach is written beside it. verify-capture: main_menu 13.21 -> 10.67 (0.06 % -> 0.02 %), extras 13.38 -> 11.43, main menu with ptbtn04 focused 13.82 -> 11.36. Per element, ptframe1 22.72 -> 4.17 and ptframe2 13.09 -> 3.32. Neutrality control, free with the table: publisher_logo 2.17 and developer_logos 3.05 are unchanged to the digit. Those are the screens whose metric is absolute and they carry no additive element, so the rewrite that routed every draw through RenderingServer canvas items did not change the picture. The improvement is the blend, not the plumbing. RenderingServer rather than child Node2Ds because boot.gd calls view.queue_redraw() from nine places and none reaches a child node -- bands would paint the previous pose, which under --script=wait is a plausible wrong capture rather than an error. Runs are recomputed per frame: the additive elements are consecutive on both measured screens, and that is an accident of those two screens. And the change first ran with the material left at its default MIX, moving ptframe1 from 22.72 to 22.69. Nothing errored and a 0.03 move is a plausible negative result. It was caught only because the measurement predicted a large move. Not done: ptframe4 is now the worst element on EXTRAS at 16.19x the frame mean and additive would plainly help it. It is not in the measured table, so it is not in the file. Filed in BLOCKED.md with pteff21/22/23, which are also in no captured draw. Refuted, mine: "neither frame has a fully-opaque pixel" was true and was not the discriminator -- pteff10 has max alpha 130, no opaque pixel, and measures nearly exact. The direction survived; the reason for it did not.
This commit is contained in:
@@ -102,5 +102,65 @@
|
||||
"",
|
||||
"What settles the menu: a direct capture of it, which the Decoder has offered."
|
||||
],
|
||||
"loop_leaf_kind": "measured"
|
||||
"loop_leaf_kind": "measured",
|
||||
"additive_elements": {
|
||||
"main_menu": [
|
||||
"pteff12",
|
||||
"ptframe1",
|
||||
"ptframe2"
|
||||
],
|
||||
"extras": [
|
||||
"pteff20",
|
||||
"ptframe3"
|
||||
]
|
||||
},
|
||||
"additive_elements_why": [
|
||||
"TRANSCRIBED, not authored, and the distinction is the whole entry. The port",
|
||||
"composited every element with straight alpha-over and four came out too dark",
|
||||
"against the capture. I measured from my side that the shortfall scales with",
|
||||
"the BACKGROUND, and that of alpha-over / additive / screen, additive roughly",
|
||||
"halves the error (DECISIONS.md, two-background composite solve, control 0.0000).",
|
||||
"That was a PROPOSAL and stayed one: nothing on the disc selects a blend mode",
|
||||
"(`docs/re/structures/t32-blend-mode-not-on-disc.md`), so choosing one was not",
|
||||
"mine to do.",
|
||||
"",
|
||||
"It is now MEASURED OFF THE RUNNING GAME. The Decoder logged RB_BLENDCONTROL0",
|
||||
"per draw in Canary and drove the game to both screens:",
|
||||
"`docs/re/structures/ui-blend-mode-measured.md`, table in",
|
||||
"`docs/re/data/ui-blend-mode-measured.txt`. 0x01010101 is src=ONE dst=ONE,",
|
||||
"additive. Elements are identified by the PIXEL SIZE of their quad against",
|
||||
"sprite dimensions.",
|
||||
"",
|
||||
"✅ Their control, and it is what makes this a blend result rather than a shader",
|
||||
"result: ONE pixel shader, 0xE59B2B3DA4AA9008, runs with BOTH states on the main",
|
||||
"menu -- 12 draws additive and 18 alpha-over. The frames and `ptbase` share a",
|
||||
"shader. Only the blend register differs.",
|
||||
"",
|
||||
"⚠️ EVERY ID HERE IS A MEASURED DRAW, and the list stops where the measurement",
|
||||
"does. THREE THINGS ARE NOT IN IT:",
|
||||
"",
|
||||
" `ptframe4` -- NOT MEASURED. It never appears in either capture's draw list,",
|
||||
" and the port measures it at 10.46x the frame mean, dark, exactly like its",
|
||||
" three siblings. It is the most tempting entry on this page and it is not",
|
||||
" going in: 'the other three frames are additive' is a RULE, and the Decoder's",
|
||||
" own warning with the table was to take it as per-element facts because which",
|
||||
" field selects the mode is still unknown. A fourth frame added on the strength",
|
||||
" of a pattern would be indistinguishable from a measured one in a month.",
|
||||
"",
|
||||
" The two unidentified additive draws per screen (889.6x1134.0 and",
|
||||
" 1299.2x1303.2) -- these are the rotated sweep strips, whose height the",
|
||||
" Decoder's OWN control reproduces independently. Additive, but the port draws",
|
||||
" them through `draw_leaf_for` and I have not checked that path.",
|
||||
"",
|
||||
" The 819.2x720.0 additive draw, which matches no sprite on either screen.",
|
||||
"",
|
||||
"⚠️ AND NOT PREMULTIPLIED TEXTURES. The measured non-additive state is",
|
||||
"0x07010701 -- src=ONE, which is the PREMULTIPLIED form of alpha-over. It would",
|
||||
"be easy to read 'so the textures are premultiplied and the port's straight",
|
||||
"alpha-over is wrong everywhere'. The Decoder flagged this before I could: src",
|
||||
"being ONE means the fixed-function stage multiplies the SHADER's output by 1,",
|
||||
"and the shader has not been read. The port's alpha-over path measures 0.92-1.31x",
|
||||
"the frame mean on `ptbase`/`pteff05`, so it is not visibly wrong. Nothing here",
|
||||
"changes it."
|
||||
]
|
||||
}
|
||||
|
||||
@@ -147,6 +147,38 @@ HANDOFF.
|
||||
| ~~P1–P7 — the keyframe record layout~~ | ~~adopt the corrected pose/time pairing~~ | — | ✅ **ADOPTED 2026-08-29 by pinning `formats-pin-2026-08-29c`.** This row was wrong twice: it said the change *"cannot be taken yet"* and that it *"reaches the port only when that branch lands on `main`"*. **It arrives when the tag is pinned**, which is what MISSION §2's tagging rule exists for. ⚠️ And the knob I tested first, `SYLPHEED_KF_TIME_SHIFT`, is a **retired partial fix** that left pose 0 untimed — the real correction is the tagged crate's default, with the old reading behind `SYLPHEED_KF_TIME_LEGACY=1`. **The blast radius was far smaller than this row predicted**: under the correction *every pose is timed* (866 keyframes, 0 untimed), so `pose_at`'s synthetic-exit branch became dead code rather than wrong code and nothing needed re-deriving. Oracle: `publisher_logo` 1.00 %→**0.75 %**, `developer_logos` 0.39 %→**0.33 %**, `extras`' differing region collapsing from 736×525 to **398×295 at the sweep position**. 🔴 Open cost: `sylpheed-cli` builds from the workspace crate, so `verify-screen` compares two decoder eras until the tag reaches `main`. Revert to the path dependency then. |
|
||||
| ~~P7 / naming — the four unnamed builds~~ | ~~which locale and variant is each of entries 0, 1, 12, 15?~~ | — | ✅ **answered 2026-08-29** (`docs/re/ui-title-build-map.md`): all four are the loading screen, two variants — plain (7 elements) and dressed (10) — decoded from their own `pgloading_*` element names. ⚠️ **Not adopted as names yet, for two reasons the RE agent gave and one the port found.** Theirs: the executable names exactly two, and *which* bundle takes which name is 🟡 undecided, so `LOADING`/`LOADING2` must not go in an asset path; and locale is 🟡 — the English member of a pair is the one in the first half of `GP_TITLE.p00`, 8/8 structurally but only 3/3 where a capture can check, and the three pairs that matter are the three no capture can check. Mine: **the message gives the bundles as "0/1 and 10/11", which is the `is_build` ordinal, and `authored/screen_names.json` is keyed by PAK ENTRY** — in entry space 10 and 11 are `palogo_sqex` and `palogo_gamearts`, the splashes. See the refutation section in `DECISIONS.md`. |
|
||||
|
||||
## New ask, 2026-08-31 — derived from HANDOFF (today's blend-mode delivery), port `HEAD` `0a054d6`
|
||||
|
||||
**Four elements on EXTRAS are in no draw capture: `ptframe4`, `pteff21`, `pteff22`,
|
||||
`pteff23`. What blend mode do they use?**
|
||||
|
||||
The measured table (`docs/re/data/ui-blend-mode-measured.txt`) named `pteff20` and
|
||||
`ptframe3` additive on EXTRAS, and the port now draws those two that way. **The
|
||||
result is exactly the shape you want and exactly why this ask matters:**
|
||||
|
||||
| EXTRAS element | before | after | signed after |
|
||||
|---|---|---|---|
|
||||
| **`ptframe3`** — measured, applied | 34.80 | **7.97** | **−0.61** |
|
||||
| **`ptframe4`** — *not measured, left alone* | 25.58 | **31.90** | −31.81 |
|
||||
| `pteff21` / `22` / `23` — *not measured* | 10.25 / 10.63 / 10.09 | 14.34 / 13.15 / 12.04 | all negative |
|
||||
|
||||
📌 **Where the blend is measured the element is now near-exact; where it is not,
|
||||
it is the worst thing on the screen.** The four unmeasured ones also got *worse* in
|
||||
absolute terms, which is consistent rather than alarming: their neighbours are now
|
||||
correctly brighter, so an alpha-over deficit that scales with the background scales
|
||||
up with it.
|
||||
|
||||
🔴 **I am not inferring them from the pattern.** `ptframe4` is the third frame on a
|
||||
screen whose other frame is measured additive, and it is dark, and additive would
|
||||
plainly help. That is precisely the argument I must not act on — the Decoder's own
|
||||
warning with the table was to read it as **per-element facts**, because which field
|
||||
selects the mode is still unknown, and a fourth frame added by pattern would be
|
||||
indistinguishable from a measured one in a month.
|
||||
|
||||
⚠️ They appear in **no** captured draw on either screen, so this may need a
|
||||
different pose rather than a re-read: they may simply not have been drawing in the
|
||||
frames that were captured.
|
||||
|
||||
## ✅ 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?**
|
||||
|
||||
@@ -9,7 +9,7 @@ dies, which is what this file is for.
|
||||
|
||||
<!-- INDEX: generated by tools/port/index-decisions -- do not hand-edit -->
|
||||
|
||||
330 sections. Search this before re-deriving anything.
|
||||
332 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)
|
||||
@@ -341,6 +341,8 @@ dies, which is what this file is for.
|
||||
* [The frames generalise, premultiplied alpha is refuted, and the shortfall tracks the background](#the-frames-generalise-premultiplied-alpha-is-refuted-and-the-shortfall-tracks-the-background)
|
||||
* [Which blend? Additive halves the error, on both frames — proposed, not adopted](#which-blend-additive-halves-the-error-on-both-frames--proposed-not-adopted)
|
||||
* [Refutation attempt: the Decoder's kind-0 claim survives, checked from my own data](#refutation-attempt-the-decoders-kind-0-claim-survives-checked-from-my-own-data)
|
||||
* [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)
|
||||
|
||||
<!-- /INDEX -->
|
||||
## P0 — the exporter, 2026-08-28
|
||||
@@ -16085,3 +16087,89 @@ field agree, and the claim is now stronger than when only one side had read it.
|
||||
declared *identically* to `ptbase` (drawn at 1.31× the frame mean) and `pteff05`
|
||||
(0.92×). Same declaration, opposite accuracy — so whatever distinguishes them is
|
||||
not in the field either of us can read.
|
||||
|
||||
## The blend is measured, so the port draws it — main_menu 13.21 → 10.67
|
||||
|
||||
The Decoder took the draw-path route and logged `RB_BLENDCONTROL0` per draw in
|
||||
Canary on both screens. `0x01010101` is `src=ONE dst=ONE`: **additive**. That
|
||||
turns my proposal into a transcription, and they said so explicitly — *"withdraw
|
||||
the instruction I gave you last time; additive is transcribed now, not authored."*
|
||||
|
||||
📌 **Their control is what licenses the change:** one pixel shader,
|
||||
`0xE59B2B3DA4AA9008`, runs with **both** blend states on the main menu — 12 draws
|
||||
additive, 18 alpha-over. The frames and `ptbase` share a shader. **Only the blend
|
||||
register differs**, so this is a blend result and not a shader result.
|
||||
|
||||
Recorded in `authored/rendering.json` as `additive_elements`, per screen, with
|
||||
every id being a measured draw and the reach written next to it.
|
||||
|
||||
### The result, and a neutrality control that came free
|
||||
|
||||
| screen | before | after |
|
||||
|---|---|---|
|
||||
| **`main_menu`** | 13.21 / 0.06 % | **10.67 / 0.02 %** |
|
||||
| **`extras`** | 13.38 / 0.20 % | **11.43 / 0.07 %** |
|
||||
| main menu, focus `ptbtn04` | 13.82 / 0.15 % | 11.36 / 0.11 % |
|
||||
| title / title_plate / title_band | 14.16 / 13.04 / 12.86 | 14.10 / 13.03 / 12.85 |
|
||||
| **`publisher_logo`** | **2.17** | **2.17** |
|
||||
| **`developer_logos`** | **3.05** | **3.05** |
|
||||
|
||||
✅ **The last two rows are the control.** They are the screens `verify-capture`
|
||||
marks *"no free-running element — absolute, means what it says"*, and they have no
|
||||
additive element. They did not move **at all**. The rewrite routed every draw in
|
||||
the project through `RenderingServer` canvas items, so "did the plumbing change
|
||||
the picture?" was a live question; those two rows answer it. The improvement is
|
||||
the blend, not the refactor.
|
||||
|
||||
Per element on `main_menu`: `ptframe1` **22.72 → 4.17** (signed −22.72 → −3.51),
|
||||
`ptframe2` 13.09 → 3.32 (−12.31 → **+1.80**), whole-screen mean 2.40 → 1.55.
|
||||
|
||||
### 🔴 The change ran, produced a number, and the number was wrong by looking right
|
||||
|
||||
First run after wiring it all up: `ptframe1` moved from **22.72 to 22.69**. Bands
|
||||
were created, ordered and assigned correctly; the screen composited exactly as
|
||||
before. I had written `CanvasItemMaterial.new()` and **never set `blend_mode`**, so
|
||||
every band was MIX.
|
||||
|
||||
📌 Nothing errored. A 0.03 move is a *plausible* outcome — I could have written
|
||||
"additive does not help after all, the two-background solve overstated it" and it
|
||||
would have read as a careful negative result. **It was caught only because the
|
||||
measurement predicted a large move and 0.03 is not one.** The comment now sits on
|
||||
that line.
|
||||
|
||||
### Why it is `RenderingServer` and not child nodes
|
||||
|
||||
Godot sets blend mode per **canvas item**, not per draw call. The obvious
|
||||
implementation — a child `Node2D` per band with a `CanvasItemMaterial` — **loses a
|
||||
frame**: `boot.gd` calls `view.queue_redraw()` from nine places and none reaches a
|
||||
child node, so bands would paint the *previous* pose. Under `--script=wait` that
|
||||
surfaces as a plausible wrong capture, not an error. The bands are canvas items
|
||||
filled synchronously inside `_draw()` instead.
|
||||
|
||||
The runs are recomputed every frame rather than cached. The additive elements are
|
||||
consecutive in paint order on both measured screens — **that is an accident of
|
||||
those two screens**, and a cache keyed on "the additive block" would be right today
|
||||
and silently wrong on the first screen that interleaves.
|
||||
|
||||
### ⚠️ What I did not do: `ptframe4`
|
||||
|
||||
`ptframe4` is now the worst element on EXTRAS (31.90, 16.19× the frame mean) and
|
||||
additive would plainly help it. **It is not in the measured table and it is not in
|
||||
the file.** Filed in `BLOCKED.md` with `pteff21`/`22`/`23`, which are also absent
|
||||
from every captured draw. Where the blend is measured the element is near-exact
|
||||
(`ptframe3` signed **−0.61**); where it is not, it is the worst thing on screen.
|
||||
That contrast is the argument for asking rather than inferring.
|
||||
|
||||
## 🔴 Refuted: my "no fully-opaque pixel" sharpener
|
||||
|
||||
I offered, as the thing that distinguished the frames, that **neither frame has a
|
||||
single fully-opaque pixel** against `ptbase`'s 99.1 %. The Decoder refuted it with
|
||||
a census (`docs/re/data/menu-sprite-alpha-census.txt`): **`pteff10` has max alpha
|
||||
130, is 100 % partial, has no opaque pixel either — and I measure it as nearly
|
||||
exact.** `pteff12`, `pteff20` and `pteff21`–`23` likewise.
|
||||
|
||||
✅ **The observation was true and it was not the discriminator.** My *direction*
|
||||
survived — the draw path was the right place to look, and it answered — but the
|
||||
reason I gave for looking there was wrong. Recording it because the conclusion
|
||||
being vindicated is exactly the circumstance in which a bad supporting argument
|
||||
survives unexamined.
|
||||
|
||||
@@ -285,6 +285,7 @@ func _ready() -> void:
|
||||
var rendering: Variant = export_tree.authored("rendering.json")
|
||||
_draw_leaf_for = [] if rendering == null else rendering.get("draw_leaf_for", [])
|
||||
_loop_leaf_screens = [] if rendering == null else rendering.get("loop_leaf_on_screens", [])
|
||||
_additive_elements = {} if rendering == null else rendering.get("additive_elements", {})
|
||||
# `--no-hold` plays a screen's groups PAST their rest instead of clamping each
|
||||
# element at its own `rest.t`. A diagnostic, not a mode: `rest.t` is the last
|
||||
# HOLD keyframe before the exit, not the settled state, and the only way to
|
||||
@@ -312,6 +313,7 @@ func _ready() -> void:
|
||||
view.looping_focus = _looping_for(name)
|
||||
view.loop_phase_units = _loop_phase
|
||||
view.draw_leaf_for = _draw_leaf_for
|
||||
view.additive_elements = _additive_elements
|
||||
view.loop_leaf = _loop_leaf_screens.has(name)
|
||||
view.focused_id = _force_focus
|
||||
if not view.load_screen(export_tree, name):
|
||||
@@ -406,6 +408,8 @@ var _pending: Variant = null
|
||||
var _looping: Dictionary = {}
|
||||
## `authored/rendering.json` `draw_leaf_for`.
|
||||
var _draw_leaf_for: Array = []
|
||||
## `authored/rendering.json` `additive_elements` -- measured off the running game.
|
||||
var _additive_elements: Dictionary = {}
|
||||
## `authored/rendering.json` `loop_leaf_on_screens`.
|
||||
var _loop_leaf_screens: Array = []
|
||||
var _script: PackedStringArray = PackedStringArray()
|
||||
@@ -558,6 +562,7 @@ func _advance() -> void:
|
||||
view.looping_focus = _looping_for(name)
|
||||
view.loop_phase_units = _loop_phase
|
||||
view.draw_leaf_for = _draw_leaf_for
|
||||
view.additive_elements = _additive_elements
|
||||
view.loop_leaf = _loop_leaf_screens.has(name)
|
||||
if not view.load_screen(view.tree, name):
|
||||
push_error(view.tree.error)
|
||||
@@ -917,6 +922,7 @@ func _menu_arrive() -> void:
|
||||
view.looping_focus = _looping_for(name)
|
||||
view.loop_phase_units = _loop_phase
|
||||
view.draw_leaf_for = _draw_leaf_for
|
||||
view.additive_elements = _additive_elements
|
||||
view.loop_leaf = _loop_leaf_screens.has(name)
|
||||
if not view.load_screen(view.tree, name):
|
||||
push_error(view.tree.error)
|
||||
|
||||
@@ -145,6 +145,32 @@ var loop_phase_units: float = -1.0
|
||||
## consistency harness compares against a renderer that draws no leaves at all.
|
||||
var draw_leaf_for: Array = []
|
||||
|
||||
## Elements the game draws ADDITIVELY, by screen -- `authored/rendering.json`
|
||||
## `additive_elements`. Measured off the running game per draw; see that file for
|
||||
## what is in the list and, more importantly, what is not.
|
||||
var additive_elements: Dictionary = {}
|
||||
## The canvas items backing the paint-order runs. See `_band`.
|
||||
var _bands: Array[RID] = []
|
||||
## The canvas item the next `_draw_quad` paints into. It is a member rather than
|
||||
## a parameter because every draw funnels through `_draw_quad` from three call
|
||||
## sites, and threading a RID through `_draw_leaf` and `_draw_focus` would change
|
||||
## their signatures to carry a value neither of them chooses.
|
||||
var _target: RID = RID()
|
||||
## 🔴 THIS USED TO BE `CanvasItemMaterial.new()` AND NOTHING ELSE, so the blend
|
||||
## mode was its default, MIX. Every band was created, ordered and assigned
|
||||
## correctly and the screen composited exactly as before: `ptframe1` moved from
|
||||
## 22.72 to 22.69. That is the failure mode this port keeps meeting -- the change
|
||||
## ran, produced a number, and the number was WRONG BY BEING RIGHT-LOOKING. It was
|
||||
## caught only because the measurement predicted a large move and a 0.03 move is
|
||||
## not one.
|
||||
var _additive_material := _make_additive()
|
||||
|
||||
|
||||
static func _make_additive() -> CanvasItemMaterial:
|
||||
var m := CanvasItemMaterial.new()
|
||||
m.blend_mode = CanvasItemMaterial.BLEND_MODE_ADD
|
||||
return m
|
||||
|
||||
## Whether this screen replays a leaf's group. See `authored/rendering.json`.
|
||||
var loop_leaf := false
|
||||
|
||||
@@ -524,20 +550,49 @@ func _template_instance_ids() -> Dictionary:
|
||||
## capture at a known angle.
|
||||
func _draw_quad(tex: Texture2D, rect: Rect2, colour: Color, pivot: Vector2,
|
||||
pos: Vector2, rotation_deg: float) -> void:
|
||||
var ci := _target if _target.is_valid() else get_canvas_item()
|
||||
if is_zero_approx(rotation_deg):
|
||||
if tex != null:
|
||||
draw_texture_rect(tex, rect, false, colour)
|
||||
else:
|
||||
draw_rect(rect, colour, true)
|
||||
_add_quad(ci, tex, rect, colour)
|
||||
return
|
||||
var anchor := pos + pivot
|
||||
draw_set_transform(anchor, deg_to_rad(rotation_deg), Vector2.ONE)
|
||||
var local := Rect2(rect.position - anchor, rect.size)
|
||||
RenderingServer.canvas_item_add_set_transform(ci,
|
||||
Transform2D(deg_to_rad(rotation_deg), anchor))
|
||||
_add_quad(ci, tex, Rect2(rect.position - anchor, rect.size), colour)
|
||||
RenderingServer.canvas_item_add_set_transform(ci, Transform2D())
|
||||
|
||||
|
||||
func _add_quad(ci: RID, tex: Texture2D, rect: Rect2, colour: Color) -> void:
|
||||
if tex != null:
|
||||
draw_texture_rect(tex, local, false, colour)
|
||||
RenderingServer.canvas_item_add_texture_rect(ci, rect, tex.get_rid(), false, colour)
|
||||
else:
|
||||
draw_rect(local, colour, true)
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2.ONE)
|
||||
RenderingServer.canvas_item_add_rect(ci, rect, colour)
|
||||
|
||||
|
||||
## 🔴 WHY THE DRAWING GOES THROUGH `RenderingServer` AND NOT `draw_texture_rect`.
|
||||
##
|
||||
## Godot sets the blend mode on a CANVAS ITEM, not on a draw call, so an additive
|
||||
## element cannot simply be drawn differently inside one `_draw()`. The measured
|
||||
## fact is per element (`authored/rendering.json` `additive_elements`), so the
|
||||
## screen is split into RUNS of consecutive paint-order entries sharing a blend
|
||||
## mode and each run gets its own canvas item, ordered by `canvas_item_set_draw_index`.
|
||||
##
|
||||
## ⚠️ The obvious implementation -- child `Node2D`s with a `CanvasItemMaterial`
|
||||
## each -- LOSES A FRAME. `boot.gd` calls `view.queue_redraw()` from nine places
|
||||
## and none of them reaches a child node, so the bands would paint the previous
|
||||
## 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.
|
||||
func _band(i: int, additive: bool) -> RID:
|
||||
while _bands.size() <= i:
|
||||
var ci := RenderingServer.canvas_item_create()
|
||||
RenderingServer.canvas_item_set_parent(ci, get_canvas_item())
|
||||
_bands.append(ci)
|
||||
var item: RID = _bands[i]
|
||||
RenderingServer.canvas_item_clear(item)
|
||||
RenderingServer.canvas_item_set_draw_index(item, i)
|
||||
RenderingServer.canvas_item_set_material(item,
|
||||
_additive_material.get_rid() if additive else RID())
|
||||
return item
|
||||
|
||||
|
||||
static func _rot_of(pose: Dictionary) -> float:
|
||||
@@ -706,9 +761,33 @@ func _draw() -> void:
|
||||
var ghosts := _template_instance_ids()
|
||||
skipped.clear()
|
||||
drawn.clear()
|
||||
for index: int in screen.get("paint_order", []):
|
||||
# The runs are computed from the paint order every frame rather than cached,
|
||||
# because the additive elements happen to be CONSECUTIVE on both screens that
|
||||
# have a measurement and that is an accident of those two screens. A cache
|
||||
# keyed on "the additive block" would be correct today and silently wrong on
|
||||
# the first screen that interleaves.
|
||||
var additive_here: Array = additive_elements.get(
|
||||
String(screen.get("name", "")), [])
|
||||
var order: Array = screen.get("paint_order", [])
|
||||
var band_of := {}
|
||||
var band_additive: Array[bool] = []
|
||||
var prev := -1
|
||||
for index: int in order:
|
||||
var add_it: bool = additive_here.has(String(elements[index].get("id", "")))
|
||||
if prev == -1 or add_it != band_additive[prev]:
|
||||
band_additive.append(add_it)
|
||||
prev += 1
|
||||
band_of[index] = prev
|
||||
for i in band_additive.size():
|
||||
_band(i, band_additive[i])
|
||||
# Runs left over from a screen with more of them would still hold last
|
||||
# frame's commands and paint over this one.
|
||||
for i in range(band_additive.size(), _bands.size()):
|
||||
RenderingServer.canvas_item_clear(_bands[i])
|
||||
for index: int in order:
|
||||
var element: Dictionary = elements[index]
|
||||
var id: String = element.get("id", "")
|
||||
_target = _bands[band_of[index]]
|
||||
if ghosts.has(index):
|
||||
skipped.append("%s (template instance)" % id)
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user