diff --git a/README.md b/README.md index 07a0d45..3602fc6 100644 --- a/README.md +++ b/README.md @@ -172,3 +172,28 @@ env SIMCTL_CHILD_M3TE_FX=11 SIMCTL_CHILD_M3TE_ANIM_TIME=3.0 \ The combo emphasis is purely visual and self-pruning: it never gates input (`BoardAnim.active` owns gating) and never touches board / score / move state. + +### Glossier gem & selection feel (P11.3) + +The selection highlight (`board_view.sx` `render_selection`) is a candy-glossier +overlay: two concentric stroked rings fake a soft outward glow, a warm wash tints +the cell, a bright rim is doubled by a thin inner highlight for a glassy edge, and +a wet sheen rides the selected gem's live pose. The engine can't tint a texture at +draw time (issue 0002), so every layer is a rect/overlay — never a gem-texture +tint. The selection-pop motion still comes from `gem_anim`, so the **t==0 idle +pose is byte-identical to the static sprite** (locked by `tests/gem_pose.sx`); the +gloss is selection-only, so the resting board (no selection) is unchanged. + +Capture it with the same P6.3 hooks — no new env var: + +```bash +# Glossy candy selection on cell (3,3), pinned mid-pop: goldens/p6_select.png +env SIMCTL_CHILD_M3TE_ANIM_TIME=0.17 SIMCTL_CHILD_M3TE_SELECT=27 \ + xcrun simctl launch booted co.swipelab.m3te +# Same selection at exact rest (no pop) — isolates the overlay: +env SIMCTL_CHILD_M3TE_ANIM_TIME=0 SIMCTL_CHILD_M3TE_SELECT=27 \ + xcrun simctl launch booted co.swipelab.m3te +``` + +The selection gloss is purely visual: it never gates input (`BoardAnim.active` +owns gating) and never touches board / score / move state. diff --git a/board_view.sx b/board_view.sx index f6bbbd6..2b47cb9 100644 --- a/board_view.sx +++ b/board_view.sx @@ -33,11 +33,18 @@ GEM_FILL_FRAC :f32: 0.84; // actually sizes it; vertical centering inside the safe area is unchanged. BOARD_INSET_X :f32: 16.0; -// Selection overlay: a translucent warm fill plus a bright opaque rim around the -// chosen cell. `add_stroked_rect` draws the rim in its FILL color (the renderer -// ignores the separate stroke color), so SELECT_RIM is passed as the fill. -SELECT_FILL :: Color.{ r = 255, g = 240, b = 120, a = 70 }; -SELECT_RIM :: Color.{ r = 255, g = 228, b = 60, a = 255 }; +// Selection overlay (P11.3): a soft candy "glow" halo, a warm wash over the cell, +// a bright rim topped by a glossy inner highlight, and a wet sheen on the chosen +// gem. `add_stroked_rect` paints the border band in its FILL colour (the shader +// ignores the separate stroke colour), so each ring colour is passed as the fill. +// The engine can't tint/fade a texture at draw time (issue 0002), so every layer +// here is a rect/overlay — never a gem-texture tint. +SELECT_GLOW_OUT :: Color.{ r = 255, g = 232, b = 140, a = 30 }; // wide faint outer bloom +SELECT_GLOW_IN :: Color.{ r = 255, g = 238, b = 150, a = 70 }; // brighter near-edge halo +SELECT_FILL :: Color.{ r = 255, g = 244, b = 150, a = 80 }; // warm wash over the gem +SELECT_RIM :: Color.{ r = 255, g = 234, b = 92, a = 255 }; // bright candy rim +SELECT_RIM_HI :: Color.{ r = 255, g = 255, b = 232, a = 220 }; // glossy inner highlight ring +SELECT_GLOSS :: Color.{ r = 255, g = 255, b = 255, a = 96 }; // wet sheen on the selected gem // HUD: a translucent card with the score and remaining moves, in the loaded Lato // font. Placed in the empty band above the centered grid (inside the safe area). @@ -302,6 +309,39 @@ BoardView :: struct { } } + // Selection emphasis (P11.3): a glossier candy highlight on the chosen cell. + // Two concentric stroked rings fake a soft outward glow (the renderer has no + // blur), a warm wash tints the cell, a bright rim is doubled by a thin inner + // highlight for a glassy edge, and a wet sheen rides the selected gem's live + // pose. All rect/overlay layers (issue 0002 forbids a draw-time gem tint); the + // selection-pop motion still comes from gem_anim, so the t==0 idle pose is + // untouched. + render_selection :: (self: *BoardView, ctx: *RenderContext, dim: f32) { + cs := self.layout.cell_size; + cf := self.layout.cell_frame(self.sel.cell.col, self.sel.cell.row); + + // Glow halo: rings just outside the cell edge, brighter nearer the rim, so + // the falloff reads as a soft bloom without tinting the gem interior. + ctx.add_stroked_rect(cf.expand(cs * 0.16), SELECT_GLOW_OUT, SELECT_GLOW_OUT, cs * 0.16, cs * 0.30); + ctx.add_stroked_rect(cf.expand(cs * 0.07), SELECT_GLOW_IN, SELECT_GLOW_IN, cs * 0.08, cs * 0.21); + + // Warm wash + bright rim + a thin glossy highlight ring just inside the rim. + ctx.add_rounded_rect(cf, SELECT_FILL, cs * 0.14); + rim_w := max(2.0, cs * 0.06); + ctx.add_stroked_rect(cf, SELECT_RIM, SELECT_RIM, rim_w, cs * 0.14); + hi_w := max(1.0, cs * 0.022); + ctx.add_stroked_rect(cf.expand(0.0 - rim_w), SELECT_RIM_HI, SELECT_RIM_HI, hi_w, cs * 0.11); + + // Wet sheen on the selected gem: a bright pill in its upper third, sized to + // the gem's live pose so it tracks the selection pop. + pose := self.gem_pose_at(self.sel.cell.col, self.sel.cell.row); + gf := self.gem_pose_frame(self.sel.cell.col, self.sel.cell.row, dim, pose); + gw := gf.size.width; + gh := gf.size.height; + gloss := Frame.make(gf.origin.x + gw * 0.22, gf.origin.y + gh * 0.13, gw * 0.40, gh * 0.22); + ctx.add_rounded_rect(gloss, SELECT_GLOSS, gh * 0.12); + } + // Play the active slice of the move timeline. Gem motion is clipped to the // grid so refilled gems slide in from behind the top edge rather than // overlapping the HUD band above the board. @@ -582,13 +622,10 @@ impl View for BoardView { } } - // 3. Selection overlay on the chosen cell: a translucent fill under a - // bright rim, drawn over the whole grid so it reads as a highlight. + // 3. Selection emphasis on the chosen cell: a soft candy glow halo under a + // warm wash, a bright glossy rim, and a wet sheen on the popped gem. if self.sel != null and self.sel.active { - cf := self.layout.cell_frame(self.sel.cell.col, self.sel.cell.row); - ctx.add_rect(cf, SELECT_FILL); - rim_w := max(2.0, self.layout.cell_size * 0.06); - ctx.add_stroked_rect(cf, SELECT_RIM, SELECT_RIM, rim_w, self.layout.cell_size * 0.14); + self.render_selection(ctx, gem_dim); } // 4. HUD card with score + remaining moves, in the band above the grid. diff --git a/goldens/p6_select.png b/goldens/p6_select.png index cec6919..4aae04b 100644 Binary files a/goldens/p6_select.png and b/goldens/p6_select.png differ