P11.3: glossier candy gem & selection feel (sx / iOS)
Make the selection highlight read as glossy candy without new art and without disturbing the idle-rest invariant. board_view's render_selection layers a soft outward glow (two concentric stroked rings — the renderer has no blur), a warm wash, a bright rim doubled by a thin inner highlight for a glassy edge, and a wet sheen that rides the selected gem's live pose. Every layer is a rect/overlay (issue 0002 forbids a draw-time gem-texture tint). The gloss is selection-only: render_selection runs solely when a cell is selected, so the resting board (no selection) is byte-identical to before and the t==0 idle pose stays exactly the static sprite (locked by tests/gem_pose). The selection-pop motion still comes from gem_anim; no board / score / move state changes and input stays gated by BoardAnim.active. Updated goldens/p6_select.png; README documents the P11.3 selection gloss and its reproduce commands (reusing the P6.3 M3TE_SELECT + M3TE_ANIM_TIME hooks).
This commit is contained in:
25
README.md
25
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
|
The combo emphasis is purely visual and self-pruning: it never gates input
|
||||||
(`BoardAnim.active` owns gating) and never touches board / score / move state.
|
(`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.
|
||||||
|
|||||||
@@ -33,11 +33,18 @@ GEM_FILL_FRAC :f32: 0.84;
|
|||||||
// actually sizes it; vertical centering inside the safe area is unchanged.
|
// actually sizes it; vertical centering inside the safe area is unchanged.
|
||||||
BOARD_INSET_X :f32: 16.0;
|
BOARD_INSET_X :f32: 16.0;
|
||||||
|
|
||||||
// Selection overlay: a translucent warm fill plus a bright opaque rim around the
|
// Selection overlay (P11.3): a soft candy "glow" halo, a warm wash over the cell,
|
||||||
// chosen cell. `add_stroked_rect` draws the rim in its FILL color (the renderer
|
// a bright rim topped by a glossy inner highlight, and a wet sheen on the chosen
|
||||||
// ignores the separate stroke color), so SELECT_RIM is passed as the fill.
|
// gem. `add_stroked_rect` paints the border band in its FILL colour (the shader
|
||||||
SELECT_FILL :: Color.{ r = 255, g = 240, b = 120, a = 70 };
|
// ignores the separate stroke colour), so each ring colour is passed as the fill.
|
||||||
SELECT_RIM :: Color.{ r = 255, g = 228, b = 60, a = 255 };
|
// 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
|
// 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).
|
// 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
|
// 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
|
// grid so refilled gems slide in from behind the top edge rather than
|
||||||
// overlapping the HUD band above the board.
|
// 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
|
// 3. Selection emphasis on the chosen cell: a soft candy glow halo under a
|
||||||
// bright rim, drawn over the whole grid so it reads as a highlight.
|
// warm wash, a bright glossy rim, and a wet sheen on the popped gem.
|
||||||
if self.sel != null and self.sel.active {
|
if self.sel != null and self.sel.active {
|
||||||
cf := self.layout.cell_frame(self.sel.cell.col, self.sel.cell.row);
|
self.render_selection(ctx, gem_dim);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. HUD card with score + remaining moves, in the band above the grid.
|
// 4. HUD card with score + remaining moves, in the band above the grid.
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.7 MiB After Width: | Height: | Size: 3.7 MiB |
Reference in New Issue
Block a user