Replaces: the circuit entry in oracle-games-pack.js (today: tags: ['maze','chase','collect','pursuit','levels'], icon ๐พ, "Drain every node. Four sentries would rather you did not." โ a Pac-Man clone wearing an electronics name).
Keeps: the id circuit and the name Circuit. The owner's complaint was never that the name was wrong โ it was that the game did not match the name. Renaming would dodge the note instead of answering it.
New one-line pitch: *Lay copper. Current takes every path you give it โ you only choose the proportions.*
> Status of this document. Nothing here is argued from taste alone. Every number below was measured from a working prototype, and every claim that turned out to be false when measured is recorded as such. Prototype: โฆ\scratchpad\bld\ (circuit.js 1,303 lines, playable at index.html; probe.js / scenes.js / shorttest.js are the headless measurement harnesses; FINAL-PROBE.txt is the raw run). No product file was touched by this run.
---
Three complete designs were built and measured. All three are continuous simulations, all three
correctly refuse the logic-gate trap, and all three are honest about their own faults. They are
not equally right for this brief.
A wins, on the owner's criteria in his order.
1. Genuinely about current flow. All three simulate it; only A makes the player *make* the
circuit. In B the player never manipulates anything electrical โ they hold one of seven
buttons and the board is scenery. That matters against the actual complaint, which was that
the game did not look or behave like the thing it is named after. "You drew that copper and
the current you gave it went everywhere at once" is an answer; "you held a button and seven
needles moved" is a weaker one. C is the same family as A with less board identity โ its
render reads as *perfboard*, a dotted lattice, not a fabricated PCB.
2. Fun at 30 seconds and at level 50. This is where B fails hardest, and its own bench says
so: the naive move โ touch whatever is worst โ measures **three times worse than doing
nothing** (dies at 5.5 s vs 17 s). A game whose intuitive first action is actively punished
has a comprehension cliff in front of the fun, on a phone, in an arcade. A's first thirty
seconds are a drag and a light coming on, and it teaches current division wordlessly by
letting the player watch a lit module dim when they add a second path.
3. Phone. B wins this one outright โ press-and-hold needs no precision at all. A is
acceptable (a 38 px routing pitch is a lane you draw *along*, not a target you must hit) but
carries the real risk in the set: dwell-for-width, untested by a thumb. It is de-risked by a
graft below rather than waved at. C is worst here: stroke-*speed* as a gauge means the game
demands slowness at exactly the moment it has made the player panic.
4. 60 fps with this art. A is the only one with a measured answer that is comfortably inside
budget, and it is the only one that found a real performance bug by measuring
(91.1 ms โ 1.10 ms, ยง11).
5. Different from the 34 shipped. All three clear this. None of them is reachable by
reskinning anything in the pack.
Two decisive facts, verified against the real files rather than taken from any design's prose:
brief agreed. Both are wrong. oracle-games.js lines 1841โ1949: `WANT_PTR = !!spec.pointer ||
TKIND === 'dragX'; pointerdown calls cv.setPointerCapture(pid) and sets g.pdown` and
g.tapN; pointermove writes g.ptrX/g.ptrY in canvas space *before* the pid === null
guard, so a captured drag keeps reporting after the thumb leaves the canvas; pointerup is
also bound on window so a release off-element still ends the gesture. That is continuous
free-form drag with capture, available today via spec.pointer: true. This deletes A's
weakness #8 and, with it, the need to touch a shared file another workflow is editing.
measured scenes, and a probe that *contradicts its own designer three times* โ copper does not
run away (positive temperature coefficient is a ballast: two parallel routes converge and go
flat, FINAL-PROBE.txt ยงG); a uniformly thin trace *cannot* burn (it chokes its own current
and settles at heat 0.50, ยงE control); and modelling only diagonal crossings meant 0 of 7 vias
were ever spent. A design that publishes the measurements that killed its own ideas is the one
to trust.
What B and C were right about, and what is now grafted in: ยง13.
---
Not one of the 34 shipped games *simulates* anything. Every one is discrete-state โ turns, tiles, toggles, collisions. snake, blockescape, slide15, 2048, lightsout, minesweeper, nonogram, sudoku, ricochet, pulse, simon, reaction, floppykai, wallrun, flipside: all of them resolve to integers on a grid. A continuous real-valued network where one local edit changes every value on the board is a mechanic the pack does not have in any form, and it is not reachable by reskinning any of them. That is the reason to build this rather than the eleven other "circuit game" ideas, all of which collapse into something already shipped (logic gates โ lightsout XOR parity; deduce-from-edge-clues โ nonogram; tap-in-phase โ pulse; aim-and-bank โ ricochet).
---
A board arrives unrouted: a 5 V input terminal with a fuse, a hatched ground pour along the foot, a scatter of ground stitching vias, and two to five power modules that each need a minimum current before their PGOOD indicator lights.
1. Route. Drag copper from a rail pad toward a module. Current begins flowing the instant the loop closes.
2. Watch it divide. Every new path re-divides the current *everywhere*, in proportion to conductance. Feeding one module well starves its neighbour. There is no "correct path"; there is a budget.
3. Hold. When every module is at or above demand simultaneously, a BRING-UP bar starts filling. Hold for ~3 s and the board passes.
4. Except the copper is warming. Resistance rises as it heats, the delivered current droops, and a board that worked at t=0 can drop a module out at t=20 s. The hold bar resets.
5. Fix it and re-hold. Widen, re-route, cut, add a parallel feed. One more.
The whole loop is 25โ70 seconds. A run is a ladder of boards inside GC.arcade, with the harness's free lives / continue / level banner.
Why it is tense: three constraints that are *mutually coupled by physics*, so satisfying one worsens another.
Measured, on a real board: two modules both fed at w=6 draw 10.86 A against an 11.6 A fuse and both light. The same two at w=12 draw 14.54 A and blow it. Full width everywhere is not a strategy, it is the loss condition.
---
One verb: DRAW COPPER. Linger to make it thicker.
That is the entire input vocabulary: press, linger, drag, release. One finger, one-handed, and identical on a desktop mouse.
Why dwell-for-width is the right gesture, not a gimmick:
Second stroke plates the copper (grafted, and it is the safety net under dwell). Re-drawing
along a segment you already own adds one width step to it. Same gesture, same finger, no mode.
It is physically the right story โ a second pass of the plating head deposits more copper โ and it
is what makes width *deliberate* rather than *emergent*: minimum copper is always what you get by
default, and every step above it is paid for in a stroke you chose to make. It also gives the
player a repair verb that is not "cut it and start again", which is the only other thing they can
currently do to a route that is too thin.
The hook already exists in the prototype and is one line: addSeg() returns 'dup' when the drag
re-enters a segment that is already there (circuit.js:410), and Game.prototype.widen(s, w)
(:437) already does the conductance and copper-budget bookkeeping. 'dup' โ widen(s, s.w + step).
Segments already carry a plated: 0 field for the render.
This is what makes the dwell risk survivable. Ship both. If dwell tests well, the two
compound โ you can lay fat copper in one pass or build it up in two. If dwell tests badly, delete
it and retrace alone is a complete, unambiguous width system that never fires by accident. That is
a much better fallback than the corner toggle this document previously conceded to, because it is
still one continuous gesture and still costs *time*, which is the difficulty knob the whole design
is built on.
Framework note โ corrected. An earlier draft of this document claimed the verb needed a new
GC.arcade touch kind. It does not, and that was checked against the file, not remembered.
oracle-games.js:1845 sets WANT_PTR from !!spec.pointer; :1879 calls setPointerCapture on
pointerdown; :1888 maintains g.tapN; :1890/:1897 write g.ptrX/g.ptrY in canvas space on
both down and move, with the move write placed *above* the if (pid === null) return guard; and
:1949 binds pointerup on window so a release outside the canvas still ends the drag. So
spec.pointer: true plus the game's own handling in step() is the whole input layer, and **no
shared file needs to change.** Snap distance must still be converted from glass px to canvas units
the same way SWIPE_MIN is (24 px measured on the glass, :1846), or it changes meaning with board
size. Hit-testing for cut and for node snap is nearest-target-within-N, never rectangle
containment โ B's prototype settled on 42 design units โ 39 CSS px at 375 wide, and that number is
worth starting from.
---
Portrait, 540ร1080 canvas (fit scales it), a 13ร22 routing grid on a 38 px pitch โ 27.8 real px on a 375-wide phone, which is a routing lane you draw *along*, not a button you have to hit.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ J1 +5V IN F1 11.6A โ terminal block + glass fuse โ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โญโญโญโญโญโญโญโญโญ โ three rail pads โ โ โ โG โG โ ground stitching vias โ โโโโโโ โ โ โ U1 โ โ module, 62ร46, โ โ โโโโโโ demand bar in silk โ โ โG โG โ โ โโโโโโ โ โ โ U2 โ โ โ โโโโโโ โ โโจโจโจโจโจโจโจโจ GROUND POUR โจโจโจโจโจโจโจโจโจโจโจโจโจโจโจโจโจโจโจโจโ hatched, dull, the return โ TP1 TP2 TP3 TP4 J3 โโโโโโโโ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ BRING-UP โฎโฎโฎโฎโฎโฎโฎโฏโฏโฏโฏ FUSE F1 โฎโฎโฎโฎโฎโฎโฏโฏโฏ LV 4 VIAS 5/7 CU 73 T 89
The HUD is silkscreen, rendered by a drawHud function on the dark bezel *outside* the board rectangle โ so the rule "nothing on the board is bright unless it is current or heat" survives contact with the UI. No amps are ever printed. The fuse is a gauge; demand is a row of ticks; the hold is a row of ticks. There is not one number on screen that the player is expected to compute.
A green fibreglass board with brown copper traces, cyan sparks streaming along them from a fused input terminal into little black modules, moving faster where the copper is narrow. Nobody will ask what it is about.
---
No tutorial. No Ohm's law. Four boards, each of which can only teach one thing:
The one concession to words is GCHOW, which sits behind a button and is never in the way (ยง9).
---
All of this is implemented and measured in the prototype.
R = KยทL/w | correct form; K is a game constant, see below |I_max = 4.0ยท(w/3)^0.725 A | IPC-2221's exponent, exactly |k really is half the external k |dh/dt = 0.396ยท(I/I_max)ยฒ โ 0.55ยทh, burn at h โฅ 1 | ฯ = 1.8 s |R(h) = Rโโยท(1 + 0.90ยทh) | โ 0.00393/K over a ~230 K rise |df/dt = 0.42ยท(I/I_fuse)ยฒ โ 0.75ยทf, blow at f โฅ 1 | a real Iยฒt curve, not a trip level |The one honest fudge, named. Real copper is too good a conductor for routing to matter at signal currents. The prototype's copper is roughly 100ร more resistive than 1 oz copper at this scale. This is not a physics error, it is a choice of regime: the game is set in power distribution, the one domain where trace resistance and ampacity genuinely dominate design, and the constant is tuned so a board-sized route has a board-sized effect. Every *law* is exact; one *constant* is a game knob, and it is written down rather than hidden.
*Measured, before and after that calibration was corrected:* at the first value (K=0.0030, 0.62 ฮฉ loads) the width sweep spanned 5.06 A โ 6.57 A and every width satisfied the load. The trace was decoration. At the shipped value (K=0.0075, 0.34 ฮฉ loads) the same sweep spans 4.33 A โ 8.55 A and w=3 starves. That correction only happened because the sweep was run, not reasoned about.
1. Parallel copper self-balances. There is no thermal runaway, and the design must not claim one.
The intuitive escalation โ a hot trace gets more resistive, pushes current onto its neighbour, which heats, cascade โ is wrong for copper in parallel. A positive temperature coefficient is a *ballast*: the hotter branch sheds current. Measured over 25 s on two parallel routes (w=6 and w=4): currents 5.01/2.84 A โ 4.50/2.74 A and then flat, heats settled at 0.28/0.18. The real escalation is the IR-drop droop โ warming copper raises total resistance, delivered current falls, and modules drop out of spec โ plus demand that ramps. That is what makes a board fail at t=20 s, and it is the honest version.
**2. A uniformly thin trace cannot burn. The *weakest link* burns.**
Same reason: a thin trace chokes its own current and settles. Measured control โ a whole route at w=3 settles at 3.34 A / heat 0.50 and never fails. But one rushed node inside a w=9 route carries the full 7.03 A of the fat copper either side of it, and its own resistance is too small a share of the loop to save it: heat 0.02 โ 1.00 and it lets go at t=1.48 s. This is a far better mechanic than the one originally planned. *Your route is only as strong as its narrowest segment*, so the punishment for rushing is specific, spatial, and teaches you to look back over your own copper.
3. Two thin traces beat one fat one, for free, and it costs board space.
Because the ampacity exponent is 0.725 < 1: one w=12 trace carries 10.93 A; two w=6 traces at *identical total conductance* carry 13.22 A โ 21 % more, at twice the area. This is why real power boards run parallel copper, and it is a genuine strategy the player can discover with no tutorial and no unlock. It emerges from the exponent.
The pour is 18 rows from the rail, so a stray trace to the pour is not a short โ it is a resistor (measured 9.1 A into a 10.5 A fuse; nothing happens). Ground stitching vias fix this and two other things at once. Measured, on a working two-module board:
Every branch dims at once, which is the single most legible proof that the rule is proportional. The player has ~0.55 s to cut it. The fuse peaks at 0.87 of 1.0 โ a near miss โ and then the shorting copper, being the weakest thing in the loop, destroys itself and saves the fuse. You lose that route permanently. That window is a skill expression, not a death.
---
Two segments that share a grid node share copper. So a route cannot pass through a node another net already owns without merging with it โ which is sometimes what you want (a second parallel feed) and sometimes a short. This is the "limited board space" pressure, and it is stronger than a crossing rule: the board fills up as nets claim nodes.
When a step would collide with foreign copper, the drag automatically dives to the back layer โ via barrel, dark green trace under the mask, via barrel โ for 2 from the via budget. No new gesture; the decision is *whether to route through there*, which is the interesting one. Going under costs three ways: the budget, +60 % resistance, and half the ampacity (IPC's internal constant). With the budget spent, the step is simply refused.
*This was measured wrong first.* Modelling only diagonal-vs-diagonal crossings meant 0 of 7 vias were used on a level-16 board โ a whole mechanic that never fired. With node occupancy included, the same board spends 6 of 7 and renders three back-layer runs. See scene-8-late-board.png.
---
Each band adds one pressure, not one mechanic. The mechanic count stays at five: route, dwell, cut, dive, hold.
Per-board par on copper used and vias spent gives a 3-star rating and a reason to replay a board you already beat.
---
Three failure states, all legible, none arbitrary:
1. FUSE BLOWN โ total draw exceeded budget. The element parts inside the glass and the glass fogs. Costs a life.
2. TIMED OUT โ the bring-up window closed with a module below spec. The board de-energises. Costs a life.
3. BURNED โ a segment exceeded ampacity. *Not* a loss: it destroys that segment and scars the board permanently. It only kills you if you cannot re-route in the time left.
Lives, the continue panel, the level banner and the result card all come free from GC.arcade (lives: 3, levelBase: 1e9 + explicit g.levelUp() per board cleared, exactly as the current circuit does).
Score. Laddered mode = 1000 base + time bonus + fuse headroom โ copper used โ 40/via, reported via GC.report; copper and vias are also the par the three-star chase is measured against. Burn-in mode is scored in coulombs delivered *(grafted, ยง13.8)* โ charge that actually reached a module while it was inside its window. It is the honest unit, it climbs smoothly, and it is the only number in the game the player is ever shown that is a real physical quantity.
Save/resume in GC.stat: level, per-board star ratings, and the burn-in high score. A board in progress is a small object (segments = {a,b,w,layer,heat}) so mid-board resume is genuinely cheap โ unlike most of the pack.
---
music โ a low 33 Hz-rooted drone bed with a slow filter sweep, tuned so its pitch tracks total current: the board hums harder as you load it. The music is a meter.coin โ a short plating tick per grid cell laid; pitch rises with the dwell width, so you *hear* the copper thicken.merge โ the PGOOD chirp when a module comes up.spawn โ the bring-up hold completing: a rising three-step.explode โ the burn: a filtered noise crack with a fast decay, not a boom.bad โ the fuse: a hard DC thump followed by silence in the drone for 400 ms. Losing the hum is the loudest thing in the game.none is proven to fail it and lookahead is proven to clear it. Physics measurements do not tell you whether a level is a game.---
Real boards have a material identity. These are fabrication finishes, not accent colours.
#8E7B4E | (under mask) | Woven glass in epoxy. Baked once as a 128 px over/under weave at 8 px pitch โ warp bundles with a specular centre, weft in a checker so it genuinely interlaces. Never a noise field. |#0C3626 | 27.0 | A translucent semi-gloss film: the weave must survive *underneath* it (drawn at ฮฑ 0.90, not opaque), and it takes one broad specular lobe, not a bloom. |#12452F / pour #164E36 | 26.9 (pour, same light) | Copper reflects back up through the film, so buried copper reads lighter and warmer. Almost nobody models this; it is what makes the pour and the inner layer look observed rather than invented. |#5A3618 | 36.0 | Deliberately oxidised. Not taste โ see ยง10.3. |#2E1B0B | โ | An etched edge is darker *and redder* than the face. |#C8A951 โ #F0DFA0 | 63.0 | Pale, slightly green-gold. Not yellow. |#AEB4BE | โ | Cold against the warm copper. Two metals on one board is what stops everything metallic reading as "the same shiny thing". |#E4E1D6 | 78.8 | Never #FFFFFF. Matte, and *bled*. |#39D7FF core #B8F4FF | 91 | Saturated cyan, small core. |#E0913A โ #FFF3D2 | 73.6 | Amber โ straw. Never red โ see ยง10.4. |#C4A86E | 37.4 | Delaminated mask showing tan FR-4. |#7CFFB0 | 91.2 | Green, because a PGOOD LED is green on every real board โ and because it must not be the same colour as charge. |Key at upper-left, azimuth 135ยฐ, elevation ~35ยฐ, L = normalize(โ0.577, โ0.577, 0.577). Everything derives from it:
rgba(0,0,0,0.50), one width wider than the object;rgba(255,240,215,0.18) bevel on the top edge only;rgba(0,0,0,0.30) at the far corner.A copper trace is four passes plus grain, and removing pass 1 or 3 is exactly what turns it back into an orange rectangle:
1. Contact shadow, width+1.4, offset along the light. Copper stands 35 ยตm proud of the board and it *occludes*.
2. Oxide flank, width+1.3, #2E1B0B.
3. Face โ a gradient ACROSS the trace, not along it. Every segment is a straight line, so the correct anisotropy is a perpendicular linear gradient: lit flank โ face โ dark flank. *This is the second version.* The first build put the specular as a centred stripe at ฮฑ 0.42; it measured **L\* 62.7 โ a grey blade โ and made hot copper vs cold copper 1.04:1, invisible**. Rolled foil grains along its length; the highlight is a flank, never a blob.
4. Grain, the baked noise tile at ฮฑ 0.26. At 0.5 it lifted the whole trace out of budget.
Occlusion is doing real work here: unenergised copper sits at only 1.37:1 against the mask and is still perfectly findable, because it casts a shadow. Occlusion substitutes for luminance contrast โ that is what lets dead traces stay quiet without becoming invisible.
v โ J = I/w, so the *same* current in *narrower* copper visibly moves faster. Two independent channels encoding two different quantities.scene-2-mid-drag.png: the trace visibly thickens toward the tip.Everything that is *board* lives between **L\* 19 and 37**: component bodies 19.2, ground pour 25.9, mask field 27.0, FR-4 rim 27.8, inner-layer ghosting ~28, unenergised copper 36.0. Nothing on that list ever changes.
The rule, measured rather than asserted: the pixels above L\* 70 on the board are carrier, copper_hot, led_good and silk_text, and together they cover 0.44 % of the board area. Bright is a *signal*, not a surface. Silkscreen is the one static exception โ it is the brightest non-emissive thing on a real board โ and it was pulled from ฮฑ 0.92 to 0.78 when it measured L\* 85.7 / 7.2:1, thirty characters of static ink shouting louder than a lit rail.
Charge and heat are separated by HUE, not brightness, because they are both bright and they mean opposite things. Measured separation: 173ยฐ (cyan 213ยฐ vs amber 40ยฐ). PGOOD sits at 144ยฐ, a third meaning with a third hue. And carrier cores are drawn source-over while only their halo is lighter, because lighter over a warm base saturates toward white and arrives as an orange-white blob with no hue left.
Tented vias (soft green domes, no hole โ deliberately *quiet*) mixed 2:1 with open plated barrels, both on half-pitch, because scattered detail reads as dirt. Unpopulated 0805 land patterns, most of them with no silk outline โ a box round every one read as confetti. Ground stitching vias with four-spoke thermal reliefs and a silk G. Thermal relief where a module leg meets the pour. Thieving copper in the pour. Fiducials. Test points TP1โTP4 with silk labels. A J3 through-hole connector footprint with a square pin-1 pad. Inner-layer copper ghosting with 45ยฐ doglegs, ฮฑ 0.38 โ the single largest "this is a real two-layer board" upgrade for the least work. Board legend KAI-7 PWR DIST REV C and the fuse rating F1 11.6A, so the board tells you its own budget before you route anything.
45ยฐ mitring is the strongest "this is a PCB" tell and it is nearly free โ one pass over the point list, radius 7 px, miterLimit 4. Orthogonal-only reads as a wiring *diagram*; mitred reads as a routed *board*.
No marking below ~5 px. Illegible text reads as dirt and actively cheapens a render. Modules get their designator in silkscreen *outside* the outline, the way a fab places it, and a bare thermal slug instead of a fake part number.
Silk outlines are corner ticks, not boxes โ four brackets say the same thing at a quarter of the ink.
Design targets, all met in the shipped prototype:
---
Desktop headless Chrome, 540ร1080 buffer at 375 CSS px / dpr 2, level 10, four modules, 36 segments, 41 nodes:
One real performance bug was found by measuring, not by reading. The static bake flag was being set by the network rebuild, so *every single segment the player laid* re-rendered the whole board โ weave, ghosting, hatch, two blurred silkscreen passes. Max frame was 91.1 ms. Separating "the board changed" from "the copper changed" took it to 1.10 ms. Bake procedural texture once at load; never inside the frame loop; and ctx.filter='blur()' belongs only on static content.
---
1. Dwell-for-width is still the single biggest risk in the design, and no thumb has touched it. It could easily feel *laggy* ("why won't my trace get fat") or *accidental* ("I stopped to think and got copper I didn't want"). Retrace-to-plate (ยง2) is now the answer to that, and it downgrades this from "the design may not work" to "one of two width verbs may not survive" โ but it does not remove the risk, it insures it. This is still the first thing that goes in front of the owner, and the question to ask him is narrow: *did the trace ever get fatter than you meant it to?*
2. A partially-routed net silently does nothing. Measured on a level-16 board: routes that ran out of vias mid-drag left dead copper that *looks* finished and carries 0 A. Unconnected copper must be drawn differently โ unplated, no carriers, and the module's demand bar flat at zero โ or players will stare at a route that looks right and is not. This is specified but not yet built.
3. The physics is calibrated, not real. Copper is ~100ร too resistive. It is stated openly and it is the right call, but an electrical engineer will notice within a minute, and the game will be quietly wrong about a real quantity in a genre that attracts people who care.
4. The mid-board period may be dead time. Between "I have routed everything" and "the hold bar fills" there can be several seconds where the correct play is *to do nothing and watch*. That is thematically perfect (it is a bench burn-in test) and it may be boring. Rising demand and switched loads exist partly to paper over this, and they arrive at level 25.
5. Levels 29โ44 are asserted, not prototyped. Diodes, capacitor banks, ground bounce and inductive kick are described here from first principles. Only routing, division, heating, burning, fusing, vias and the hold have actually been built and measured. The back half of the curve is a plan, and plans of this kind are usually 30 % wrong.
6. The scar reads as a component. A pale tan capsule on a green board is legible as *damage* in motion, but in a still frame it can be mistaken for a part. It wants to be more irregular and less symmetric than a stroked line.
7. Deuteranopia gets the worst possible hue triple. Structural mitigations are real (motion, form, position), but a colour-blind player will have a meaningfully harder time than a sighted one, and no amount of shape language fully fixes cyan-vs-green.
8. ~~It needs a framework change.~~ Withdrawn โ this was false, and it is worth recording why. Both this document and the research brief asserted that no GC.arcade touch kind gives continuous free-form drag with capture, and therefore that shared code had to change. Reading oracle-games.js:1841โ1949 shows spec.pointer: true already provides exactly that, including capture and an off-canvas release. Two of the three competing designs checked the file and got it right; this one reasoned from the research summary and got it wrong. The lesson generalises past this game: a claim about the framework that was never read out of the framework is a guess.
9. It is the slowest game in the pack. A board takes 25โ70 seconds. Every other game in the arcade is a 30-second reflex loop. This may simply be a different appetite than the one the Game Center currently serves, and the burn-in mode is the only part of it that behaves like an arcade game.
10. Procedural boards may produce dull ones. The generator here is a placeholder โ column table plus a stitch list. A board grammar that reliably forces interesting collisions (rather than five parallel straight lines) is real design work that has not been done, and without it level 20 looks like level 5 with more modules.
11. **The difficulty curve has never been *played*, only reasoned about โ and this is the largest methodological hole in the design.** Every number in FINAL-PROBE.txt is a physics measurement. Not one of them answers *is level 16 winnable*, *does doing nothing win*, or *is the correct move ever the obvious one*. The competing design measured exactly that and it changed its curve; this one has not. ยง13.1 is the fix and it should be built before any level past 8 is tuned.
12. The grafted suppression verb collides with cut. Both are press-and-hold. They must be disambiguated by what is under the finger (copper โ cut; a gate body โ suppress), never by duration โ two different hold-lengths on one surface is precisely the mode-switch this design has otherwise refused. If that disambiguation feels ambiguous in the hand, suppression is the thing that goes, not cut.
13. The bottom third of the board is empty and empty mask reads as unfinished, not as restraint. Visible in every scene frame: below the last module there is silkscreen, four test points and a connector, and then nothing. Restraint means *dull*, not *absent*. That region needs inner-layer ghosting, thieving copper, a second footprint and legend โ all of it below L\* 30 so it fills the frame without competing for attention.
---
Neither losing design was wrong; they were right about different things. Everything below is
adopted with attribution, and the two items that contradict this document's earlier claims are
marked as corrections rather than quietly patched.
B's circuit-bench.js does not measure physics. It measures whether the game is a game, by
running scripted players against every level and asking four questions:
1. Is doing nothing fatal? (If not, the level is dead time wearing a difficulty label.)
2. Can a skilled policy clear it? (If not, it is not tuned, it is broken.)
3. Is the correct action ever non-local โ on a branch other than the one visibly in trouble?
4. What does the sim cost per frame under real play, not on an idle board?
Crucially it runs every policy with human latency baked in: re-decide every 200 ms, and the
decision lands 150 ms late. The numbers are therefore not a superhuman bot's.
This is the missing half of this design's evidence. Build the equivalent for routing, with three
policies โ none (never touches the board), greedy (always widens the starved module), and
lookahead (widens whichever edit most raises the *minimum* margin across all modules). Then:
none must fail every level from 3 onward. If it survives, that board has dead time in it,and weakness #4 is real and measurable rather than a worry.
lookahead must clear to ~50 and start failing beyond it. If it clears 80, the curve is flat.greedy must be measurably worse than lookahead from about level 8, because that is thelevel where "help the one that is starving" stops being the right answer and "stop feeding the
one that is fat" starts being it. B measured its equivalent crossover at level 12 and found the
correct target was on a *different* branch than the one in trouble 100 % of the time past it.
If routing has no such crossover, the game is a search for one best path โ i.e. a maze โ and the
whole strategic argument in ยง0 collapses.
B also recorded that its first scoring function was wrong and produced a fake curve: a policy
protecting the *average* margin played no better than doing nothing, and only protecting the
*minimum* produced a real skill gradient. Take that as given rather than rediscovering it.
B's entire game is one verb this design does not have: **press and hold a MOSFET to choke its
branch, and every other branch gets stronger.** As a whole game it has a comprehension cliff; as a
level-25 unlock inside this one it is close to perfect, because:
division does the rest. No solver change โ it is one conductance being driven by input.
verb instead of with a rising number. This is a better fix than "switched loads", because a load
that switches itself is content happening *to* the player; a gate is content the player operates.
close ฯ = 90 ms, open ฯ = 260 ms โ are worth taking verbatim, because the asymmetry means you
*commit* to a hold, and it makes a 180 ms tap already an 86 % pinch without needing a
tap/hold distinction at all.
Its leash is the hold-up capacitor, also from B: a starved module runs on stored charge for a
few seconds before its PGOOD drops (B measured ~4.9 s at total outage). That is what stops the
player parking on one gate forever, and it is a far better introduction to capacitance than this
document's level-33 "charge a bank and dump it" โ accumulation and depletion become something the
player *causes with their own thumb* rather than a thing they wait for.
Replaces the current level 25โ28 band. The capacitor-bank band at 33โ36 stays, and now lands on a
player who already understands stored charge.
B's strongest single argument is a one-line bench: sweep the source stiffness. At 99.5 % ideal, a
gate closing gives its siblings +0.17 % โ no game exists. At 66 %, +9.88 %. Set the source
impedance to zero and there is literally nothing to play.
Run the routing equivalent and put the number in this document: **hold the fuse and the loads
constant, drive rail impedance to zero, and confirm that widening one branch stops affecting the
others.** If it still affects them, the coupling is coming from something other than the physics
and needs finding. This is the cheapest possible answer to "could this be reskinned?", and it is an
answer with a number in it.
B measured its silkscreen at 2.20:1 against the mask and, rather than brightening it, ruled that
no decision may depend on reading one. This document's silk measures better (5.91:1, dL\* 51.8,
FINAL-PROBE.txt ยงJ) but the rule is adopted anyway: reference designators, the revision string
and the fuse rating are second-look detail. If the player ever has to *read* U3 to know which
module is starving, the state is being carried by text and the render has failed. Modules are told
apart by position, by the colour of their PGOOD dot, and by the copper going into them.
This document's endless mode says "demand ramps forever". C's formulation is better and should
replace it: **the supply itself ramps, I(t) = Iโ ยท (1 + t/T), T falling from 50 s to 20 s across
the run.** One number, physically honest, and every other pressure falls out of it rather than
being added to it โ more current means more IยฒR in every trace, which means ampacity crossings and
burns, which means the fuse budget tightens, all without a single new rule. It also means the
endless mode needs no new content at all, which is what makes it shippable in the first version.
C measured the burn timing as a table rather than a threshold: **1.2ร ampacity runs hot forever and
never fails; 1.5ร burns at 6.6 s; 2ร at 1.9 s; 3ร at 0.7 s.** That shape is what makes "hot" a
*warning the player learns to read* instead of a countdown they cannot see. Re-measure it against
this design's constants (HEAT_A = 0.396, HEAT_B = 0.55, AMP_EXP = 0.725) and **treat the
1.2ร-is-survivable rung as a requirement, not an outcome** โ if every overheating trace eventually
burns, the amber state means "you have already lost" and the player has no reason to look at it.
The most valuable playtest datum either runner-up produced, and it was a *failure*: C's scripted
policy built a ballast path to dump surplus current and killed its own run in 3.0 s, because a
19-segment thin path to ground is ~0.20 ฮฉ against 0.35 ฮฉ load branches โ so the dump took 46 % of
the budget and starved everything it was meant to protect.
This applies verbatim to the ballast and keep-out content at levels 13โ20. **If a bot with perfect
information gets it exactly backwards, a first-time player certainly will.** So ballast is not
introduced as an affordance and explained; it is introduced as a **board that is already wrong in
that exact way**, where the player's first move is to *narrow* the dump and watch the modules come
back. Teach it as a repair, not as a tool.
C's justification for its cut gesture is better than this document's and costs nothing to adopt:
the iron adds where it moves and lifts where it lingers. That is why hold-to-cut is intuitive
rather than arbitrary โ park a soldering iron on a trace and it comes up. State it that way in the
how-to guide. Take also C's live ring on the stroke head: whatever the width verb is doing, it
is shown at the fingertip, in the material, never in a corner of the HUD.
C's score unit is also the right one: coulombs delivered โ charge that actually reached a
module inside its window. It is the honest quantity, it is the same thing B scores in
(amp-seconds), and it is what the burn-in mode should count. The laddered mode keeps board-pass
plus a copper/via par for the three-star chase.
called it "UI wearing a solder-mask coat" and it is โ but it is *load-bearing* UI there, because
comparison of seven branches needs a common baseline. This design has two to five modules, each
with its own demand bar drawn *at the part*, and that is the more beautiful answer. It is only
available because there are fewer things to compare. If module count ever passes five, this
decision has to be revisited rather than defended.
exactly the moment it has manufactured panic. Dwell has the same failure mode in a milder form,
which is why ยง2 now ships retrace alongside it.
authorship is the reason this design has a par chase and a level-50 that does not look like
level 5.
---
โฆ\scratchpad\bld\circuit.js | The game: solver, board model, input, renderer. 1,303 lines. |โฆ\scratchpad\bld\index.html | Playable. Open it and drag on the board. |โฆ\scratchpad\bld\probe.js | Physics tables + pixel measurement + performance. |โฆ\scratchpad\bld\FINAL-PROBE.txt | The raw run every number in this document comes from. |scene-1โฆ10-*.png | Level 1 untouched ยท mid-drag ยท starved ยท holding ยท current division ยท about to burn ยท scarred ยท a late board with back-layer routing ยท desktop ยท the short circuit. |The runner-up prototypes, kept because their benches are being grafted (ยง13):
โฆ\scratchpad\circuit-core.js ยท circuit-bench.js | Design B โ the 7-gate board and its policy bench. | ยง13.1 โ the none / panic / lookahead harness with human latency is the model for the routing bench, and circuit-frame-02.png is the densest copper render of the three. |โฆ\scratchpad\hotcopper.html ยท hc_probe.js | Design C โ the ramping-supply board. | ยง13.5โ13.7 โ the ramp law, the thermal knife-edge table, and the run its own ballast policy killed in 3.0 s. |โฆ\scratchpad\carrier-test.html ยท carrier-01.png | Six carrier treatments, measured. | The finding that decided the palette: a cyan carrier on *bright* copper scores 2.43:1 and loses its hue entirely; on oxidised copper, 6.25:1 and it stays cyan. The conductor must be darker than the charge. |โฆ\scratchpad\pcb-01/02/03.png | The three material-study frames. | The luminance-budget progression: naive โ materials right / signalling wrong โ budget corrected. |---
> Scope. ยง10 states *what the board should look like* and measures the result. This section
> states *how each material is made* and measures the cost. It is a second, independent
> prototype (โฆ\scratchpad\pcb\) written to answer one question: can a board that reads as a
> photographed PCB โ layer stack, plating, weave, vias, slivers โ hold 60 fps on a phone with
> no image files? Yes, with 3.3 ms of a 16.7 ms budget at 375ร812 dpr2. Four claims in the
> first draft of this section were falsified by measurement and are recorded as such.
>
> Environment: Electron, software raster (disableHardwareAcceleration), offscreen,
> muted, headless. Software raster is the pessimistic case and was chosen deliberately โ a
> phone with a working GPU-composited canvas does better, not worse.
A PCB photograph reads as a PCB because of four stacked physical layers, not because of its
colour. Render them in order and the look arrives on its own; author the final colours directly
and it never does.
createPattern(โฆ,'repeat') |multiply of a tint, then a low-alpha source-over scatter |filter:'blur(0.85px)', bled copy under a crisp core |multiply, not a fillSolder mask is a translucent film. Light goes down through it, reflects off whatever is
underneath, and comes back up through it. That is multiplication, and Canvas2D has the
operator:
// mk = mask coverage, opaque tint, with pad apertures punched by destination-out ctx.globalCompositeOperation = 'multiply'; ctx.drawImage(mk, 0, 0); ctx.globalCompositeOperation = 'source-over'; ctx.globalAlpha = 0.34; ctx.drawImage(mkScatter, 0, 0); // the film's own body
Because it is multiplicative, buried copper stays brighter than buried substrate for free.
You do not author "mask over copper" and "mask over substrate" as two greens โ you author one
film, and the copper underneath decides. Three things fall out that authored flats cannot do:
extra colours;
multiply is a no-op andbare plating shows through.
Falsified claim, recorded. The first draft asserted the film would also *win on contrast*.
It does not. Measured on the same region: film pipeline 1.14:1 buried-trace-vs-field, flat
authored greens 1.25:1. The flat version is marginally more legible and looks like a
diagram; the film version looks like a board and responds to state. The film is chosen for
material and for dynamic range, not for contrast, and ยง14.4 buys the contrast back a
different way.
FR-4 weave โ a real plain weave, never a noise field. Tile 64 px, pitch 8 px, bundle 6.4 px.
Three passes: all warp strips laid down, all weft over them, then warp redrawn **clipped to the
cells where (i+j) is even** so it genuinely interlaces. Each bundle is a cylinder โ a
perpendicular linear gradient weft โ warp โ warp โ weft with stops at 0/0.30/0.52/1.0 โ plus
three wrapping hairline striations at ฮฑ 0.13 for glass anisotropy. Then an epoxy flood
(rgba(156,138,87,0.34)) and one crossover-occlusion multiply. Seamless because the tile is an
exact multiple of the pitch.
Copper trace โ four passes, per segment. Passes 1 and 3 are what stop it being an orange
rectangle:
1. contact shadow, width+1.6, offset against the light โ copper occludes;
2. oxide flank, width+1.3, #5A3115 (an etched edge is darker *and redder* than the face);
3. face โ a linear gradient across the trace, not along it (rolled foil grains along its
length, so the highlight is a *flank*, never a blob);
4. grain โ the baked streak tile at ฮฑ 0.26, overlay.
Round caps and joins hide the per-segment seams; 45ยฐ mitring (mitre(), radius 7 px) is one
pass over the point list and is the strongest single "this is a routed board" tell.
Plating โ the finishes differ by VARIANCE, not by hue. This is the finding I would carry to
any other project:
They are 4 L\* apart in mean and measure 1.07:1 against each other โ they are *not*
distinguished by brightness or colour. ENIG is a shallow gradient over a flat fill plus one
thin nickel rim; HASL is a radial dome plus a small sharp hot spot offset toward the key and
jittered per pad. Two finishes on one board is what stops everything metallic reading as "the
same shiny thing".
Falsified claim, recorded. My first ENIG measured **sd 18.8 โ higher than the HASL beside
it**, i.e. exactly backwards, and rendered as a gold ball. Two separate errors: a wide-range
gradient, and a measurement bug (a *square* sample window around a *round* pad is dominated by
the pad/background edge and reports โ17 for every finish). Sampling inside rร0.78 and
flattening the ramp gave the table above. **A material claim measured through a bad window is
not measured.**
Via โ annular ring, then a black bore, then a radial gradient offset away from the key
(light falls into the tube and lands on the *far* wall), then a dark arc on the near rim. The
asymmetry is the entire effect: a symmetric dark circle reads as a dot, not a hole.
Tented via โ a soft dome with a centre sag dimple, measured at 1.17:1 against the mask
field. Deliberately almost invisible: it is texture, not information.
Teardrops โ the fab fillet where a trace enters a pad. Two quadratics from the trace edges
to tangent points at 0.72 r on the pad. Absent from most procedural boards, present on every
real one, and it costs one filled path.
Silkscreen bleed โ screen-printed ink spreads; it is never a crisp vector. Draw to an
offscreen, then compose a blurred copy at ฮฑ 0.62 under a crisp copy at ฮฑ 0.40. ctx.filter
is expensive (2.8 ms full-screen) and therefore belongs only on baked content.
Mask slivers โ the fragile 1โ1.5 px mask webs between fine-pitch pads, with a lit edge on
the key side and a dark edge opposite. One of the most recognisable close-up PCB details and
almost free.
Edge fingers โ hard gold with a per-finger diagonal gradient, a dark trailing edge, a bright
leading edge, and a routed chamfer along the board edge (transparent โ light โ black over
4 px). The chamfer is the tell.
What is cacheable: essentially everything. The entire static board โ substrate, copper,
film, plating, silkscreen, components โ bakes to one offscreen and the frame loop draws it with
a single drawImage.
Caching the board is worth 17ร at phone dpr2 (2.1 ms vs 36.4 ms) and is the difference
between shipping and not. Tile bakes are one-time: weave 90.9 ms, grain 2.0 ms.
Isolated costs at 750ร1624, for budgeting: one full-screen multiply 3.7 ms; 36 four-pass
trace segments 5.9 ms; a full-screen blur(0.85px) silk bake 2.8 ms; 60 ENIG pads
1.5 ms; 60 open vias 2.2 ms; 60 tented vias 0.6 ms; a full-screen weave pattern fill 0.3 ms.
Carrier sprites beat live gradients by 30โ50 %. Bake the halo once to a small canvas and
blit it: 420 carriers cost 3.3 ms as sprites vs 4.2 ms as per-carrier createRadialGradient
(1920ร1080: 5.9 vs 8.9 ms). createRadialGradient inside a per-object loop is the most common
avoidable cost in this kind of renderer.
A composite op inside a hot loop is catastrophic โ measured. The weave tile's crossover
occlusion was 64 per-cell multiply fills on a 64 px canvas and cost 601 ms. Building the
occlusion map with plain source-over and applying it with one multiply gave identical
pixels at 90.9 ms โ 6.6ร (I predicted 70ร and was wrong; the remaining cost is the 64
radial gradients themselves, not the compositing). **Every globalCompositeOperation change
forces a surface read/modify/write. Count them; do not count draw calls.**
Buried copper measured 1.055:1 against the mask field โ invisible. My hypothesis was the
scatter pass. I swept its alpha 0.00 โ 0.80 and the contrast moved from 1.03 to 1.06. **The
hypothesis was wrong and the sweep is what said so.** Two real causes:
1. Flank treatments must be a constant width in PIXELS, not a fraction of the stroke. The
face gradient used proportional stops (0.34 / 0.66), so an 8 px trace was *all flank and no
face* โ its centre sat 75 % of the way into the oxide colour. Deriving the flank fraction
from an absolute 1.6 px (fl = clamp(1.6/w, 0.06, 0.34)) with a plateau between took it to
1.27:1 on its own. This generalises to every bevel, rim and edge highlight in the game.
2. The substrate was too bright to leave copper anywhere to go. Real FR-4 seen from the
mask side is resin-rich and dull. Darkening the weave tile by 0.64 finished the job.
Darken with a TINT, not with grey. A neutral multiply drops chroma with luminance and the
tan cloth turns to concrete (visible in the first study sheet). Multiplying by
rgb(255,238,198)รk is the same darkening with the chroma intact.
Final contrast table, all three resolutions:
The luminance budget holds: under 1 % of the board is bright on a phone. Bright is a
signal, not a surface. Silkscreen at 375ร812 (3.48) sits just under its band and wants ฮฑ 0.82
rather than 0.78 at dpr1 โ the one open tuning item.
Direction, magnitude and rate are three separate channels and each gets its own encoding.
Nothing here is brightness, because brightness is already spent on *heat* and on *importance*.
still frame still shows which way. Asymmetry, not an arrow.
countable, because carriers are routed into a branch with probability equal to its share.
visibly runs faster. Two quantities, two channels, no collision.
capsule whose length โ speed (motion blur as a drawn primitive, not a filter), and the
trace itself warms. Four cues agreeing. A trickle is four well-separated dots crawling on
cold copper.
whole net and the mask relief brightens โ and only then do carriers begin to crawl. That
distinction is physically true, visually rich, and free.
persists when input stops; carriers arriving at the plate are *absorbed* and stop. A pulsing
glow cannot represent a state variable. A column that stays where you left it can.
"Magnitude = spacing" is only a readout while the dots are separable. Past a density they fuse
into a lit bar โ the "glowing line that pulses" failure, arrived at from the other direction.
Measured as peak L\* minus mean trough L\* along the trace centreline (the honest metric;
global max/min is pinned by the brightest core and the darkest gap and stays flat at 0.525
regardless of density, which is why the first attempt at this measurement said nothing):
It is the halo that fuses, not the core. With an 11.7 px halo the troughs have risen to
L\* 50 *at 16 px spacing* โ already a bar with bumps. Taking "trough stays below L\* 40" as the
criterion, the minimum workable spacing is 6 px at halo 0, 9 px at halo 5.2, 14 px at halo 8.3
and ~19 px at halo 11.7 โ a constant ratio:
> halo radius โค 0.58 ร carrier spacing, with an absolute floor of spacing โฅ 6 px set by
> the core diameter. The density ladder (flowtest-ladder.png) confirms it by eye: 6/12/24/48
> carriers over 800 px are countable, 96 is a fusing chain, 140 is a solid bar.
This is a design constraint, not a rendering note. It puts a hard cap on carriers per unit
length, and therefore a surge can never be drawn by adding more dots. Above the cap the
magnitude channel is saturated and the game must switch to speed, capsule length, heat and
trace state โ which is precisely the behaviour that makes a surge read as a surge instead of as
a brighter line. The 200-carrier frame in measure-phone_750x1624_dpr2.png is the failure
preserved: at 2.8 px spacing it is a neon bar.
Honest, from looking at the frames rather than the tables:
1. **The ground pour and bare substrate are 0.4 L\* apart** (26.1 vs 25.7) โ the pour boundary
reads as a *texture* change, not a copper boundary, and the pour is the largest copper
feature on the board. It needs its own separation (target โฅ 1.3:1); ยง14.4's substrate
darkening gets it to 1.31 and that is the floor, not the goal.
2. Pour keep-outs are axis-aligned rectangles and read as floating boxes. A real pour is
poured *around* the routing with a constant clearance. The keep-out must be generated by
offsetting the trace paths, not by punching rectangles.
3. Tented vias read as water droplets โ too glossy and too round. Flatten the dome and cut
the specular.
4. HASL is still too spherical. The variance is now right; the silhouette is not. It wants
an irregular rim, not a circle.
5. The bottom third of the board is empty, exactly as ยง12.13 says. Confirmed visually.
6. The weave is convincing at 4ร and near-invisible at 1ร. That is arguably correct, but it
means 90.9 ms of bake is buying detail most players will never resolve on a phone; a cheaper
2-pass weave should be measured against it before shipping.
7. Everything here is measured under software raster on a desktop. No phone has run it. The
ร6 rule of thumb applied to 3.3 ms says 19.8 ms โ *over* budget โ and the reason I am not
worried is that the phone case is GPU-composited while this measurement is not. **That is an
argument, not a measurement, and it is the first thing to check on real hardware.**
โฆ\scratchpad\pcb\pcb.js | The material library: tiles, trace passes, plating, vias, teardrops, film composite, board bake, carriers. Every technique above. |โฆ\scratchpad\pcb\scene.js | One deterministic board with named probe points. |โฆ\scratchpad\pcb\measure.html ยท probe.js | The bench: bake/frame cost at 3 resolutions, contrast, luminance budget. Electron, offscreen, muted, self-quitting. |โฆ\scratchpad\pcb\sweep.html | The scatter-alpha, copper and substrate sweeps of ยง14.4. |โฆ\scratchpad\pcb\studies.html | The 9-panel material sheet (studies-main.png). |โฆ\scratchpad\pcb\flowtest.html | The carrier-fusion measurement and the density ladder. |RESULT-*.json | Every number in this section, raw. |