feat(vk): draft the VK community cover
Adds VK's own cover template (1920x768) and a self-contained generator that renders an Эрудит board in perspective with ГРАМОТНОСТЬ / СОПЕРНИК / ЭРУДИЦИЯ / УМ already played, in six art-direction variants for review. The position is checked, not asserted: assertLegal re-reads the finished grid and fails the build unless the centre is covered, every declared word is on the board, and no maximal run of two or more letters is a word nobody played. All four words are in the Эрудит dictionary and tile values come from scrabble-solver rules.Erudit(). The camera has pitch only, so the perspective warp reduces to an exact per-scanline remap of a flat texture; tile thickness is filled as 3D quads between the board plane and the tile plane. skia-canvas is borrowed from the render sidecar rather than installed a second time for a one-off asset build. Composition respects the two VK keep-outs measured off the template: the top 130 px some clients crop, and the mobile avatar circle over the bottom-left — which is why the default word arrangement keeps that corner empty.
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
# VK community cover
|
||||
|
||||
The cover art for the VK community (`1920×768`). The community avatar — the «Э» tile — is
|
||||
uploaded separately; this is only the backdrop behind it.
|
||||
|
||||
`build-cover.mjs` is the whole thing: a small software renderer that draws an Эрудит board in
|
||||
perspective with the opening `ГРАМОТНОСТЬ / СОПЕРНИК / ЭРУДИЦИЯ / УМ` already played, and
|
||||
emits one PNG per art-direction variant plus a contact sheet.
|
||||
|
||||
```sh
|
||||
cd assets/vk/cover
|
||||
node build-cover.mjs # every variant -> preview/
|
||||
node build-cover.mjs night-spot # one variant
|
||||
FAST=1 node build-cover.mjs # 1x instead of 2x supersampling, for quick iteration
|
||||
ZONES=1 node build-cover.mjs # overlay the VK keep-outs on the render
|
||||
LAYOUT=cross node build-cover.mjs # the alternative word arrangement
|
||||
```
|
||||
|
||||
It needs no install: skia-canvas comes from the render sidecar's `node_modules`, and the
|
||||
letters are set in `Spectral-Bold.ttf` from the icon brand folder.
|
||||
|
||||
## The position
|
||||
|
||||
`ГРАМОТНОСТЬ` opens through the centre square (its `Т` on H8) and the other three words hook
|
||||
onto a shared letter. `assertLegal` re-reads the finished grid and fails the build if the
|
||||
centre is uncovered, if a declared word is missing, or if any maximal run of two or more
|
||||
letters is a word nobody played — so the board on the cover is always a position the engine
|
||||
would accept. All four words are in the Эрудит dictionary; tile values come from
|
||||
`scrabble-solver` `rules.Erudit()`.
|
||||
|
||||
The default `ladder` arrangement puts both long words horizontally. The `cross` arrangement
|
||||
hangs `ЭРУДИЦИЯ` down the left instead, which looks more symmetric but collides with the VK
|
||||
mobile avatar (below).
|
||||
|
||||
## VK keep-outs
|
||||
|
||||
Measured off VK's own template (`../VK_group_cover_template.fig`), in cover pixels:
|
||||
|
||||
| Zone | Area | Why |
|
||||
|------|------|-----|
|
||||
| Top crop | `y < 130` | trimmed by some clients — nothing important there |
|
||||
| Mobile avatar | circle, centre `(283, 818)`, r `283` | the community picture sits over the bottom-left in the VK app |
|
||||
|
||||
`ZONES=1` paints both so a composition can be checked against them.
|
||||
|
||||
## Rendering technique
|
||||
|
||||
The camera has pitch only — no roll, no yaw — so a board-space horizontal line always maps to
|
||||
a screen-space horizontal line. That makes the perspective warp an exact per-scanline remap of
|
||||
a flat texture rather than a general homography: the board surface and the tile faces are each
|
||||
drawn once, flat, at high resolution, then replayed band by band (`warpPlane`). Tile thickness
|
||||
is filled as real 3D quads between the two planes, so the tiles have visible edges.
|
||||
|
||||
Two things that are easy to get wrong here and cost a hundredfold in render time:
|
||||
`ctx.filter = 'blur(...)'` applies per drawing call, so many blurred shapes must be collected
|
||||
into one offscreen layer and blurred once (`blurred`); and `drawImage` re-snapshots a live
|
||||
`Canvas` source on every call, so a texture sampled hundreds of times must be baked into an
|
||||
`Image` first (`freeze`).
|
||||
Reference in New Issue
Block a user