port: implement the decoded leaf composition -- and it does not close the 1.82%
The Decoder decoded the rule I refused to guess: draw the leaf on its own timeline, do NOT multiply the parent's alpha in. Multiplying is refuted rather than unsupported -- at the fitted time the parent has expired, so leaf x parent predicts zero for both quads and the sweeps would be invisible. They are drawn. Implemented: `_draw_leaf` runs the leaf unclamped, like the spinning ring and for the same reason -- held at its own rest.t the leaf sits at x=1521, entirely off the right edge, so `holding` would delete the sweeps rather than settle them. AND IT CHANGES NOTHING MEASURABLE. The title is still 1.82% against the oracle: 1.82 at t=261, 1.81 at t=355, 1.79 at t=420. At t=355 my interpolation puts the leaf's top-left at x ~ -324, off-screen left, where the Decoder's model puts the quad's CENTRE at 981. Those cannot both be right, and it is not something to tune away -- it is a disagreement about how the leaf's keyframes become a placed quad, most likely in the pivot and the rotation about it. Handed back with both numbers. So: the exporter no longer drops the data, the composition rule is implemented as decoded, and the port's largest oracle gap is exactly where it was. Fixing the export was necessary and not sufficient. TWO FLAGGED ELEMENTS DELIBERATELY NOT DRAWN, in authored/rendering.json with reasons. title_jp/ptlogo_eff2 (parent 125%, leaf 100%) is the same shape and is the element DECISIONS has recorded since P1 as the largest render disagreement -- but the Decoder said plainly "I have not tested it", and drawing it would extend a decode past the case it was fitted on. pgloading_loop5's leaf is scale (0,0), and scale-0 is one of the three historical failures this corpus names. Neither can be adjudicated here: title_jp has no oracle capture, and verify-screen compares against a renderer that draws no leaves at all, so ANY leaf drawing increases that divergence whether right or wrong. Its max went 155 -> 232 when they were drawn, and that number is not evidence in either direction. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
This commit is contained in:
42
authored/rendering.json
Normal file
42
authored/rendering.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"format": "sylpheed.rendering/1",
|
||||
"_": [
|
||||
"WHICH decoded rules the runtime applies where. AUTHORED because it is a",
|
||||
"choice about the REACH of somebody else's decode, not about the disc.",
|
||||
"Delete an entry the day the decode covers the case outright.",
|
||||
"",
|
||||
"The exporter flags `leaf_carries_geometry` on 15 elements -- those whose",
|
||||
"nested `.rat` leaf declares a scale or rotation the parent does not. That",
|
||||
"flag is a CENSUS FACT and it is emitted for all 15. What is DECODED is",
|
||||
"narrower: the Decoder fitted the game's own composed alpha (per-draw vertex",
|
||||
"colours C3FFFFFF / B6FFFFFF = 195 and 182) against the ptloop leaves and got",
|
||||
"one consistent time, then PREDICTED the quad centres to ~11 px. That covers",
|
||||
"`ptloop01` and `ptloop02` and nothing else."
|
||||
],
|
||||
"draw_leaf_for": ["ptloop01", "ptloop02"],
|
||||
"draw_leaf_why": [
|
||||
"The two the decode covers. `docs/re/structures/ui-leaf-vs-parent-alpha.md`.",
|
||||
"",
|
||||
"NOT DRAWN, though the exporter flags them and ships their data:",
|
||||
"",
|
||||
" `title_jp/ptlogo_eff2` -- parent 125% at (412,96), leaf 100% at (402,66).",
|
||||
" It is the same SHAPE as the ptloop case and it is the element DECISIONS has",
|
||||
" recorded since P1 as the largest render disagreement in the export. The",
|
||||
" Decoder said in as many words: 'I have not tested it.' Drawing it would be",
|
||||
" extending a decode past the case it was fitted on, which is the failure",
|
||||
" this port has made four times this session.",
|
||||
"",
|
||||
" `build_12,15/pgloading_loop5` -- leaf scale (0,0). A zero scale is one of",
|
||||
" the three historical failures this corpus names, so it is exactly where a",
|
||||
" guess is most expensive.",
|
||||
"",
|
||||
"AND THERE IS NO WAY TO ADJUDICATE EITHER HERE. `title_jp` has no oracle",
|
||||
"capture, and `verify-screen` compares against `sylpheed-cli`, which does not",
|
||||
"draw leaves at all -- so ANY leaf drawing increases that divergence whether",
|
||||
"it is right or wrong. Its max went 155 -> 232 when they were drawn, and that",
|
||||
"number is not evidence in either direction.",
|
||||
"",
|
||||
"What deletes this list: a decode covering those cases, or an oracle capture",
|
||||
"of title_jp."
|
||||
]
|
||||
}
|
||||
@@ -20,7 +20,7 @@ fn main() {
|
||||
for el in &b.elements {
|
||||
if !el.name.starts_with("ptloop") { continue; }
|
||||
let r = el.rest();
|
||||
println!("\nPARENT {} -> rest scale {:?} rot {:?}", el.name,
|
||||
println!("\nPARENT {} sprite={:?} -> rest scale {:?} rot {:?}", el.name, el.sprite,
|
||||
r.map(|r| (r.scale_x, r.scale_y)), r.map(|r| r.rotation_deg));
|
||||
if let Some(&(off, size)) = b.records.get(&el.name) {
|
||||
match ui_layout::parse_build(&bundle[off..off + size]) {
|
||||
|
||||
@@ -166,6 +166,34 @@ pub struct Element {
|
||||
/// known.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub leaf: Option<Focus>,
|
||||
|
||||
/// True when the leaf's geometry DIFFERS from the parent's, so the leaf is
|
||||
/// what the game draws.
|
||||
///
|
||||
/// Decided here rather than in the runtime because it is disc knowledge.
|
||||
/// The Decoder's rule: *"the discriminator is which record carries the
|
||||
/// geometry, not a fixed order"* — and the census over this export splits
|
||||
/// cleanly, with no ambiguous middle:
|
||||
///
|
||||
/// * **30 of 46** leaf elements duplicate the parent's scale and rotation
|
||||
/// exactly. That is the BASE-record case `screen.rs` already handled: the
|
||||
/// leaf may differ by a unit of position (`ptbtn04`: parent y=401, leaf
|
||||
/// y=402) and the parent wins. Flag is false; nothing changes.
|
||||
/// * **16 differ**, and all of them differ in scale or rotation, not by a
|
||||
/// rounding unit: the ten `ptloop01`/`ptloop02` sweeps ((100,600) at +30°
|
||||
/// and (100,800) at −45° against an identity parent), two
|
||||
/// `pgloading_ring` (leaf scale **(0,0)**), and `title_jp`'s
|
||||
/// `ptlogo_eff2` (**parent 125 %, leaf 100 %**).
|
||||
///
|
||||
/// ⚠️ **Only the `ptloop` case is decoded.** The Decoder fitted the game's
|
||||
/// own composed alpha — vertex colours `C3FFFFFF`/`B6FFFFFF`, i.e. 195 and
|
||||
/// 182 — against the two leaf ramps and got one consistent time, t=355, then
|
||||
/// *predicted* the quad centres at 981 and 478 against 992.0 and 467.2
|
||||
/// measured. The other two are the same shape and are **not** separately
|
||||
/// confirmed; they are flagged so the harness can adjudicate them rather
|
||||
/// than being asserted.
|
||||
#[serde(skip_serializing_if = "std::ops::Not::not")]
|
||||
pub leaf_carries_geometry: bool,
|
||||
/// The raw `opt ` link inside this element's `.rat` record.
|
||||
///
|
||||
/// ⚠️ **This is not a focus link.** It was read as one, and that was
|
||||
@@ -485,6 +513,15 @@ pub fn export_build(
|
||||
sprite: sprite_out,
|
||||
focus_sprite,
|
||||
focus,
|
||||
leaf_carries_geometry: leaf.as_ref().is_some_and(|l| {
|
||||
let p = el.rest();
|
||||
l.elements.iter().any(|le| {
|
||||
p.is_none_or(|p| {
|
||||
le.rest.scale != [p.scale_x, p.scale_y]
|
||||
|| le.rest.rotation_deg != p.rotation_deg
|
||||
})
|
||||
})
|
||||
}),
|
||||
leaf,
|
||||
opt_link: el.focus_link.clone(),
|
||||
pivot: [el.pivot_x, el.pivot_y],
|
||||
|
||||
@@ -3996,3 +3996,61 @@ cancelling, the same shape as mine. It is a **shared decode gap, not a defect in
|
||||
my compositor**, and MISSION's *"Needs a human decision — rotation"* now has a
|
||||
number attached: **1.82 % of the title's pixels, in a signature that can be
|
||||
recognised.**
|
||||
|
||||
## The leaf composition is decoded and implemented — and it does **not** close the 1.82 %
|
||||
|
||||
The Decoder decoded the rule I refused to guess: **draw the leaf on its own
|
||||
timeline; do not multiply the parent's alpha in.** Multiplying is *refuted*, not
|
||||
merely unsupported — at the fitted time the parent has expired (its group returns
|
||||
to 0 at t=250 and holds), so `leaf × parent / 255` predicts zero for both quads
|
||||
and the sweeps would be invisible. They are drawn.
|
||||
|
||||
The fit is worth repeating because of its shape: the game's own composed alpha is
|
||||
observable in the per-draw vertex colours (`C3FFFFFF`/`B6FFFFFF` = **195** and
|
||||
**182**), fitting *only those two numbers* against the two leaf ramps gives one
|
||||
consistent time **t = 355**, and the same t then **predicts** quad centres at 981
|
||||
and 478 against **992.0** and **467.2** measured. No x entered the fit.
|
||||
|
||||
Implemented: `_draw_leaf` runs the leaf unclamped — like the spinning ring, and
|
||||
for the same reason. Held at its own `rest.t` the leaf sits at **x = 1521**,
|
||||
entirely off the right edge, so `holding` would delete the sweeps rather than
|
||||
settle them.
|
||||
|
||||
### 🔴 And it changes nothing measurable. The title is still 1.82 %
|
||||
|
||||
| t | units | differing |
|
||||
|---|---|---|
|
||||
| 4.35 s | 261 | 1.82 % |
|
||||
| **5.917 s** | **355** | **1.81 %** |
|
||||
| 7.00 s | 420 | 1.79 % |
|
||||
|
||||
**At t=355 my interpolation puts the leaf's top-left at x ≈ −324 — off-screen
|
||||
left — where the Decoder's model puts the quad's centre at 981.** Those cannot
|
||||
both be right, and the gap is not something to tune away: it is a disagreement
|
||||
about how the leaf's keyframes become a placed quad, most likely in the pivot and
|
||||
the rotation about it. **Handed back with both numbers.**
|
||||
|
||||
So the honest state: the exporter no longer drops the data, the composition rule
|
||||
is implemented as decoded, and **the port's largest oracle gap is exactly where
|
||||
it was.** Fixing the export was necessary and was not sufficient.
|
||||
|
||||
### Two flagged elements deliberately not drawn
|
||||
|
||||
`authored/rendering.json` narrows drawing to `ptloop01`/`ptloop02` — what the
|
||||
decode covers — while the exporter keeps flagging all 15.
|
||||
|
||||
* **`title_jp/ptlogo_eff2`** — parent 125 % at (412,96), leaf 100 % at (402,66).
|
||||
The same shape, and the element `DECISIONS` has recorded since P1 as the
|
||||
largest render disagreement in the export. The Decoder said plainly *"I have
|
||||
not tested it."* Drawing it would extend a decode past the case it was fitted
|
||||
on — the failure this port has made four times this session.
|
||||
* **`pgloading_loop5`** — leaf scale **(0, 0)**. A zero scale is one of the three
|
||||
historical failures this corpus names, so it is exactly where a guess costs
|
||||
most.
|
||||
|
||||
⚠️ **And neither can be adjudicated here.** `title_jp` has no oracle capture, and
|
||||
`verify-screen` compares against `sylpheed-cli`, **which draws no leaves at all**
|
||||
— so *any* leaf drawing increases that divergence whether it is right or wrong.
|
||||
Its `max` went 155 → 232 when they were drawn, and **that number is not evidence
|
||||
in either direction.** Recognising it as uninformative is the reason it did not
|
||||
become a justification.
|
||||
|
||||
@@ -182,6 +182,9 @@ func _ready() -> void:
|
||||
# own logic on purpose -- see `looping_focus` there for the census that says
|
||||
# this cannot be a rule.
|
||||
_looping = timing.get("looping_focus_records", {})
|
||||
# Which decoded rules apply where. See `authored/rendering.json`.
|
||||
var rendering: Variant = export_tree.authored("rendering.json")
|
||||
_draw_leaf_for = [] if rendering == null else rendering.get("draw_leaf_for", [])
|
||||
# `--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
|
||||
@@ -197,6 +200,7 @@ func _ready() -> void:
|
||||
viewport.add_child(view)
|
||||
|
||||
view.looping_focus = _looping_for(name)
|
||||
view.draw_leaf_for = _draw_leaf_for
|
||||
if not view.load_screen(export_tree, name):
|
||||
push_error(export_tree.error)
|
||||
get_tree().quit(2)
|
||||
@@ -260,6 +264,8 @@ var _play := false
|
||||
var _pending: Variant = null
|
||||
## `authored/timing.json` `looping_focus_records`, keyed `<screen>/<element>`.
|
||||
var _looping: Dictionary = {}
|
||||
## `authored/rendering.json` `draw_leaf_for`.
|
||||
var _draw_leaf_for: Array = []
|
||||
var _script: PackedStringArray = PackedStringArray()
|
||||
var _shots := ""
|
||||
## `--skip-at=SECONDS`: when to send a synthetic (A) during a movie, or 0.
|
||||
@@ -360,6 +366,7 @@ func _advance() -> void:
|
||||
view.holding = true
|
||||
view.time_units = 0.0
|
||||
view.looping_focus = _looping_for(name)
|
||||
view.draw_leaf_for = _draw_leaf_for
|
||||
if not view.load_screen(view.tree, name):
|
||||
push_error(view.tree.error)
|
||||
get_tree().quit(2)
|
||||
@@ -562,6 +569,7 @@ func _menu_arrive() -> void:
|
||||
view.holding = true
|
||||
view.time_units = 0.0
|
||||
view.looping_focus = _looping_for(name)
|
||||
view.draw_leaf_for = _draw_leaf_for
|
||||
if not view.load_screen(view.tree, name):
|
||||
push_error(view.tree.error)
|
||||
get_tree().quit(2)
|
||||
@@ -876,6 +884,7 @@ func _raise_overlay(name: String) -> void:
|
||||
overlay.units_per_second = view.units_per_second
|
||||
overlay.exit_ramp_units = view.exit_ramp_units
|
||||
overlay.looping_focus = _looping_for(name)
|
||||
overlay.draw_leaf_for = _draw_leaf_for
|
||||
overlay.holding = true
|
||||
overlay.time_units = 0.0
|
||||
if not overlay.load_screen(view.tree, name):
|
||||
|
||||
@@ -58,6 +58,17 @@ var exit_ramp_units: float = 24.0
|
||||
## rule's clothes.
|
||||
var looping_focus: Dictionary = {}
|
||||
|
||||
## Element ids whose nested `.rat` leaf the runtime actually draws.
|
||||
##
|
||||
## The exporter flags `leaf_carries_geometry` on 15 elements -- a census fact.
|
||||
## This is narrower on purpose: it is the subset the DECODE covers, and it comes
|
||||
## from `authored/rendering.json` with its reasons. Two elements are flagged and
|
||||
## deliberately not drawn (`title_jp/ptlogo_eff2`, `pgloading_loop5`), because
|
||||
## drawing them would extend a decode past the case it was fitted on and neither
|
||||
## can be adjudicated here -- `title_jp` has no oracle capture, and the
|
||||
## consistency harness compares against a renderer that draws no leaves at all.
|
||||
var draw_leaf_for: Array = []
|
||||
|
||||
## While true the screen holds at `rest` and never plays its exit. The
|
||||
## sequencer clears it to send the screen away.
|
||||
var holding: bool = true
|
||||
@@ -99,6 +110,8 @@ func _load_textures() -> void:
|
||||
var paths: Array = [element.get("sprite", ""), element.get("focus_sprite", "")]
|
||||
# The focus record's own elements carry their own sprites -- the ring is
|
||||
# only reachable this way.
|
||||
for fe: Dictionary in element.get("leaf", {}).get("elements", []):
|
||||
paths.append(fe.get("sprite", ""))
|
||||
for fe: Dictionary in element.get("focus", {}).get("elements", []):
|
||||
paths.append(fe.get("sprite", ""))
|
||||
for rel: String in paths:
|
||||
@@ -367,6 +380,54 @@ static func _rot_of(pose: Dictionary) -> float:
|
||||
## The label is 13 px larger per axis than the base and sits at (-7,-7), which
|
||||
## keeps the two concentric; drawing it at the base position pushes it 7 px
|
||||
## down-right and off-centre.
|
||||
## Draw an element's nested `.rat` leaf INSTEAD of the element itself.
|
||||
##
|
||||
## Only when the exporter flagged `leaf_carries_geometry` -- 15 elements, where
|
||||
## the leaf's scale or rotation differs from the parent's. Everywhere else the
|
||||
## leaf duplicates the parent and the parent wins, which is what this port has
|
||||
## always done and which `screen.rs` documents for base records.
|
||||
##
|
||||
## ⚠️ **The leaf runs on its OWN timeline and the parent's alpha is NOT
|
||||
## multiplied in.** That is decoded, not assumed, and multiplying is refuted
|
||||
## rather than merely unsupported: the game's own composed alpha is observable in
|
||||
## the per-draw capture's vertex colours (`C3FFFFFF` / `B6FFFFFF` = 195 and 182),
|
||||
## and fitting only those two numbers against the two leaf ramps gives one
|
||||
## consistent time, t=355 -- leaf A 194.8 against 195, leaf B 182.2 against 182.
|
||||
## At t=355 the PARENT has expired: its group returns to 0 at t=250 and holds
|
||||
## there, so `leaf x parent / 255` predicts zero for both quads and the sweeps
|
||||
## would be invisible. They are drawn.
|
||||
##
|
||||
## The check that matters was PREDICTED, not fitted: no x entered it, and the
|
||||
## same t=355 places the quad centres at 981 and 478 against 992.0 and 467.2
|
||||
## measured off the capture -- ~11 px on quads travelling 1 560 and 1 950 px.
|
||||
##
|
||||
## ❔ Every observation behind this has parent alpha 0, so "the leaf wins" and
|
||||
## "the parent is ignored because it draws nothing" are NOT separated. A capture
|
||||
## during t=100...238 would separate them.
|
||||
func _draw_leaf(element: Dictionary) -> void:
|
||||
for fe: Dictionary in element.get("leaf", {}).get("elements", []):
|
||||
var rel: String = fe.get("sprite", "")
|
||||
if rel == "":
|
||||
continue
|
||||
var tex: Texture2D = textures.get(rel)
|
||||
if tex == null:
|
||||
skipped.append("%s (leaf sprite failed to load)" % fe.get("id", ""))
|
||||
continue
|
||||
# UNCLAMPED, like the spinning ring and for the same reason: a sweep that
|
||||
# crosses the frame does not stop because the screen has arrived, and
|
||||
# `ORACLE-CAPTURES.md` says these two "move continuously". Held at its own
|
||||
# `rest.t` the leaf sits at x=1521 -- entirely off the right edge -- so
|
||||
# `holding` would delete the sweeps rather than settle them.
|
||||
var was := holding
|
||||
holding = false
|
||||
var pose := pose_at(fe, time_units)
|
||||
holding = was
|
||||
var pivot := _vec(fe.get("pivot", [0, 0]))
|
||||
_draw_quad(tex, placement(pose, pivot, tex.get_size()), modulate_of(pose),
|
||||
pivot, _vec(pose.get("pos", [0, 0])), _rot_of(pose))
|
||||
drawn.append(fe.get("id", ""))
|
||||
|
||||
|
||||
func _draw_focus(element: Dictionary) -> void:
|
||||
var focus: Dictionary = element.get("focus", {})
|
||||
var parent_id := String(element.get("id", ""))
|
||||
@@ -443,6 +504,13 @@ func _draw() -> void:
|
||||
var pivot := _vec(element.get("pivot", [0, 0]))
|
||||
var pos := _vec(pose.get("pos", [0, 0]))
|
||||
var rot := _rot_of(pose)
|
||||
# An element whose LEAF carries the geometry draws the leaf instead of
|
||||
# itself: the parent is a container whose own record has identity scale
|
||||
# and rotation. See `_draw_leaf`.
|
||||
if element.get("leaf_carries_geometry", false) \
|
||||
and draw_leaf_for.has(String(element.get("id", ""))):
|
||||
_draw_leaf(element)
|
||||
continue
|
||||
# A focused button draws its own record instead of its base sprite -- and
|
||||
# so does an element the authored table says always shows it, which is
|
||||
# how the PRESS (A) plate gets its glow on a screen that has no buttons
|
||||
|
||||
Reference in New Issue
Block a user