Professor Oops

Hoe we het kloppend houden

Hoe we het kloppend houden

Duizenden automatische tests doen wat kinderen doen, en controleren dan of de pagina nog goed uitkomt

Honing die vandaag op een tekening wordt gegoten, moet zich gedragen zoals gisteren. Achter Professor Oops zitten duizenden automatische controles die doen wat kinderen doen: verf gieten, een hamer laten neerkomen, een vuurtje stoken, wachten op schimmel. Daarna kijken ze naar de pagina. De beloften waar een gezin om geeft, zoals “geen enkel dier raakt ooit gewond” en “geen reclame”, zijn ook als controles opgeschreven, zodat het breken van een ervan de testrun laat mislukken.

Geschilderde illustratie van de schilder die bij een oven gehurkt zit en kijkt hoe een waskrijttekening erin goudbruin roostert, met een ijsblokjesbak en een citroen.

Op elke 100 regels spel zijn er bijna 60 regels tests

Bij de release bevatte de code 270 testbestanden, met daarin 2.614 tests en 8.933 losse controles. Dat is 56 regels test op elke 100 regels spel. De volgende update heeft 353 bestanden en 60 regels op elke 100 volgende update. De tests groeiden sneller dan het spel: op de tweede dag van het bouwen waren ze 12% van de omvang ervan.

De meeste zijn niet de gebruikelijke kleine controles op één functie. Veel ervan leggen een echte pagina neer, gebruiken er een echt stuk gereedschap op, laten de simulatie een paar seconden draaien en lezen wat er gebeurde. Hun namen zijn zinnen die je aan de keukentafel zou kunnen nagaan: “een klap plet een tomaat maar geen banaan; een deegroller die hard neerkomt, laat een sinaasappel barsten maar geen appel”. Ongeveer twee op de drie testbestanden laten de echte engine stappen zetten.

In een test beweegt de simulatie in vaste stappen, meestal een zestigste seconde, nooit op de klok. Zo krijgt een drukke computer precies hetzelfde antwoord als een rustige. Een snelle laag laat de zevenhonderd of zo tests weg die langer dan een seconde duren, voor gebruik tijdens het werk. De volledige run voegt een typecontrole van elk bestand toe, een productiebuild en een run in de browser, in Chrome.

Hier is een naad, eerlijk getoond. De controles draaien op onze eigen computers. Er is geen aparte buildserver die een release weigert, dus een mislukte wijziging buiten het spel houden is een werkregel die we met de hand bewaken. Echte telefoons en tablets worden ook met de hand geprobeerd: de automatische runs gebruiken een Chrome op de computer, plus één test van het aankoopverloop op een iPhone-simulator.

Duizenden automatische tests doen wat kinderen doen, en controleren dan of de pagina nog goed uitkomt

Voor technici

Duizenden automatische tests doen wat kinderen doen, en controleren dan of de pagina nog goed uitkomt

Bij de release bevatte de code 270 testbestanden met 2.614 tests en 8.933 asserties tegenover 100.923 regels TypeScript; ongeveer twee op de drie testbestanden van de volgende update laten de echte engine in vaste stappen lopen, meestal van 1/60 s. Zeven WebAssembly-SIMD-kernels hebben leesbare JS-tweelingen waarvan de gekopieerde broncode een vingerafdruk heeft, zodat een veranderde lus de run laat mislukken tot zijn kernel is overgezet en nagekeken; vijf ervan, elk apart getest, komen bit voor bit overeen met hun tweeling. Regels zoals “het scherm raakt de toestand van de engine nooit aan” en “elk voorwerp is een schildering” zijn scans van de syntaxisboom die falen bij een nieuwe overtreding.

Onder de motorkap
Op elke 100 regels spel zijn er bijna 60 regels tests

Onder de motorkap

Op elke 100 regels spel zijn er bijna 60 regels tests

tellingreleasevolgende update
testbestanden270353
it/test-plekken2,6143,458
expect(-aanroepen8,93311,715
testregels56,26372,142
src-regels100,923119,523

Geteld over de testbestanden en de TypeScript van het spel, gegenereerde kernels niet meegerekend. Testgevallen tellen geeft te weinig: veel tests worden in lussen gegenereerd.

Waar de 353 bestanden van de volgende update aan komen (ze overlappen): 258 gebruiken de echte engine, 240 laten hem stappen, 173 sturen de echte gereedschappen aan, 60 de tekencode, 39 de interface, 38 de worker-host, 38 de dieren, 34 aankopen en licenties, ongeveer 33 lezen broncode als data, 8 draaien een kernel tegen zijn leesbare tweeling.

Lagen: een test die langer dan ongeveer een seconde duurt, gaat naar de trage laag, met een marge zodat hij niet heen en weer springt (716 tests in 160 bestanden op de lijst van de volgende update). De volledige run bouwt de kernels en draait elke test één keer, met een typecontrole en een productiebuild ernaast, en daarna de run in de browser in beide threadmodi. Een mislukt bestand krijgt één losse herkansing, die in de samenvatting wordt genoemd.

Typen: de strikte modus van TypeScript, tests ook op typen gecontroleerd; nul any en nul @ts-ignore bij beide revisies. De eerlijke tegenhanger: 86 casts as unknown as in de broncode van de volgende update.

De tests groeiden sneller dan het spelDe release was op 2 nov. De testregels gingen van 12% van de broncode naar 60%.

Van 18 okt. tot 5 nov. groeide de broncode van het spel van 21,3 tot 119,5 duizend regels, en de tests van 2,5 tot 72,1 duizend.

De tests groeiden sneller dan het spel02040608010012018 okt.20 okt.22 okt.24 okt.26 okt.28 okt.30 okt.1 nov.3 nov.5 nov.datum (2026)duizend regelsDe tests groeiden sneller dan het spel02040608010012018 okt.24 okt.30 okt.5 nov.datum (2026)duizend regels
  • broncode van het spel
  • tests
Gegevenstabel
De tests groeiden sneller dan het spel
datum (2026)broncode van het speltests
18 okt.21,32,5
20 okt.35,77,7
22 okt.48,620,2
24 okt.71,633,9
26 okt.85,944,4
28 okt.86,145,5
30 okt.99,454,4
1 nov.106,960,7
3 nov.118,269,1
5 nov.119,572,1

Bron: Regels code van het spel en van de tests, op één versie per dag geteld; na 2 nov. zijn de getallen die van de volgende update.

Waar de code zit, in duizenden regels

De simulatie groeide van 41,1 tot 49,1 duizend regels en de tests van 57,3 tot 73,3 duizend; de andere delen bevatten elk tussen 3,6 en 13,9 duizend.

Waar de code zit, in duizenden regels0,010,020,030,040,050,060,070,080,0simulatiedierentekenenworkertestsduizend regelsWaar de code zit, in duizenden regels0,020,040,060,080,0simulatieenginedierengereedschaptekeneninterfaceworkerwasmtestsscriptsduizend regels
  • release
  • volgende update
Gegevenstabel
Waar de code zit, in duizenden regels
releasevolgende update
simulatie41,149,1
engine3,65,0
dieren6,97,6
gereedschap6,47,3
tekenen10,813,9
interface8,811,5
worker4,84,9
wasm4,04,3
tests57,373,3
scripts11,512,8

Bron: Regels TypeScript en scripts per deel van de code, tests met hun hulpcode; geteld bij de release en de volgende update.

De snelle code die een tablet draait, wordt gecontroleerd tegen de leesbare code, en vijf van de zeven delen moeten precies overeenkomen

De drukste lussen van de simulatie bestaan twee keer. Dat zijn de plekken waar smurrie stroomt, papier doorweekt, vuur zich verspreidt, een hand verf wegschuift en de lucht haar druk vindt. De ene kopie is leesbare TypeScript. De andere is gecompileerd naar WebAssembly met SIMD, zodat een goedkope tablet het bijhoudt. De tests draaien de leesbare kopie en de apps de gecompileerde, dus de tests zeggen alleen iets over de apps als de twee overeenkomen.

Drie lagen houden ze bij elkaar. Ten eerste noemt elke gecompileerde kernel precies welke functies en constanten hij kopieert, en de build bewaart een vingerafdruk van hun broncode. Verander één regel van een gekopieerde lus en de testrun mislukt, met de naam van de functie erbij, tot zijn tweeling is overgezet en iemand heeft vastgelegd dat hij het heeft nagekeken. Commentaar en witruimte tellen niet mee; de code wel.

Ten tweede draait elke kernel een reeks testpagina’s twee keer, met en zonder hemzelf, en elk veld van elke cel wordt vergeleken. Voor smurrie, vuur, de wegschuivende hand, zeep op melk en de drukoplossing van de lucht, elk apart getest met de andere kernels uit, moet het antwoord tot op de laatste bit kloppen. Dat lukt doordat ze hun sommen in dezelfde volgorde en met dezelfde precisie maken als de leesbare kopie. De kernels voor papier en voor de drijvende laag gebruiken voor de snelheid 32-bitsbanen, dus die worden in plaats daarvan aan een kleine marge gehouden: één deel op 100.000 voor papier, één op 10.000 voor de drijvende laag.

Ten derde draait één pagina met elk materiaal erop met alle kernels aan en met alle uit, op zes paginavormen, en die moet binnen een opgegeven marge overeenkomen. Als de kernels op een apparaat niet kunnen starten, valt het spel terug op de leesbare lussen, en een test controleert dat dat precies dezelfde smurrie en natheid geeft als de leesbare engine.

Twee naden, eerlijk getoond. Op één scheve pagina wijken zes cellen in de onderste rij tot 3,4 delen op 10.000 af tussen de papierkernel en zijn tweeling. De test is zo geschreven dat hij die mislukking verwacht, zodat hij mislukt op de dag dat iemand het oplost en die persoon dwingt er een gewone test van te maken volgende update. En bij lagere detailinstellingen doet de leesbare lucht een goedkopere drukoplossing, terwijl de gecompileerde lucht altijd de volledige doet. De pariteitstest zet ze eerst gelijk. In de apps is de wind bij die instellingen de vollere, met opzet.

Onder de motorkap
De snelle code die een tablet draait, wordt gecontroleerd tegen de leesbare code, en vijf van de zeven delen moeten precies overeenkomen

Onder de motorkap

De snelle code die een tablet draait, wordt gecontroleerd tegen de leesbare code, en vijf van de zeven delen moeten precies overeenkomen

Elke gecompileerde kernel geeft aan welke leesbare functies en constanten hij kopieert: 141 bij de release, 152 in de volgende update. De build maakt met SHA-256 een vingerafdruk van hun broncode, zonder commentaar en witruimte, en bewaart die vingerafdruk in de kernel; een test berekent hem bij elke run opnieuw. Een tweede vingerafdruk koppelt de gegenereerde lijmcode aan zijn bron.

kerneleisgetest op
smurrietot op de bit10 pagina’s
vuurtot op de bit9 pagina’s
wegschuiventot op de bit14 pagina’s + 4 streken
marangonitot op de bit8 pagina’s
luchttot op de bit12 roosters, 4 scènes
papier, opzuigenbinnen 1 deel op 100.00013 gevallen
drijvende laagbinnen 1 deel op 10.00010 gevallen

“Tot op de bit” is streng: NaN moet NaN tegenkomen en +0 moet +0 tegenkomen. De bitexacte kernels doen hun sommen met dezelfde precisie en in dezelfde volgorde als de leesbare code; elk wordt apart getest, met de andere uit in beide engines. De controle van de hele pagina draait elk materiaal, vuur ook, en staat maar een klein deel van de cellen toe om meer dan de marge te verschillen.

Deze tests kunnen niet slagen door niets te doen: ze controleren eerst dat de kernel is aangesloten en echt werk heeft gedaan. Het gedeelde geheugen van de kernels wordt precies afgemeten op wat elk ervan opgeeft, en een test houdt dat vast op veel paginaformaten.

Probeer het: twee kopieën van één lus De eigen regel van het spel

Links de leesbare lus; rechts zijn kopie. Beide draaien de regel van het spel voor nat papier op dezelfde 64 × 48 cellen, met ongeveer elke seconde een druppel die landt. Cellen waar de kopie zelfs maar één bit van het origineel verschilt, worden rood. Een vingerafdruk verschijnt als zijn 32-bitsgetal. Waar je op let: met dezelfde sommen in dezelfde volgorde komen de twee vingerafdrukken precies overeen, voor altijd. Tel dezelfde getallen in een andere volgorde op, of doe de sommen met 32-bitsgetallen zoals SIMD-banen doen, en de vingerafdruk breekt meteen, al blijven de cellen heel dicht bij elkaar: honderdmiljoensten uit elkaar bij een andere volgorde, miljoensten bij 32-bitssommen. Daarom houdt het spel vijf kernels aan de bit, en papier en drijvende lagen aan een marge. De klok loopt 3× zo snel.

Twee kopieën van de lus die papier nat maakt naast elkaar, met cellen die zelfs maar één bit verschillen rood gemarkeerd
  • de leesbare lus
  • zijn kopie
De kopie doet
vingerafdruk, origineel
—
vingerafdruk, kopie
—
cellen niet gelijk tot op de bit
—
grootste relatieve verschil
—
tot op de bit
—
binnen 1e-5
—
Hoe ver elke gecompileerde lus van zijn leesbare tweeling mag afwijken“5 exact”: smurrie, vuur, wegschuiven, marangoni en lucht, elk apart, moeten identiek zijn (0, op de bodem getekend). Papier: binnen 1e-5 van zijn tweeling; drijvende laag: binnen 1e-4; hele pagina met elk materiaal: 2e-3, in een klein deel van de cellen.

Vijf kernels moeten identiek zijn; papier mag 1e-5 verschillen, de drijvende laag 1e-4, de hele pagina 2e-3. Het bekende gat is 3,4e-4, en één afronding in 32 bits is ongeveer 6e-8.

Hoe ver elke gecompileerde lus van zijn leesbare tweeling mag afwijken110100100010 000100 0001 000 00010 000 000afronding5 exactpapierbekend gatdrijvendhele paginarelatief verschil, in miljardstenHoe ver elke gecompileerde lus van zijn leesbare tweeling mag afwijken110100100010 000100 0001 000 00010 000 000afrondingbekend gatrelatief verschil, in miljardsten
Gegevenstabel
Hoe ver elke gecompileerde lus van zijn leesbare tweeling mag afwijken
afronding5 exactpapierbekend gatdrijvendhele pagina
afronding60
5 exact1
papier10 000
bekend gat340 000
drijvend100 000
hele pagina2 000 000

Bron: Toegestane of waargenomen verschillen tussen elke gecompileerde lus en zijn leesbare tweeling. Bekend gat: 3,4e-4, gezien in zes cellen van één scheve pagina, vastgelegd als verwachte mislukking volgende update. “Afronding”: één afronding van een 32-bitsgetal, ongeveer 6e-8, ter vergelijking. De maten verschillen, dus vergelijk elk op zichzelf, niet onderling.

Verander één spikkeltje van een testpagina en een controle mislukt, en bijna elke bewuste wijziging komt met een notitie

Sommige testpagina’s zijn vastgepind. Een drukke pagina wordt bewerkt door een hele reeks gereedschappen, en aan het eind worden de velden ervan teruggebracht tot een vingerafdruk van acht tekens. Als verf, water of olie op die pagina anders uitkomen, al is het maar in één cel met één bit van de velden die hij dekt, verandert de vingerafdruk en mislukt de test.

Gedrag verandert wel met opzet. Dan wordt de vingerafdruk bijgewerkt, en de notitie ernaast zegt wat er veranderde en waarom, vaak met wat de vingerafdruk zou zijn als het nieuwe gedrag uit stond. Over de hele levensduur van deze bestanden kwamen 108 van de 112 wijzigingen aan een vingerafdruk met een notitie in dezelfde wijziging. Op volgorde gelezen zijn de notities een logboek van de natuurkunde van het spel: “de korrels van een pigment rijden met de waterstroom mee naar een drogende rand”.

Een vingerafdruk zegt “zoals eerst”. Hij kan niet zeggen “juist”: daarvoor zijn de scènetests. En die controleren vooral gedrag dat een kind zou herkennen, geen getallen uit de echte wereld.

Hetzelfde idee, tot op de bit, bewaakt een aantal beloften die je per ongeluk makkelijk breekt:

  • Opgenomen spel, afgespeeld vanaf een bewaard moment, eindigt op precies dezelfde pagina. Zonder de opgenomen willekeurige getallen en klok gaat het een andere kant op, en dat controleert de test ook.

  • Een sprong in de tijd geeft dezelfde pagina bij drie detailniveaus, elk met een eigen werkbudget.

  • Het meetpaneel openen verandert niets op de pagina.

  • De beelddata die tegel voor tegel worden verstuurd, zijn precies wat het opnieuw inpakken van de hele pagina zou versturen.

  • Ongedaan maken met kleine verschillen zet precies terug wat hele kopieën deden.

  • Spiegel de pagina en de wind spiegelt mee, tot op de bit.

Onder de motorkap
Verander één spikkeltje van een testpagina en een controle mislukt, en bijna elke bewuste wijziging komt met een notitie

Onder de motorkap

Verander één spikkeltje van een testpagina en een controle mislukt, en bijna elke bewuste wijziging komt met een notitie

Een pin hasht de ruwe bytes van gekozen velden met de standaard 32-bits FNV-1a-hash. Zes pinnen in vier testbestanden, allemaal bij de release (hun waarden zijn sindsdien veranderd):

pinwat hij dekt
lageneen drukke pagina bewerkt door elk gereedschap dat lagen leest; dieren en speelgoed door verf, sap, water
oliënverf, ketchup, honing, melk, sap en water met de hand bewerkt; was en boter op verf en ketchup
marmerenolie op een bad, gekamd met een tandenstoker, die wegtrekt
drijvende laagwat de tekenaar ontvangt

Geteld over de hele geschiedenis van de vier bestanden: 112 waardewijzigingen, 108 met nieuw commentaar in dezelfde commit. Pinnen gelden alleen voor de leesbare lussen.

Tests met dezelfde invoer: een herhaling gehasht over elk bewaard veld (“een herhaling die niets deed, zou niet slagen”), een sprong in de tijd bij drie detailniveaus met elk een eigen budget, het meetpaneel aan- en uitgezet, tegel-voor-tegel inpakken tegen een volledige herverpakking, ongedaan maken met verschillen met het oude ongedaan maken ernaast als referentie, en de wind opgelost op een gespiegelde pagina.

Probeer het: verander één cel met één bit

De eigen hash van de tests. Een kleine pagina verf, 40 × 30 cellen, teruggebracht tot een vingerafdruk met dezelfde FNV-1a-hash die de vastgepinde tests gebruiken. Eén cel, rood omcirkeld, verschilt al van de vastgepinde pagina met de kleinste stap die een 32-bitsgetal kan zetten, ongeveer één deel op tien miljoen. Waar je op let: het plaatje kan het niet laten zien, toch is de vingerafdruk een heel ander getal en mislukt de pin. Tik op de omcirkelde cel, of druk op Zet de laatst veranderde cel terug, en de pin slaagt; tik op een andere cel om die te veranderen.

Een kleine pagina verf en zijn vingerafdruk; tikken op een cel verandert die met één bit en de vingerafdruk verandert helemaal
vastgepind
—
nu
—
cellen veranderd
—
laatste wijziging
—
pin
—
Bijna elke wijziging aan een vingerafdruk kwam met een notitie die zegt waarom

Van de 112 wijzigingen aan een vastgepinde waarde kwamen er 108 met een notitie in dezelfde wijziging en 4 zonder.

Bijna elke wijziging aan een vingerafdruk kwam met een notitie die zegt waarom020406080100120met een notitie i…zonderwijzigingenBijna elke wijziging aan een vingerafdruk kwam met een notitie die zegt waarom020406080100120met een notit…zonderwijzigingen
Gegevenstabel
Bijna elke wijziging aan een vingerafdruk kwam met een notitie die zegt waarom
wijzigingen aan een vastgepinde waarde
met een notitie in dezelfde wijziging108
zonder4

Bron: Elke wijziging aan een vastgepinde waarde in de vier bestanden, over hun hele geschiedenis.

Fouten die we maakten, werden regels die de computer bij elke run controleert

De simulatie draait in een Web Worker, een aparte thread, zodat tekenen en aanraken soepel blijven terwijl de pagina wordt uitgerekend. De kant van het scherm ziet een kopie van wat hij moet tekenen. Die splitsing heeft één nare valkuil. Schermcode die rechtstreeks in de engine grijpt, werkt nog als alles op één thread draait, en doet stilletjes niets in de worker. “Het is vier keer gebeurd”, zegt de test die het nu tegenhoudt.

Die test leest de broncode van de schermkant met de TypeScript-compiler en somt elke waarde op die hij van de kant van de engine haalt. Elke waarde moet op een lijst staan met een reden: een constante, een pure functie, een klasse waarvan de kopie aan de schermkant leeft, of toestand die de twee met opzet delen. Een nieuwe laat de run mislukken met een bericht dat zegt waar hij hoort. De test controleert ook dat hij een nieuwe import opmerkt, zodat hij niet stilletjes blind kan worden.

Een run in de browser dekt wat code lezen niet kan. Die opent het echte spel twee keer in Chrome zonder venster, met de simulatie in haar worker en in dezelfde thread, en draait in beide dezelfde controles. Zand springt op een luidspreker. Schuifjes bereiken de simulatie. Een bewaard plaatje is een echte JPEG van 3200 pixels. Een kleurplaat laadt. Een reeks scènes draait: een vetbrand onder een deksel, olie op water, gevouwen deeg, een batterij die een lampje laat branden. De naam van elk gereedschap past onder zijn plaatje, in elke taal. Elke controle is er omdat dat ding “een keer stilletjes kapotging in de workermodus”.

Onder de motorkap
Fouten die we maakten, werden regels die de computer bij elke run controleert

Onder de motorkap

Fouten die we maakten, werden regels die de computer bij elke run controleert

Een test ontleedt alle code aan de schermkant met de TypeScript-compiler en controleert elke waarde die hij van de kant van de engine importeert tegen een toegestane lijst met redenen: 156 namen bij de release, 198 in de volgende update. Hij weigert ook luie imports van enginecode, controleert dat “gedeelde” namen echt gedeeld zijn en test zijn eigen detector.

Nieuwe getekende toestand moet ingedeeld worden: een tweede test mislukt bij elk nieuw stuk toestand van de engine tot het is gemarkeerd als bewaard, opnieuw uitgerekend of kortstondig. De kopie waaruit het scherm tekent, groeide van 47 enginevelden en 40 klassen bij de release naar 53 en 46 in de volgende.

De run in de browser: 39 controles bij de release, 71 in de volgende, in beide modi hetzelfde. Hij vergelijkt de pagina’s van de twee modi niet met elkaar; binnen een modus vergelijkt hij de toestand van de worker met de kopie van het scherm.

Workerthread

De engine, materialen, water, vuur, lucht, dieren en gereedschap, en de zeven WebAssembly-kernels.

Wat er tussen hen heen en weer gaat

  • → berichten. Elke aanraking, elk schuifje en elke knop wordt een aanroep met een naam die de worker afhandelt.

  • ← replica. Een kopie van wat er getekend wordt: 47 enginevelden en 40 klassen bij de release.

  • ← texturen. Alleen de veranderde tegels van de pagina, overgedragen, niet gekopieerd.

  • toegestane lijst. Waarden die de schermkant mag importeren, elk met een reden: 198 in de volgende update volgende update.

Hoofdthread

Menu’s en knoppen, het WebGL2-papier, de schilderingen en de dieren erop.

Wat de schermkant uit de engine mag importeren, per reden

Van de 198 waarden op de toegestane lijst in de volgende update zijn er 103 constanten, 68 pure functies, 22 gedeelde toestand, 3 replicaklassen, 1 een hook en 1 een configuratie.

Wat de schermkant uit de engine mag importeren, per reden020406080100120constantengedeeldhookwaardenWat de schermkant uit de engine mag importeren, per reden020406080100120constantenpure functiesgedeeldreplicahookconfiguratiewaarden
Gegevenstabel
Wat de schermkant uit de engine mag importeren, per reden
waarden op de toegestane lijst
constanten103
pure functies68
gedeeld22
replica3
hook1
configuratie1

Bron: De toegestane lijst in de volgende update volgende update, elke waarde met haar reden.

Elke regelkaart zegt wat er een keer misging, en dat een test het nu tegenhoudt

Bij elke run gecontroleerd door een test

Het scherm grijpt nooit in de engine

Een import “werkt in dezelfde thread en doet stilletjes niets in de worker”.

Elke enginewaarde die de schermkant importeert, staat op een lijst met haar reden.

Bij elke run gecontroleerd door een test

Elk stuk toestand heeft een bekende plek

“Bewaard, of opnieuw uitgerekend, of alleen voor het moment.”

Niets nieuws gaat stilletjes verloren bij ongedaan maken, bij opnieuw laden of onderweg naar het scherm.

Bij elke run gecontroleerd door een test

Elke module laadt op zichzelf

Een fout in de laadvolgorde “hield de app een keer tegen in de browser”.

Elke simulatiemodule, de engine en de gereedschappen laden eerst, alleen, in gewone Node.

Bij elke run gecontroleerd door een test

Voorwerpen zijn schilderingen

“Voorwerpen die in code getekend werden, kwamen steeds terug.”

Elke functie die vormen of tekst tekent, moet zeggen waarom ze geen voorwerp tekent: 92 bij de release.

Bij elke run gecontroleerd door een test

De naam van de app heeft één thuis

Een naam die op veel plekken staat, loopt uit elkaar.

De broncode, configuraties, native projecten en juridische pagina’s halen hem uit één instelling.

Vastgelegd in een test

De toestemming voor reclame blijft eruit

Het commentaar in het manifest noemt het “een belofte aan gezinnen”.

Het samengevoegde manifest van Android mag om internet en betalen via de winkel vragen; de toestemmingen voor de advertentie-ID en voor opslag zijn verwijderd.

Bij elke run gecontroleerd door een test

Het verborgen testpaneel kan niets ontgrendelen

Regel 2.3.1 van de App Store.

De pagina kan het niet bereiken met een statische import, en geen van de bestanden ervan mag een aankoop toekennen, melden of wegschrijven.

Bij elke run gecontroleerd door een test

Claim geen scheikunde die we niet doen

volgende update Zout water dat door een batterij wordt gesplitst, bubbelt en wordt zuur en zeepachtig, maar de simulatie maakt geen zuurstof of chloor.

“Geen woord over de elektrolyse, in de code of de catalogus, zegt zuurstof of chloor.”

Het beeld wordt belicht vanuit de eigen getallen van de simulatie, zodat het licht en het water dezelfde hoogtes lezen

De simulatie maakt geen beeld. Ze houdt een stapel getallen bij voor elk plekje op het papier, elk ongeveer een halve millimeter breed: hoeveel kleur er is ingetrokken, hoe nat het is, hoe dik de ketchup ligt, hoe heet en hoe verkoold. Elk beeld zet een klein programma op de grafische chip die getallen om in een belicht oppervlak voor elke pixel op het scherm.

Kleur wordt bewaard zoals licht erdoorheen gaat. Elk plekje slaat op hoe sterk het rood, groen en blauw opneemt, en het papier schijnt erdoorheen als e tot de macht min dat. Twee lagen waterverf over elkaar tellen hun absorbantie op, dus geel over blauw geeft groen, zoals bij glacislagen. Dat is de wet van Beer-Lambert Swinehart 1962, waarvan we na het bouwen ontdekten dat hij ons model beschreef, in drie kanalen. Echte verf verstrooit ook licht Kubelka 1931; dat laat het spel weg.

Het papier heeft echt reliëf. Een deuk van een hard aangedrukt potlood, de verhoogde omtrek van een kleurplaat, de golven waarin nat papier opdroogt, een klodder ketchup en een plas erbovenop tellen allemaal op tot één hoogte. Het licht leest die hoogte om hellingen te schaduwen, korte slagschaduwen te werpen en holtes donkerder te maken. Het water leest dezelfde deuken en omtrekken, dus volgens de code loopt het een groef in en blijft het binnen een verhoogde lijn. Een potloodgroef is wel ondiep, en in het spel zie je die niet, en ook geen water dat erin loopt. Een klodder ketchup of dikke verf laat zijn reliëf duidelijk zien.

Glans volgt ook de hoogte. Een glanzend oppervlak kaatst de lamp alleen terug waar het ernaartoe kantelt, dus een grote, vlakke plas heeft midden in bijna geen glanslicht, en zijn randen en rimpelingen glanzen. Eén keer gieten trekt binnen een seconde of twee in, voordat het dat kan laten zien. Nat papier wordt donkerder, en alleen papier dat helemaal doorweekt is, begint te glanzen.

De korrel van het papier is ruis die één keer per vel wordt gemaakt: een fijne ruwheid plus langere vezels. Nat papier golft op terwijl het doorweekt en wordt bij het drogen nooit helemaal vlak, omdat zijn golven stoppen met krimpen bij een lage bodem in plaats van plat te worden. Een strijkijzer of een deegroller drukt ze plat. Brand een gat en de geschilderde houten tafel komt erdoorheen.

Onder de motorkap
Het beeld wordt belicht vanuit de eigen getallen van de simulatie, zodat het licht en het water dezelfde hoogtes lezen

Onder de motorkap

Het beeld wordt belicht vanuit de eigen getallen van de simulatie, zodat het licht en het water dezelfde hoogtes lezen

De worker pakt de velden van de pagina in een handvol texturen met halve floats en bytes; het grafische programma leest ze voor elke pixel in elk beeld. De pagina is 512 × 384 cellen.

Hoogte: één som van de deuken van het papier, zijn golven, zijn vezel, de smurrie en het water erop, plus drijvende lagen, deeg, draad, verkoling en gaten, begrensd zodat een halve float niet kan overlopen in een zwarte pixel. De normaal komt uit centrale verschillen van die hoogte.

Belichting: één vast hoofdlicht ongeveer 45° omhoog vanuit linksboven; diffuus licht, een Blinn-Phong-glanslicht dat scherper wordt met de glans, zachte schaduwen uit een korte mars naar het licht die met de afstand zachter worden, en ambient occlusion uit de hoogtes rondom. Minder schaduwstappen op tragere apparaten.

Alleen veranderde tegels gaan omhoog, binnen een budget per beeld, die bij de hand eerst. Zonder budget is wat er wordt verstuurd na elk beeld tot op de bit gelijk aan een volledige herverpakking; een test controleert dat op een kleine pagina, zonder GPU.

Probeer het: belicht het papier vanuit zijn getallen De eigen regel van het spel

Een hoekje van de pagina, 120 × 80 cellen, belicht met de eigen formules van het spel: dezelfde hoogtesom, hetzelfde hoofdlicht, dezelfde schaduwmars, occlusie en glans. Er is net water gegoten op een ingedrukte groef, met ketchup en honing in de buurt en de verhoogde omtrek van een kleurplaat. Kies iets en sleep over het papier, of druk op Voeg toe, of giet opnieuw. Waar je op let: het water vlakt uit, vult de groef en trekt binnen een paar seconden in; het natte papier blijft donkerder en golft op terwijl het droogt. Het midden van de honing blijft dof terwijl de rand glanst. Zet schaduwen of glans uit om te zien wat elk toevoegt, en minder schaduwstappen om te zien wat een trage tablet opgeeft. In het spel is een potloodgroef te ondiep om te zien, en we hebben nooit gegoten water er een in zien lopen.

Een hoekje papier met een groef, een verhoogde omtrek, ketchup, honing en water, belicht met de formules van het spel
Sleep om toe te voegen
Schaduwstappen
water erop
—
ingetrokken
—
schaduwstap
—
felste glans
—
Een ronde klodder ketchup op het papier in het spel, linksboven helder met een klein glinsterpuntje en rechtsonder in de schaduw, boven een gele potloodlijn en een grijze plek waar net water is ingetrokken.
In het spel. Een klodder ketchup belicht door het hoofdlicht van de kamer vanuit linksboven: de kant naar het licht is helder, met een klein glinsterpuntje, en de verre kant valt in de schaduw. Eronder een potloodlijn, en papier dat donkerder is geworden waar net water is ingetrokken.
Glans verschijnt alleen waar een oppervlak naar de lamp kanteltOp vlak water (0°) is het glanslicht ongeveer een honderdduizendste van zijn piek.

Elk glanslicht piekt bij een kanteling van 22,5° naar het hoofdlicht: 0,85 voor water of honing, 0,77 voor ketchup, 0,51 voor mosterd en 0,07 voor droog papier.

Glans verschijnt alleen waar een oppervlak naar de lamp kantelt0,00,20,40,60,8010203040kanteling naar het hoofdlicht (graden)glanslichtpiek bij 22,5°Glans verschijnt alleen waar een oppervlak naar de lamp kantelt0,00,20,40,60,8010203040kanteling naar het hoofdlicht (graden)glanslichtpiek bij 22,5°
  • droog papier
  • mosterd
  • ketchup
  • water of honing
Gegevenstabel
Glans verschijnt alleen waar een oppervlak naar de lamp kantelt
kanteling naar het hoofdlicht (graden)droog papiermosterdketchupwater of honing
00,013470,000470,000030,00001
0,50,014490,000650,000050,00002
10,015560,000880,000080,00003
1,50,016680,001190,000120,00006
20,017840,001590,000180,00009
2,50,019060,002110,000280,00014
30,020320,002780,000410,00021
3,50,021620,003640,000610,00033
40,022970,004720,000890,00049
4,50,024360,006090,001280,00074
50,025790,007790,001830,0011
5,50,027260,009890,002580,0016
60,028760,012460,003610,00232
6,50,03030,015590,004990,00331
70,031860,019370,006820,00467
7,50,033450,023890,009230,00652
80,035050,029250,012360,009
8,50,036680,035560,016390,01229
90,038310,042930,021510,01658
9,50,039960,051460,027930,02212
100,04160,061250,035910,02917
10,50,043250,072380,04570,03806
110,044880,084950,057580,0491
11,50,04650,099010,071820,06264
120,04810,114580,088680,07903
12,50,049680,13170,108420,09863
130,051230,150330,131220,12173
13,50,052740,170410,157260,14861
140,054210,191860,18660,17945
14,50,055630,214530,219240,21434
150,0570,238250,255050,25325
15,50,058310,262790,293810,296
160,059560,287890,335160,34223
16,50,060730,313250,37860,39144
170,061840,338550,42350,44292
17,50,062860,363420,469130,4958
180,06380,38750,514630,54907
18,50,064660,41040,559080,60157
190,065420,431720,601490,65207
19,50,066090,451120,640870,69929
200,066670,468220,676230,74194
20,50,067140,482730,706670,77884
210,067510,494350,731360,80889
21,50,067780,502870,749620,83118
220,067940,508120,760940,84502
22,50,0680,510,764990,84999
230,067950,508460,761660,84592
23,50,06780,503540,751050,83293
240,067540,495340,733460,81145
24,50,067180,484010,709380,78213
250,066720,469780,679480,74587
25,50,066150,452920,644560,70373
260,065490,433740,605540,65691
26,50,064740,412580,563380,60668
270,063890,389830,519090,55432
27,50,062960,365850,473650,50107
280,061940,341040,427990,4481
28,50,060850,315760,382980,39644
290,059680,290390,339370,34698
29,50,058440,265250,29780,30042
300,057130,240640,258760,25732
30,50,055770,216840,222640,21802
310,054350,194050,189680,18272
31,50,052890,172480,160010,15148
320,051380,152250,133650,12422
32,50,049840,133480,110530,10075
330,048260,116210,09050,08082
33,50,046660,100480,073370,06413
340,045040,086270,058880,05032
34,50,043410,073560,046780,03905
350,041770,062290,036790,02997
35,50,040120,052370,028650,02274
360,038480,043720,022080,01707
36,50,036840,036240,016840,01266
370,035210,029830,012720,00929
37,50,03360,024380,00950,00674
380,032010,019780,007030,00483
38,50,030450,015940,005140,00343
390,028910,012740,003730,0024
39,50,027410,010120,002670,00166
400,025930,007970,001890,00114
40,50,02450,006240,001330,00077
410,02310,004840,000920,00052
41,50,021750,003730,000630,00034
420,020440,002860,000430,00022
42,50,019180,002170,000290,00014
430,017960,001630,000190,00009
43,50,016790,001220,000130,00006
440,015670,000910,000080,00004
44,50,01460,000670,000050,00002
450,013570,000490,000030,00001

Bron: Berekend met de eigen glansregel van het spel, een Blinn-Phong-glanslicht dat scherper wordt met de glans, halverwege tussen het hoofdlicht en het oog.

Bij het opslaan wordt je pagina opnieuw getekend, tot 3200 pixels breed, met scherpe randen en papiervezel

Plaatje opslaan blaast het scherm niet op. De worker pakt de hele pagina opnieuw in, en hetzelfde grafische programma draait nog eens met een afdrukschakelaar aan, 3200 pixels aan de lange kant, of 2400 op een telefoon of een apparaat met weinig geheugen. Bij de 512 cellen breed van de volle tafel is dat 6,25 pixel per cel.

Tussen cellen bemonstert het langs krommen in plaats van rechte hellingen. Waar de cellen eromheen een echte trede hebben, zoals een stiftlijn of de rand van een vlek, drukt het de zachte helling weer samen tot een scherpe rand, en zachte lagen waterverf laat het met rust. Het leunt een beetje naar de inkt, zodat dunne lijnen heel blijven, en de vezels van het papier maken elke rand wat rafelig. Pigment zakt in de kuiltjes van het papier. Was van waskrijt pakt alleen de toppen van de ruwheid, zodat het papier er in spikkels doorheen komt. Gaten, verkoling en de randen van smurrie worden een pixel breed uitgesneden.

Daarna worden de dingen die op het papier liggen eroverheen getekend vanuit hun schilderingen, zonder wat je in je hand hebt en zonder de hulplijnen. Een grote tekening wordt in banden van rijen gemaakt, elk met een pauze teruggelezen, zodat het spel tijdens het opslaan blijft reageren.

Dit detail is opnieuw opgebouwd, niet gesimuleerd. Het rooster eronder is hetzelfde, en de fijne vezel in een afdruk wordt alleen uit de positie uitgerekend, zodat elke afdruk dezelfde korrel heeft.

Onder de motorkap
Bij het opslaan wordt je pagina opnieuw getekend, tot 3200 pixels breed, met scherpe randen en papiervezel

Onder de motorkap

Bij het opslaan wordt je pagina opnieuw getekend, tot 3200 pixels breed, met scherpe randen en papiervezel

Het bewaarde plaatje is dezelfde papiershader met een afdrukschakelaar aan. Tussen cellen bemonstert hij met Catmull-Rom-krommen. Daarna kijkt hij per kleurkanaal naar de laagste en hoogste waarden in de cellen eromheen: waar die een echte trede hebben, trekt hij de zachte helling naar een scherpe rand, waar dat niet zo is, laat hij de laag met rust, en het resultaat wordt over de cellen gemengd zodat er geen naad te zien is. De vezel van het papier duwt een beetje aan waar elke rand valt, leunend naar de inkt; was van waskrijt zwakt het effect af.

Granulatie: pigment is in de kuiltjes van het papier wat sterker te zien. De fijne vezel is gelaagde gradiëntruis, deels uitgerekt en gedraaid, en die kantelt het oppervlak en geeft het ook een tint. Het plaatje wordt in banden van rijen teruggelezen en als JPEG bewaard; een automatische test controleert het.

Probeer het: het scherm en het bewaarde plaatje naast elkaar De eigen regel van het spel

Een kleine pagina blauwe inkt: een vlek met een harde rand, een zachte laag, een stiftlijn en potloodarcering. Op beide manieren getoond wordt hetzelfde stukje twee keer getekend: links zoals op het scherm, rechts volgens de regels van het bewaarde plaatje. Trek stiftlijnen met je vinger, of druk op Trek een stiftlijn. Waar je op let: de rand van de vlek gaat van een helling van ongeveer tien pixels breed naar ongeveer drie, terwijl de zachte laag zijn verloop houdt. Er verschijnt korrel in het papier en in het pigment. Zoom in om te zien dat de randen van de afdruk scherp blijven. (Vlak belicht, zonder schaduwen, om het snel te houden.)

Een kleine pagina blauwe inkt op twee manieren getekend: zoals op het scherm links, en volgens de regels van het bewaarde plaatje rechts
Toon
6,25
rand van de vlek op het scherm
—
in het bewaarde plaatje
—
een pagina van 512 cellen, op deze schaal bewaard
—
Van het scherm: een blauwe stiftlijn die een klodder blauwe waterverf raakt, allebei met zachte, wazige randen.
In het spel, op het scherm. Dezelfde 3 cm of zo van één tekening, een stiftlijn die in een tikje waterverf loopt, zoals het scherm het tekent.
Hetzelfde stuk uit het bewaarde plaatje van 3200 pixels: de stiftlijn en de rand van de waterverf scherp, met papiervezel zichtbaar in de verf en het papier.
In het spel, het bewaarde plaatje. Hetzelfde stukje zoals het bewaarde plaatje het tekent, op dezelfde schaal. Twee echte opnamen, op dezelfde manier bijgesneden en vergroot, nooit bewerkt.

Elk voorwerp op tafel is een schildering, en nieuwe tekencode moet zeggen waarom

Alles wat als voorwerp op het papier ligt, is een schildering: een beker, een tandenstoker, een fles, de poes. Dingen op tafel zijn recht van boven geschilderd, zoals een kaart ze laat zien, in één gouachestijl met warm licht van linksboven, dezelfde kant als de lamp in het grafische programma. Gereedschap in je hand is van opzij geschilderd, want zo zie je het als je het vasthoudt.

Elke schildering begint als een gegenereerd beeld, gemaakt met een beeldmodel in één gedeelde stijl, met de eigen kunst van het spel als leidraad en het plankpictogram van het gereedschap ernaast, zodat het ding in je hand het ding op de plank is. Elk ervan wordt op het oog uitgekozen. Scripts maken het daarna schoon, snijden het in spritesheets en meten dingen zoals waar een tuitje naartoe wijst uit de verf zelf. Delen die een kleur aannemen, worden een eigen laag, zodat één schildering van een waskrijtje voor elke kleur waskrijt dient.

Een ding wisselt van schildering als het van toestand wisselt. In het uitgebrachte spel betekent dat ingeknepen flessen, een tandenstoker die rechtop staat, gebroken en in stukken, klemmen open en dicht, en de houdingen van elk dier. De volgende update voegt er meer toe, zoals flessen die op hun zij liggen, en ballen waarvan de vlakken komen van de echte vorm van een voetbal volgende update.

Twee regels houden het zo. Iets waarvan de schildering nog niet binnen is, tekent niets tot die er is, en een gereedschap waarvan de kunst niet af is, blijft van de plank. En een test leest alle tekencode aan de schermkant: elke functie die vormen of tekst tekent, moet op een lijst staan, met de reden waarom het geen voorwerp is. De volgende update voegt de functies toe die pixels schrijven volgende update. Vloeistoffen, vuur, schaduwen, licht, kabels, hulplijnen en de tekentjes op de knoppen blijven met opzet code.

Dieren zijn schilderingen die buigen. Het lijf van een poes is één schildering van bovenaf, gespannen over een net dat haar botten volgt, zodat ze draait en zich uitrekt zonder naad. Haar poten en staart zijn eigen geschilderde stukken, en een nieuwe houding vloeit in vanuit een andere schildering.

Onder de motorkap
Elk voorwerp op tafel is een schildering, en nieuwe tekencode moet zeggen waarom

Onder de motorkap

Elk voorwerp op tafel is een schildering, en nieuwe tekencode moet zeggen waarom

Een test ontleedt alle tekencode aan de schermkant en vindt elke functie die vormen of tekst op een canvas tekent, of een SVG bouwt. Elke functie moet op de lijst staan met waarom ze code blijft: 92 bij de release, 106 in de volgende. De volgende update zet ook de 10 functies die pixels schrijven op de lijst, elk met haar soort en “nooit een voorwerp”. Een functie die stopt met tekenen, moet van de lijst af.

Wat hij niet kan zien: een nieuwe tekening binnen een functie die al op de lijst staat, en de shaders. Of een opgeschreven reden eerlijk is, beoordeelt een mens. Eén uitzondering om hardop te zeggen: 12 functies tekenen dieren in code, alleen gebruikt zonder WebGL2, als hun schilderingen niet laden, of in een bewaard plaatje voordat ze er zijn.

Dieren: elk lijf is een schildering op een fijn net dat elk beeld de botten volgt, allemaal samen getekend op de grafische chip; hun schaduwen worden zo gemengd dat overlappende delen nooit twee keer donkerder worden. Bij de release: 94 spritesheets, 347 sprites, 615 kleurplaten.

Wat code nog mag tekenen, en waarom“Overig” zijn materialen, deeltjes, kabels, hulplijnen en wat de simulatie op een ding bijhoudt. “Reserve” tekent de dieren in code, alleen gebruikt zonder WebGL2, als hun schilderingen niet laden, of in een bewaard plaatje voordat ze er zijn.

Van de 106 functies op de lijst tekenen er 59 andere dingen, 15 schaduwen, 14 licht, 12 de reservedieren en 6 tekentjes in de interface.

Wat code nog mag tekenen, en waarom0102030405060overiglichtinterfacetekentjesfuncties op de lijstWat code nog mag tekenen, en waarom0102030405060overigschaduwenlichtreserveinterfaceteke…functies op de lijst
Gegevenstabel
Wat code nog mag tekenen, en waarom
functies
overig59
schaduwen15
licht14
reserve12
interfacetekentjes6

Bron: De 106 functies op de lijst van de test in de volgende update volgende update (92 bij de release).

Een pagina in het spel van boven gezien: de poes die slaapt naast een kop koffie, een tandenstoker in een klodder rode verf en een fles ketchup die wordt ingeknepen, elk een schildering met een zachte schaduw.
In het spel. Een beker, de poes, een tandenstoker in verf en een fles ketchup die wordt ingeknepen: elk is een schildering, neergezet waar de simulatie zegt dat hij is.

Beloften aan gezinnen, zoals geen reclame en geen leed voor dieren, worden met code gecontroleerd overal waar code ze kan vasthouden

Geen enkel dier raakt ooit gewond, en een test stuurt onheil op elk ervan af

Professor Oops heeft dieren: vliegen, mieren, een duif, een poes, een hond en slakken. De regel is dat geen van hen ooit gewond raakt. Ze ontwijken, vluchten of trekken zich terug. Een test zet elk ervan door een lange lijst onheil, van een hamer en een deegroller tot een vuurtje ernaast, de oven en lang wachten. Hij controleert dat er niets op het dier terechtkomt, er geen vlam onder wordt aangestoken, het niet verdwijnt en dat het bewoog toen er iets op afkwam.

Bij de release bevatte de lijst 61 soorten onheil voor 8 toestanden van dieren, 488 runs in totaal; ongeveer 25 van die soorten gebruiken gereedschap dat nog wordt afgemaakt. De volgende update heeft 97 soorten voor 10 toestanden, 970 runs volgende update.

Veiligheid komt van een wereld die meegeeft, niet van snelle dieren. Elke druk, welk gereedschap het ook is, gaat door één gedeelde regel die leest wat waar drukt. Een klein dier wordt door de druk zelf opzij geschoven. In de volgende update wacht een zwaar ding boven een poes of een hond in de lucht tot het dier is weggegaan, en wordt het weggehaald als het dier niet beweegt volgende update.

Er is geen reclame, en de toestemming voor reclame is eruit gehaald

Het spel bevat geen reclamecode en geen analysebibliotheek van een ander bedrijf. In één onderzoek naar 135 apps die jonge kinderen gebruiken, had 95% reclame Meyer 2019. Op Android is de toestemming voor de advertentie-ID, die een bibliotheek zou kunnen binnensmokkelen, verwijderd, samen met de toestemmingen voor opslag, en een test bewaakt de lijst. Op iOS meldt de app geen tracking. Het uitgebrachte spel gebruikt nooit de microfoon of de camera. Anonieme tellingen, zonder kenmerk en zonder tijd fijner dan een dag, kan een volwassene uitzetten, en ze staan bij het begin uit in het Verenigd Koninkrijk en het grootste deel van Europa.

De apps spelen zonder verbinding

Het hele spel zit in de app, de WebAssembly-kernels ook, ingepakt in de code zodat er niets wordt opgehaald. Bij de release zit de stem van de schilder ook in de app. In de volgende update worden stem en de meeste muziek pakketten die worden opgehaald als er verbinding is, en alles werkt ook zonder volgende update. De browserversie heeft een verbinding nodig om te laden.

Alleen echte dingen is een besluit, genomen op de eerste dag

Op de eerste dag, toen er alleen een prototype draaide, werden een paar honderd ideeën opgeschreven en gescoord op plezier, gemak, hoe nieuw ze waren en hoeveel “dat mag je nooit doen”-sensatie ze gaven. Drie van de acht hoogst scorende nieuwe ideeën waren een voetstap van een reus, een niesbui van een reus en een zwart gat. Dezelfde dag ging een harde regel boven de scores: alleen echte dingen die een kind echt met papier kan doen. Geen monsters, geen reuzen, geen zwarte gaten. Geen test kan dat controleren. Het wordt vastgehouden door wie beslist wat er wordt gebouwd. Sommige zachte ideeën die laag scoorden, kwamen er toch: rode kool die van kleur verandert en een laag van theevlekken als gereedschap, en een zonneafdruk en een potloodwrijfsel zonder eigen gereedschap, omdat de gedeelde natuurkunde ze maakte.

Dingen voor volwassenen zitten achter een som die een jong kind niet kan maken

Kopen, het deel voor volwassenen en extra speeltijd zitten achter een som zoals 37 + 40. Die is bedoeld om een jong kind tegen te houden, en de code zegt dat ook: het is een som, geen slot. Aanbiedingen zijn zeldzaam: hooguit drie vragen, een week uit elkaar, nooit meer na “Nee, bedankt, vraag het niet meer”. Een ouder kan een dagelijkse grens van 15, 30, 45 of 60 minuten instellen, standaard uit, en de schilder zegt het twee minuten voor het einde.

Onder de motorkap
Beloften aan gezinnen, zoals geen reclame en geen leed voor dieren, worden met code gecontroleerd overal waar code ze kan vasthouden

Onder de motorkap

Beloften aan gezinnen, zoals geen reclame en geen leed voor dieren, worden met code gecontroleerd overal waar code ze kan vasthouden

De test zonder leed kruist elke toestand van elk dier met elk opgesomd leed. Elke run legt een kleine pagina neer met het dier erop, brengt het leed, en laat dan de echte engine een paar seconden lopen op 60 Hz, terwijl hij elke druk en elke vlam bekijkt. Hij mislukt als er een druk boven wat het dier kan dragen op het dier landt, er een vlam onder wordt aangestoken of brandt, het verdwenen is, of als er iets op afkwam en het niet bewoog. Bij 42 van de 61 soorten leed (69 van de 97 in de volgende) moet het bewegen; de rest mag het uitzitten. Elk dier geeft aan wat het niet hoeft te laten bewegen, zoals een poes die een pluizenroller verdraagt.

“Ongedeerd” heeft geen getal: dieren hebben geen gezondheid om af te lezen. Elke druk, van welk gereedschap ook, gaat door één gedeelde gevarenregel die het contact leest, niet het gereedschap. Het wachten tot een dier beweegt, zit in de volgende update.

Android: de toestemmingen voor de advertentie-ID, de netwerkstatus en externe opslag zijn uit het samengevoegde manifest gehaald, en een test legt vast wat er overblijft, internet en betalen via de winkel. iOS: de app meldt geen tracking en geen trackingdomeinen; de enige toestemming waar hij om vraagt, is iets toevoegen aan de fotobibliotheek. De kernels reizen mee in de code van het spel.

De rangschikking van ideeën: een gewogen score van plezier, gemak, nieuwheid en sensatie, elk van 1 tot 10, over 176 ideeën. Het hek: een som met twee cijfers, een paar pogingen.

De meeste beloften worden door een test vastgehouden; één door mensen

een test

Geen enkel dier raakt ooit gewond

Elke toestand van elk dier krijgt elke opgesomde soort onheil: 488 runs bij de release, 970 in de volgende update volgende update.

Bij elke run gecontroleerd door een test

een test

Geen reclame, geen advertentie-ID

Geen reclamecode en geen analysebibliotheek van een ander bedrijf. De toestemmingen voor de advertentie-ID en voor opslag zijn uit de Android-build gehaald.

Vastgelegd in een test

hoe het gebouwd is

De apps spelen offline

Het hele spel, de WebAssembly-kernels ook, zit ingepakt in de app, dus er wordt niets opgehaald om te spelen.

De kernels reizen mee in de code van het spel

code

Dingen voor volwassenen vragen om een som

Kopen, het deel voor volwassenen en extra tijd zitten achter een som zoals 37 + 40. Die is bedoeld om een jong kind tegen te houden, niet om een slot te zijn.

In de code vastgelegd, en daar een som genoemd

mensen

Alleen echte dingen

Geen monsters, geen reuzen, geen zwarte gaten. Geen test kan dat controleren; het wordt vastgehouden door wie beslist wat er wordt gebouwd.

een besluit genomen op de eerste dag

Eén test kan een deegroller, een potlood of een bewaard plaatje volgen door lagen die het hele spel deelt

Elk van deze loopt van iets wat een kind doet, door een laag die veel systemen delen, naar wat er volgt. Een test legt elk ervan vast.

Gedeeld: een vingerafdruk van de gekopieerde broncode

Verander één regel van de waterlus van het papier

De testrun mislukt, met de naam van de functie erbij, tot de WebAssembly-kopie is overgezet. Vastgelegd in een test.

Snel op een goedkope tablet

Gedeeld: deuk in de grondhoogte

Druk een potloodlijn hard aan en arceer er dan overheen

Het grijs slaat de ingedrukte lijn over, die schoon blijft, met donkere randen waar de wanden van de groef het grijs opvangen. Vastgelegd in een test.

Potlood, waskrijt en papier

Gedeeld: één druk, gelezen door de gedeelde gevarenregel

Rol een deegroller over een mier

De mier wordt opzij geschoven en blijft er nooit onder liggen. Vastgelegd in een test.

Dieren met een eigen wil

Gedeeld: twee lagen samengevouwen tot één texel door de shader achterstevoren op te lossen

Giet olie over opgedroogde ketchup

De ketchup wordt nauwelijks bleker, en aan het front van de olie is geen harde lijn te zien. Vastgelegd in een test.

Dun, plakkerig en vettig

Gedeeld: een verse volledige verpakking, dezelfde shader met afdrukken aan

Bewaar een plaatje van een pagina met een ingebrand gat

Het gat en de tafel eronder staan ook in het bewaarde plaatje; een controle in de browser zorgt dat er een echte JPEG van 3200 pixels uitkomt, in de workermodus net als in dezelfde thread.

Warmte en vuur

0.56
Regels test per regel spel bij de release; 0,60 in de volgende update.
5 of 7
Gecompileerde kernels die tot op de laatste bit met hun leesbare tweeling overeenkomen, elk apart getest.
488
Runs met onheil bij de release in de test zonder leed: 8 toestanden van dieren × 61 soorten, ongeveer 25 ervan met gereedschap dat nog wordt afgemaakt.
108 of 112
Wijzigingen aan de vingerafdruk van een pagina die kwamen met een notitie over wat er veranderde.
0
any-typen en @ts-ignores in de TypeScript van het spel, in de strikte modus.
3200 px
Over de breedte van een bewaard plaatje, opnieuw getekend uit de eigen getallen van de pagina, nooit vergroot vanaf het scherm.

Waar we vereenvoudigen, en waarom

Controleren is een vak van kiezen wat je vastzet. Dit zijn de gaten tussen wat er wordt gecontroleerd en wat er wordt uitgebracht, en tussen de echte wereld en het beeld, met opzet.

In het echt. De apps draaien gecompileerde kernels

In het spel. Gedragstests draaien de leesbare JS

Waarom dat genoeg is. Een wijziging aan een lus komt naar boven in zijn eigen leesbare test. Pariteit overbrugt de rest: vijf kernels exact op zichzelf, papier en drijvende lagen binnen afrondingen, de hele pagina binnen 2e-3. Bij weinig detail doet de wind in de apps een vollere oplossing, met opzet.

In het echt. Duizenden telefoons en grafische chips

In het spel. Chrome zonder venster op computers, plus één aankooptest op een iPhone-simulator

Waarom dat genoeg is. Snel en herhaalbaar. Echte apparaten worden met de hand geprobeerd; er is geen apparatenpark en geen reeks regressietests met schermafbeeldingen.

In het echt. Een test laat zien dat het gedrag juist is

In het spel. Een vingerafdruk laat zien dat het is zoals eerst

Waarom dat genoeg is. Het houdt ongelukjes tegen en houdt een logboek bij. Weinig tests vergelijken met getallen uit de echte wereld; de scènetests controleren wat een kind zou herkennen.

In het echt. Een kind zou alles met een dier kunnen doen

In het spel. Een opgesomde reeks soorten leed maal toestanden van dieren, elk met één positie en één paginaformaat

Waarom dat genoeg is. Begrensd en herhaalbaar. Leed dat niemand op de lijst zette, wordt niet getest, dus de veiligheid rust op de gedeelde gevarenregel, en de lijst controleert die.

In het echt. Licht heeft een spectrum en verf verstrooit het

In het spel. Drie kanalen absorbantie die optellen

Waarom dat genoeg is. Eén getal per kanaal; lagen vermenigvuldigen, zoals dunne glacislagen doen. Wat we kwijtraken: kleuren die onder het ene licht gelijk zijn en onder het andere niet, en het sterk lichter maken door witte verf.

In het echt. Een tafel die in drie dimensies wordt gezien

In het spel. Een hoogteveld, één vast hoofdlicht, één vast gezichtspunt, schaduwen die een kort stukje naar het licht worden gevolgd

Waarom dat genoeg is. Alles leeft in platte velden, tegen begrensde kosten per pixel. Geen overhangen, geen weerkaatst licht; glans is een glanslicht, geen spiegel.

In het echt. Oppervlaktespanning trekt fijne rimpelingen glad

In het spel. Het fijne deel van de helling van een vloeistof wordt voor gladde materialen afgezwakt

Waarom dat genoeg is. Anders zou elke cel als een eigen facet oplichten, in spikkels. Het is gladstrijken bij het tekenen; het water van een plas wordt niet gladgestreken.

In het echt. Een afdruk laat detail zien dat fijner is dan het rooster

In het spel. Randen opnieuw opgebouwd, vezel uit de positie uitgerekend

Waarom dat genoeg is. Een pagina van 512 cellen wordt een afdruk van 3200 pixels die eruitziet als papier. Het detail is verzonnen, en elke afdruk heeft dezelfde fijne korrel.