Same disc, same place, same size. What changed is what it does. It was a tone: a denser patch laid down before the threshold and put through the screen with everything else, so what showed was a change in the density of the dots already there. An inlay rather than an inscription, and it could not clash with a pattern it was made of -- which was the right instinct and one step short of the answer. An inlay needs room inside itself for a density to differ. At a coarse ruling the disc is barely wider than one cell of the screen, so it thinned out to nothing exactly where the screen was boldest, and under SOLID it could not appear at all: that screen discards tone by definition, and a mark made of tone then has nothing to be made of. Both were written down as honest failures. They were the start of a list of special cases. So the disc inverts the picture instead of contributing to it. Every pixel inside it is swapped, ink for paper, after the threshold and before the colour, which puts the mark exactly in register with the screen and lets the tint pass reprint the reversed dots for free -- so it comes out in the block's own red where the field is paper and paper where the field is inked, and no stage below it knows it exists. That is the whole of the fix. An inversion does not want two densities to tell apart; it wants one and the opposite of it, and that is available under every screen at every ruling. Under SOLID the block is a flat plate and the mark is a clean disc of paper. Under BILLBOARD it is a handful of enormous dots, photographically reversed. Under a line screen it is the stripes running backwards inside a circle. The wipe and the solve flash already invert the picture this way; this is that, in a circle, and it is cut in buffer pixels rather than layout units, so the edge of the disc is as fine as the screen it reverses.
- commit
- e56203e45ddf65f2dbbd59b3076d9963df297df8
- parent
- e5d8739b3e9acee8c181f6a9c67c44b109139317
- author
- Dami <dami.etoile@gmail.com>
| M | README.md | 35 | ++++++---- |
| M | mockup/ | 108 | ++++++++++++++++++++---------- |
| M | mockup/ | 2 | +- |
@@ -164,23 +164,32 @@ rather than as a share of the height that happened to work at 852 tall. On a 360
164164 share came up seven units short and drew the UNDO/RESET row fourteen pixels below the bottom of the165165 screen. The head gives way first, since it is air above the board rather than anything in it.166166 167-**The goal block's mark is made of the shading, not stamped on it.** It used to be a struck ring167+**The goal block's mark is the shading reversed, not stamped on it.** It used to be a struck ring168168 in bare paper, and a struck ring is an opinion about colour: stamping paper through the middle of a169169 shaded block fought whatever screen the rung drew, and a hard outline across a Ben-Day field or a170170 hatch reads as damage rather than as a mark. Under almost every screen, it did.171171 172-So it is a *tone* instead — a disc laid down before the threshold and put through the same screen173-in the same ink, so what shows is a change in the density of the dots already there. An inlay174-rather than an inscription, and it cannot clash with a pattern it is made of. Under a line screen175-it appears as the rules themselves bulging; under diffusion, as a solid roundel in a dotted field.176-177-Denser rather than thinner, which took a look to settle: thinning it was the first instinct and it178-vanished, because at a coarse ruling the disc is barely wider than one cell of the screen and there179-is no room inside it for a change in density to show. Going the other way needs no room — the disc180-simply runs out of paper before the field around it does. It still fades at the coarsest rulings,181-which is what a coarse screen does to any fine detail, and is a far better failure than fighting182-the pattern. Under SOLID it is absent, honestly: that screen discards tone by definition, so a mark183-made of tone has nothing to be made of.172+So the disc *inverts* the picture instead of contributing to it. Every pixel inside it is swapped,173+ink for paper, after the threshold and before the colour — so the mark is the exact negative of174+whatever screen the rung drew, in register with it, and comes out as the block's own red where the175+field is paper and paper where the field is inked. Under a line screen it is the stripes reversed176+inside a circle; under diffusion, a light disc carrying red dots in a red field carrying light177+ones. The wipe and the solve flash already invert the picture this way. The goal block does it in a178+circle.179+180+It was a *tone* before that — a disc laid down before the threshold and put through the same screen181+in the same ink, so what showed was a change in the density of the dots already there. The same182+idea, one step short: an inlay cannot clash with a pattern it is made of, but it does need room183+inside itself for a density to differ. At a coarse ruling the disc is barely wider than one cell,184+so it thinned out to nothing exactly where the screen was boldest, and under SOLID it could not185+appear at all — that screen discards tone by definition, and a mark made of tone then has nothing186+to be made of. Every fix for that was a special case about to be written.187+188+An inversion needs no room. It does not want two densities to tell apart, it wants one and the189+opposite of it, which is available at every ruling and under every screen — including SOLID, where190+the block is a flat plate and the mark is a clean disc of paper, and BILLBOARD, where the block is191+a handful of enormous dots and the mark is those dots photographically reversed. Same disc, same192+place, same size; the structure did not move, and the clarity stopped being something to arrange.184193 185194 **Toolbar icons carry their boxes.** RECORD and SETTINGS are a bar chart and a pair of sliders in186195 hard-ruled squares rather than words — the way a system of this vintage drew a toolbar, and the@@ -249,6 +249,41 @@ function applyTints(dst, w, h) {
249249 }250250 }251251 252+/* Discs to reverse once the picture has been thresholded and before it is253+ * reprinted in colour, which is the mark on the goal block and nothing else.254+ *255+ * Between those two passes on purpose. Run after the colour it would have to256+ * know what a piece's ink and its second plate were and swap three things257+ * rather than two; run here it is the same ink-for-paper swap the wipe and the258+ * flash already do, and the tint pass that follows reprints the reversed dots259+ * for free -- so the mark comes out in the block's own colour, in register with260+ * its screen, and no stage below this one has to know it exists.261+ *262+ * Sprite-local by construction: drawBlock is the only thing that fills it and263+ * blockSprite is the only thing that calls drawBlock, so it is emptied around264+ * the one place it is used rather than living across a frame. */265+const inverts = [];266+function applyInverts(dst, w, h) {267+ for (const v of inverts) {268+ // In real pixels, like the screens themselves -- so the disc is cut on the269+ // same grid as the dots it reverses, and gains its edge back at density 3270+ // rather than being quantised to layout units and scaled up blocky.271+ const cx = dpx(v.cx), cy = dpx(v.cy), r = dpx(v.r);272+ for (let dy = -r; dy <= r; dy++) {273+ const y = cy + dy;274+ if (y < 0 || y >= h) continue;275+ const half = Math.floor(Math.sqrt(r * r - dy * dy));276+ const x0 = Math.max(0, cx - half), x1 = Math.min(w - 1, cx + half);277+ const row = y * w;278+ for (let x = x0; x <= x1; x++) {279+ const p = dst[row + x];280+ if (p === INK32) dst[row + x] = PAPER32;281+ else if (p === PAPER32) dst[row + x] = INK32;282+ }283+ }284+ }285+}286+252287 /* The second plate.253288 *254289 * Driven to the primary the colour is already mostly made of, with the other@@ -772,45 +807,45 @@ function drawBlock(c, x, y, w, h, cls, opt = {}) {
772807 }773808 // The mark on the goal block: the one piece whose identity matters.774809 //775- // It used to be a struck ring in bare paper, and a struck ring is an opinion776- // about colour. Stamping paper through the middle of a shaded block fights777- // whatever screen the rung drew -- a hard outline across a Ben-Day field or a778- // hatch reads as damage rather than as a mark, and it did so under almost779- // every one of them.810+ // It is the same disc it has always been, in the same place, at the same811+ // size. What changed is what the disc *does*. It was a tone -- a denser patch812+ // laid down before the threshold and put through the screen with everything813+ // else, so what showed was a change in the density of the dots already there.814+ // That is a lovely idea and it is at the mercy of the screen it is made of:815+ // it needs room inside itself for a density to differ, so it thinned out at816+ // the coarse rulings where a disc is barely wider than one cell, and under817+ // SOLID it could not appear at all, because that screen discards tone by818+ // definition and a mark made of tone then has nothing to be made of.780819 //781- // So it is not drawn on top of the shading at all. It is a *tone*: a disc782- // laid down before the threshold and put through the same screen, in the same783- // ink, so what shows is a change in the density of the dots already there.784- // An inlay rather than an inscription, and it cannot clash with a pattern it785- // is made of.820+ // So the disc inverts the picture instead of contributing to it. Every pixel821+ // inside it is swapped, ink for paper, after the threshold and before the822+ // colour -- so the mark is exactly the negative of whatever screen the rung823+ // drew, in register with it, and comes out as the block's own colour where824+ // the field is paper and paper where the field is inked.786825 //787- // Denser rather than thinner, which took a look to settle. Thinning it was788- // the first instinct and it disappeared: at a coarse ruling the disc is789- // barely wider than one cell of the screen, so there is no room inside it for790- // a change in density to show. Going the other way needs no room at all --791- // the disc simply runs out of paper before the field around it does, and a792- // solid roundel inside a dotted one reads at every ruling because it is not793- // competing with the screen, it is the absence of it.826+ // Which is why this is not a hack and the tone was starting to be one. An827+ // inversion cannot clash with the pattern, for the same reason the old inlay828+ // could not: it is made of it. But it also cannot run out of room. There is829+ // no ruling coarse enough to wash it out, because it does not need two830+ // densities to be distinguishable -- it needs one, and the opposite of it.831+ // Under SOLID, where the block is a flat plate of colour, it is a clean disc832+ // of paper. Under BILLBOARD, where the block is four enormous dots, it is833+ // those dots photographically reversed. The wipe and the solve flash already834+ // invert the picture this way; the goal block simply does it in a circle.794835 //795- // Plotted span by span rather than filled as an arc: a filled path is796- // anti-aliased, and every half-covered pixel would land on some in-between797- // tone and become its own group in the area pass.798- //799- // Under SOLID it does not appear, and that is the honest outcome rather than800- // a gap: that screen discards tone by definition, so a mark made of tone has801- // nothing to be made of. The block is still the only 2x2 and the only red.836+ // Recorded rather than drawn, because a canvas cannot express "swap these837+ // pixels for their opposites" and the threshold has not run yet in any case.838+ // Plotted span by span when it is applied, for the reason it always was: a839+ // filled arc is anti-aliased, and half-covered pixels are not a thing a840+ // one-bit picture has anywhere to put.802841 if (opt.device) {803- const cx = Math.round(bx + bw / 2), cy = Math.round(by + bh / 2);804- // Generous, because the disc has to be wider than the screen's own period805- // before it can read as a shape at all. It still fades away at the coarsest806- // rulings, which is what a coarse screen does to any fine detail and is a807- // far better failure than fighting the pattern.808- const r = Math.max(3, Math.round(Math.min(bw, bh) * 0.26));809- c.fillStyle = grey(Math.round(tone * 0.3));810- for (let dy = -r; dy <= r; dy++) {811- const half = Math.floor(Math.sqrt(r * r - dy * dy));812- c.fillRect(cx - half, cy + dy, half * 2 + 1, 1);813- }842+ // Unchanged, and generous for a reason that has changed: it used to have to843+ // clear the screen's own period before it could read as a shape at all. Now844+ // it only has to be large enough to be a mark rather than a speck.845+ inverts.push({846+ cx: bx + bw / 2, cy: by + bh / 2,847+ r: Math.max(3, Math.round(Math.min(bw, bh) * 0.26)),848+ });814849 }815850 if (cls && PIECE32[cls]) {816851 const p = PATTERNS[opt.shading];@@ -857,6 +892,7 @@ function blockSprite(w, h, cls, opt) {
857892 cx.setTransform(DENSITY, 0, 0, DENSITY, 0, 0);858893 859894 const outer = tints.splice(0); // the sprite has its own tint space895+ inverts.length = 0; // and is the only thing that marks one860896 fill(cx, 0, 0, sw, sh, T.tray);861897 // Resolved here rather than read off the module inside drawBlock, so a sprite862898 // asked for in another screen -- the atlas previews each row in its own --@@ -865,8 +901,10 @@ function blockSprite(w, h, cls, opt) {
865901 const img = cx.getImageData(0, 0, pw, ph);866902 const buf = new Uint32Array(img.data.buffer);867903 shade(buf, buf, pw, ph, shading, INK32, PAPER32, ruling);904+ applyInverts(buf, pw, ph); // the mark, before there is a colour868905 applyTints(buf, pw, ph);869906 tints.length = 0; tints.push(...outer);907+ inverts.length = 0;870908 871909 // Keep only the block and its shadow; the rest was scaffolding for the872910 // threshold and would otherwise blank a neighbour.