From 07d41dc3214d9bab2df9d539160420fbdab9d1bb Mon Sep 17 00:00:00 2001 From: grom Date: Fri, 16 Oct 2026 19:11:13 -0600 Subject: [PATCH] portrait: measure SetPosition's unit instead of fitting it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One `WOW_BOOTH_LOG` run with Tmog open printed every term, so the factor is arithmetic now rather than a fit: - The cards draw through the paper-doll camera, logged `eye=(0.003, 1.103, -2.919)`, and the same run logged the dwarf's `head_y=1.364`. - Tmog's browse cards frame on the slot being browsed, so its per-race **head** row is a request to put that race's head on the camera's axis. The dwarf row's vertical is `-1.2`. - Centring the head needs `1.364 - 1.103 = 0.261` model units against a request of `1.2`: **0.2175**, rounded to 0.22. The same run confirmed the whole chain end to end: the logged pose inverted back to `(3.72, 0, 0.489)` at facing 3.2, and `Tmog.lua:332` reads `{ 3.8, 0, 0.5, 3.2 }` — the small gap is the previous factor, `G48` at grom's actual aspect. It cross-checks on depth: the head row's `6.3` then moves the model 1.37 of its 2.92 standoff, leaving it 1.55 out — a view about 0.75 units tall around a head of roughly 0.35. The two earlier values are what make the arithmetic worth trusting: 1.0 put every card *behind* its camera and the grid drew empty; `G48` ≈ 0.49 left them 1.06 out, the extreme close-up in the screenshot. Still a measurement of an addon's table against this client rather than of the reference's own root, and the constant says so. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn --- crates/benilla-app/src/portrait/mod.rs | 31 ++++++++++-- .../benilla-app/src/ui_script/extract/mod.rs | 47 ++++++------------- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/crates/benilla-app/src/portrait/mod.rs b/crates/benilla-app/src/portrait/mod.rs index 8ba1f154..75a949ac 100644 --- a/crates/benilla-app/src/portrait/mod.rs +++ b/crates/benilla-app/src/portrait/mod.rs @@ -761,10 +761,33 @@ pub(crate) fn pane_bake_size(longest_px: u32) -> u32 { #[derive(Resource, Default)] pub(crate) struct PaneBakeSizes(pub(crate) HashMap); -/// Each drawn pane's `Model:SetPosition`, as the translation its booth's model root carries: one -/// model unit per `SetPosition` unit, in bevy's axes, converted at the publish so no booth has to -/// know the widget's coordinate order. The extract's `ModelPane` arm says why the reference's -/// `· layoutScale` is deliberately left off here. +/// Model units per `SetPosition` unit on the booth path, **measured** rather than traced. +/// +/// The measurement, off one `WOW_BOOTH_LOG` run with Tmog open, because every term in it is a +/// number this client printed: +/// +/// - Tmog's browse cards frame on the slot being browsed, so its per-race head row is a request +/// to put that race's head on the pane camera's axis. For a dwarf that row's vertical is +/// `-1.2` (`Tmog.lua:248`). +/// - The cards draw through the paper-doll camera, logged as `eye=(0.003, 1.103, -2.919)`, and +/// the same run logged the subject's `head_y=1.364`. +/// - So the displacement that centres the head is `1.364 - 1.103 = 0.261` model units, against a +/// request of `1.2`: **0.2175**, rounded here. +/// +/// It cross-checks on depth. At this factor the head row's `6.3` moves the model 1.37 of its +/// 2.92 standoff, leaving it 1.55 out — a view about 0.75 units tall around a head of roughly +/// 0.35, which is a head filling half a card. The two previous values are what make the +/// arithmetic worth trusting over a guess: 1.0 put every card *behind* its camera (the grid drew +/// empty) and `G48` ≈ 0.49 left them at 1.06, the extreme close-up grom photographed. +/// +/// Still a measurement of an addon's table against this client, not of the reference's own root; +/// a capture of `0x76d1a0` would supersede it. The extract's `ModelPane` arm says why the +/// reference's `· layoutScale` is left off entirely. +pub(crate) const SET_POSITION_UNIT: f32 = 0.22; + +/// Each drawn pane's `Model:SetPosition`, as the translation its booth's model root carries: +/// [`SET_POSITION_UNIT`] model units per unit, in bevy's axes, converted at the publish so no +/// booth has to know the widget's coordinate order. /// /// A channel of its own, keyed by slot, for the reason [`PaneBakeSizes`] gives and one more: it /// serves the four stock window panes as well as the pool, and only the extract can see a pane's diff --git a/crates/benilla-app/src/ui_script/extract/mod.rs b/crates/benilla-app/src/ui_script/extract/mod.rs index adb75b60..6c0e0670 100644 --- a/crates/benilla-app/src/ui_script/extract/mod.rs +++ b/crates/benilla-app/src/ui_script/extract/mod.rs @@ -1249,35 +1249,18 @@ fn convert_entry( // frozen (`0x7acf10`) rather than republished through the root, so the pane zooms and // pans — Tmog's wheel and right-drag. // - // **`G48` model units per `SetPosition` unit**, `G48 = 1/√(a²+1)` for the screen - // aspect — the client's own `[0x832a48]`, the same constant `Model::layout_unit` - // inverts for the orthographic leg. Deliberately not the reference's `pos · - // layoutScale`: that factor belongs to the group the reference's root scales with it - // (`T(pos·layout) · R(facing) · S(G48·(5/3)·modelScale·layout)`) and this booth's - // root carries only the renormalizer, so spending the translate's share alone would - // move a `scale="4"` pane's model four times its own size. - // - // The constant is **fitted, not traced**, and the fit is Tmog's own shipped table: - // `Positions[slot][race][sex]` frames a browse card on the slot being browsed, so - // each row is a displacement that must land the model's head in frame against that - // race's authored camera. Across gnome (3.8, standoff ≈2.2), dwarf (6.3), human (8.8, - // standoff 4.02) and night elf (11.8, the tallest) the ratio sits at 0.45–0.55, and - // `G48` is 0.50 at a 16:9-ish panel. Shipping 1.0 put every card's model behind its - // own camera and the grid drew empty, which is what pinned the order of magnitude. - // A capture of the reference's root would settle it properly; until then this is a - // measurement of an addon, not of the client. - let diag = if h > 0.0 { - ((w / h) * (w / h) + 1.0).sqrt() - } else { - 5.0 / 3.0 - }; - let g48 = 1.0 / diag; + // **[`SET_POSITION_UNIT`] model units per `SetPosition` unit.** Deliberately not + // the reference's `pos · layoutScale`: that factor belongs to the group the + // reference's root scales with it (`T(pos·layout) · R(facing) · + // S(G48·(5/3)·modelScale·layout)`) and this booth's root carries only the + // renormalizer, so spending the translate's share alone would move a `scale="4"` + // pane's model four times its own size. booths.positions.0.insert( slot.to_string(), benilla_assets::coords::wow_to_bevy([ - position.0 * g48, - position.1 * g48, - position.2 * g48, + position.0 * crate::portrait::SET_POSITION_UNIT, + position.1 * crate::portrait::SET_POSITION_UNIT, + position.2 * crate::portrait::SET_POSITION_UNIT, ]), ); // The aspect is published before the readiness check: the bake waits on the publish. @@ -2310,21 +2293,21 @@ mod extract_gate_tests { "{what}: got {got:?}, want {want:?}" ); }; - // The window is 1024x768 in this harness, so `G48 = 1/√((4/3)²+1) = 0.6`. + // `SET_POSITION_UNIT` is 0.22: `(2, 0.5, -0.25)` lands as `(-0.11, -0.055, -0.44)`. near( at(&app, UNIT_PANE_SLOTS[0]), - Vec3::new(-0.3, -0.15, -1.2), + Vec3::new(-0.11, -0.055, -0.44), "an addon's own pane publishes under the pooled slot it claimed", ); // A stock name takes its window's booth, and its pane's position with it: the channel is // keyed by slot precisely so the four window panes are not a second path. near( at(&app, "paperdoll"), - Vec3::new(0.0, 0.0, -0.6), + Vec3::new(0.0, 0.0, -0.22), "a named pane publishes under its window's booth", ); - // `G48` model units per `SetPosition` unit: the pane's own layout scale does **not** - // multiply it, because this booth's root does not scale by it either + // A fixed number of model units per `SetPosition` unit: the pane's own layout scale does + // **not** multiply it, because this booth's root does not scale by it either // (`framing::pane_root_scale_at`). Taking the translate's share of the reference's // `T(pos·layout) · R · S(…·layout)` group while dropping the scale's would move a // `scale="4"` Tmog card's model four times its own size. @@ -2335,7 +2318,7 @@ mod extract_gate_tests { app.update(); near( at(&app, UNIT_PANE_SLOTS[0]), - Vec3::new(-0.3, -0.15, -1.2), + Vec3::new(-0.11, -0.055, -0.44), "a scaled pane moves its model by the same model units", ); // A pane that stops drawing publishes nothing at all, so the booth keeps where it was