From 8ba97e61beb17167067bfb787cd9e12e3d4701a0 Mon Sep 17 00:00:00 2001 From: Sazid Al Bayazid Date: Tue, 13 Oct 2026 21:58:25 +0600 Subject: [PATCH] Slide show: Honeycomb's hexagons cover the whole slide again (#420) The row count came from 1/(1.5*rh) - the row spacing alone - and ignored how far a settled hexagon actually reaches. On a 16:9 slide the eleven rows stopped at ~75% of the height: from t~0.9, when every cell has settled, the bottom quarter was plain black until the t=1 endpoint snapped to the full new slide. The count now derives from the real geometry: rows sit 0.866*rh apart and the flat bottom zone of a settled hexagon reaches 0.289*rh past its centre, so counting to the flat-zone reach covers the slide for every aspect ratio - counting to the vertex reach still left small V-notches of black above the bottom edge on about a third of them (US Letter, square, ...). 16:9 gains four rows (15, was 11), 4:3 seven (20, was 13). Tests: at t=0.99, when every cell is settled and the seams are nearly closed, a 0.02-step grid over the slide must sit inside settled incoming cells (padded 1% past the hairline seams) on five aspect ratios including US Letter and square. Fails on the old count at (0.2, 0.74); before/after frames rendered with render-transition. --- ROADMAP.md | 1 + crates/anim/src/tests.rs | 30 ++++++++++++++++++++++++++++++ crates/anim/src/transition_fx.rs | 6 +++++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index 46a87add..9dfaece0 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -128,6 +128,7 @@ app's JSON control channel (`deckcraft --control PORT`). | Date | What landed | |---|---| +| 2026-10-13 | Slide show: Honeycomb's hexagons cover the whole slide again — the bottom quarter stayed black until the endpoint (#420) | | 2026-10-13 | Slide transitions like PowerPoint: every transition and option measured frame by frame against PowerPoint's video export and retuned (curves, directions, soft edges, 3-D perspective, contents moving over backgrounds); PowerPoint's own XML elements and defaults read and written (diagonal Wipe, Zoom, Shape In/Out, Glitter Hexagons, Shred In/Out, Page Curl Single); `deckcraft-cli render-transition` renders frames headless | | 2026-10-13 | Wild text cases measured against PowerPoint (102 synthetic slides): bidi by run language, East Asian vertical text upright, mongolianVert/wordArtVert/wordArtVertRtl, underline styles and strikethrough at the font's metrics, centre/right/decimal tabs, East Asian, Thai, Hindi, Arabic and Hebrew numbering, small caps, all-caps "ß", transparent text, soft hyphens, kinsoku, rtlCol and just/dist anchoring, emoji presentation, fallback faces that no longer move the baseline | | 2026-10-13 | Render matches PowerPoint more closely on wild shape, picture and table cases: even-odd custom paths, dashes and caps, arrowheads, radial and path gradients, tile rects, rotWithShape, gradient lines, shadows cast by what is drawn (group and perspective too), group fill, picture borders, tiles, reflection, recolour (biLevel, alphaInv, brightness/contrast), table borders and grid default, 20+ preset shapes by their ECMA formulas, default miter joins, and approximate 3-D bevels, cameras and face lighting | diff --git a/crates/anim/src/tests.rs b/crates/anim/src/tests.rs index 1fd82d78..18d934fa 100644 --- a/crates/anim/src/tests.rs +++ b/crates/anim/src/tests.rs @@ -789,3 +789,33 @@ fn show_follows_links() { assert_eq!(s.follow(&p, &Action::LastViewed, None), LinkJump::None); assert_eq!(s.slide, 3); } + +/// #420: late in the Honeycomb every cell has settled, so the incoming hexagons must cover the +/// whole slide — the old row count stopped at ~75% of a 16:9 slide and left the bottom black. +#[test] +fn honeycomb_cells_reach_the_bottom_before_the_slide_ends() { + let sign = |a: [f64; 2], b: [f64; 2], c: [f64; 2]| (a[0] - c[0]) * (b[1] - c[1]) - (b[0] - c[0]) * (a[1] - c[1]); + let inside = |p: [f64; 2], t: [[f64; 2]; 3]| { + // The dark seams between cells are the effect: at t=0.99 they are ~0.012% of the width + // open, so a 1% pad forgives them and still fails genuinely uncovered ground. + let c = [(t[0][0] + t[1][0] + t[2][0]) / 3.0, (t[0][1] + t[1][1] + t[2][1]) / 3.0]; + let t = t.map(|v| [c[0] + (v[0] - c[0]) * 1.01, c[1] + (v[1] - c[1]) * 1.01]); + let (d1, d2, d3) = (sign(p, t[0], t[1]), sign(p, t[1], t[2]), sign(p, t[2], t[0])); + !((d1 < 0.0 || d2 < 0.0 || d3 < 0.0) && (d1 > 0.0 || d2 > 0.0 || d3 > 0.0)) + }; + for aspect in [16.0 / 9.0, 3.0 / 2.0, 4.0 / 3.0, 11.0 / 8.5, 1.0] { + // t = 0.99: every cell is settled and the seams are nearly closed. + let ls = transition_frame("honeycomb", "", 0.99, aspect); + let tris: Vec<[[f64; 2]; 3]> = + ls.iter().filter(|l| l.source == Source::New && l.alpha >= 1.0).map(|l| [l.quad[0], l.quad[1], l.quad[2]]).collect(); + assert!(!tris.is_empty()); + let mut sy = 0.0; + while sy <= 1.0 { + for sx in [0.0, 0.2, 0.4, 0.6, 0.8, 0.99, 1.0] { + let p = [sx, sy]; + assert!(tris.iter().any(|t| inside(p, *t)), "honeycomb aspect {aspect} t=0.99: {sx},{sy} is uncovered (no settled cell reaches it)"); + } + sy += 0.02; + } + } +} diff --git a/crates/anim/src/transition_fx.rs b/crates/anim/src/transition_fx.rs index d5fb2e16..3bbc4a8d 100644 --- a/crates/anim/src/transition_fx.rs +++ b/crates/anim/src/transition_fx.rs @@ -794,7 +794,11 @@ fn honeycomb(t: f64, a: f64) -> Vec { let cols = 11usize; let w = 1.0 / cols as f64; let rh = w * a * 0.5; - let rows = (1.0 / (rh * 1.5)).ceil() as usize + 2; + // Rows sit 0.866*rh apart; a settled hex reaches only 0.577*rh from its centre at the side + // vertices, and the flat bottom zone 0.289*rh. Count the rows whose FLAT zone still covers + // the slide's bottom: counting the vertex reach instead left V-notches of black above the + // bottom edge on about a third of the aspect ratios (#420). + let rows = ((1.0 / (rh * 0.866) - 1.0 / 3.0).ceil().max(1.0)) as usize + 1; let mut v = vec![Layer::full(Black, 1.0)]; let seam = ss(win(t, 0.0, 0.12)) * (1.0 - ss(win(t, 0.85, 1.0))); let hex = |cx: f64, cy: f64, s: f64| -> Vec<[f64; 2]> {