Hoe Kimi K3 een digitale vulpenstreek bouwde
We wilden Freeforms richtingsgevoelige vulpen in Lulucat Notes. De specificatie was een schermafbeelding van handschrift. Na twee modellen, één onderscheidende vraag en een ellips-penpunt schrijft de waterpen zoals hij hoort. Het onderzoek, de code en de validatie werden gedaan door Kimi K3 op Fireworks.

Gerenderd door de Lulucat Notes Metal-pijplijn waar dit bericht over gaat.
Lulucat Notes heeft een gewone pen en een markeerstift. Het volgende gereedschap op de routekaart was een waterpen — de richtingsgevoelige vulpen die je kent uit Apple Notes en Freeform, waarbij verticale streken dik uitkomen en horizontale streken dun. De specificatie voor dit gereedschap was geen document. Het was een schermafbeelding: drie regels handschrift, met de woorden “pencilkit”, “无边记” en “这种有方向的水笔能力” — dit richtingsgevoelige waterpenvermogen.
Net als de Metal-pijplijn ervoor werd het werk gedaan door Kimi K3, het open model van Moonshot dat op Fireworks draait: meten, modelleren, coderen en valideren. De mens leverde de schermafbeelding, beantwoordde één vraag en beoordeelde het gevoel op een echte iPad.
De specificatie is een schermafbeelding
Een statisch beeld kan je niet vertellen waarom een streek dun is. Het kan je alleen vertellen hoe dun en waar. De eerste stap was dus meten. We scanden de schermafbeelding rij voor rij en kolom voor kolom, volgden de afwijking van de middellijn van elke streek om de richting te bepalen en zetten vervolgens de scanbreedte met de sinus van die hoek om in de werkelijke breedte:
| Streek | Richting | Werkelijke breedte |
|---|---|---|
| Opgaande stok van “l”, “pencilkit” | ≈ 78° | 27.3 px |
| Steel van “k”, “pencilkit” | ≈ 90° | 28 px |
| Naar links vallende haal van 力 | ≈ 66° | 25.7 px |
| Cursieve verbindingsstreken | ≈ 8° | 11 px |
| Chinese horizontale streken (横) | ≈ 0° | 6–11 px |
De verhouding dik-dun was ≈ 2.5. Het fitten van
Versie één: breedte uit richting
Het eerste model lag voor de hand. Bereken voor elk invoerpunt een richting, koppel die richting via de gefitte kromme aan een breedte en bak het resultaat bij het vastleggen in de straal van het punt. De richting werd causaal geschat — een exponentieel afnemend gewogen gemiddelde over de laatste paar punten van de boog, berekend in de ruimte van verdubbelde hoeken zodat een omkering van de beweging de schatting niet opheft. Er worden alleen punten uit het verleden gebruikt, dus de live-streek en de vastgelegde streek komen byte voor byte overeen.
De builder doorstond zijn unitcheck: synthetische horizontale, verticale, 45°- en omkeerpaden bakten allemaal de theoretische breedtes in — 0.99, 2.52, 2.00 en 0.99 punten. Renderen had helemaal geen nieuwe code nodig: een streek met ingebakken straal is gewoon een keten van ronde stempels, en onze point-sprite-pijplijn tekende die al.
Op de iPad had de mens ongeveer tien seconden nodig om hem af te wijzen: “Dit is een artpen, niet de waterpen.”
Twee verklaringen passen bij één beeld
Waarom voelde het verkeerd? Er waren twee mogelijke verklaringen, en de schermafbeelding kon ze niet uit elkaar houden:
- Richtingsvergrendeling. De geometrie van de penpunt dwingt horizontale streken dun en verticale streken dik te blijven, wat de hand ook doet. Dat is wat versie één implementeerde.
- Druk en snelheid. De pen wordt door druk aangestuurd, en het patroon van het voorbeeld is gewoon handschriftdynamiek: neerhalen worden vanzelf aangedrukt, verbindingsstreken vanzelf snel en licht.
Beide leveren een schermafbeelding op met dunne horizontalen en dikke verticalen. Het verschil zit in wat er gebeurt als je hard op een horizontale streek drukt. Versie één houdt die dun. Een drukpen maakt hem dik. Daarom stelden we de mens één vraag: moet een hard aangedrukte horizontale streek dikker worden?
“Nee. Horizontalen blijven dun.”
De richtingsvergrendeling was bevestigd. Maar er was nog iets mis, want versie één was óók richtingsvergrendeld.
Het antwoord zat in de streekuiteinden
De volgende aanwijzing zat bovenaan de opgaande stokken. Toen we inzoomden op de “l”- en “k”-stelen in het voorbeeld, zagen we twee dingen: een rechte streek behoudt van begin tot eind dezelfde breedte, en de streekuiteinden zijn vlakke diagonale sneden — de vorm van een beitelpenpunt die wordt opgetild. Geen ronde stippen. Geen drukverlopen.

Dat was wat “artpengevoel” werkelijk betekende. Versie één modelleerde het uiterlijk van het voorbeeld — breedte als functie van geschatte richting — maar niet de pen. Een richtingsschatter is een sensor: hij trilt bij ruisige invoer, loopt achter in bochten en rondt elk streekuiteinde af tot een cirkel. Een echte penpunt heeft geen van die problemen, omdat hij niets berekent. De breedte is geometrie.
Versie twee: een ellips-penpunt
Het uiteindelijke model heeft helemaal geen richtingsschatter. De penpunt is een georiënteerde ellips: de lange as is horizontaal, de korte as is vast. Stempels van deze ellips worden dicht langs het pad van de streek geplaatst, en al het andere volgt uit de geometrie:
-
Een horizontale streek beweegt langs de open rand, dus komt hij altijd
breed uit — een constante dunne lijn, bij elke druk. Precies de bevestigde specificatie. -
Een verticale streek kruist de volledige lange as:
, het dikke uiteinde. -
Een diagonale streek neemt de koordbreedte van de ellips loodrecht op de beweging,
-
Streekuiteinden zijn ellipsvormige sneden — de vlakke, penpuntvormige uiteinden uit het voorbeeld, zonder extra werk.
-
Druk schaalt alleen de lange as,
, dus druk levert extra inktvolume bij neerhalen en kan een horizontale streek nooit dikker maken.
Uit de metingen van het voorbeeld hielden we
Renderen had slechts één nieuwe fragmentshader nodig en verder niets. Het vertexformaat — positie, diameter, kleur — droeg al alles mee: de diameter is de lange as en de korte as is een uniform per doorgang. De shader evalueert een ellips-SDF met dezelfde halve-pixel-dekkingsramp als onze ronde stempels. Daardoor is hij pixel-vergelijkbaar met de Core Graphics-referentie-implementatie (fillEllipse per stempel). De validatie ging door de gebruikelijke poort: een synthetisch corpus met twee vulpenstreken werd op beide manieren gerenderd en pixel voor pixel vergeleken — nul structurele verschillen, en een gecontroleerde horizontale streek mat 12 px in beide renderers.

Versie één links, versie twee rechts. Dezelfde handgeschreven invoer, dezelfde pijplijn. De uiteinden vertellen het hele verhaal.
Op de iPad doorstond de nieuwe pen meteen de test: “好,很好” — goed. Heel goed.
De misser behouden
Versie één verdween niet in de prullenbak. Het is een interessant penseel — alleen geen waterpen. Daarom werd het uitgebracht als eerste item in een nieuw menu voor experimentele penselen, onder de naam die de mens eraan gaf: 漏水的圆珠笔, de lekkende balpen. De werkbalk kreeg een flesknop die een tekstlijst met experimenten opent; het volgende toevoegen kost één regel in een registry. Een afgewezen model is geen verspild werk als het kan worden uitgebracht als een duidelijk gelabeld experiment.
Een avond vol lussen
Het hele traject — meten, modelleren, bouwen, op het apparaat testen, vragen, opnieuw modelleren, opnieuw bouwen, valideren — nam één avond in beslag. Kimi K3 op Fireworks voerde de volledige technische kant van de lus uit: de meetscans bedenken, de onderscheidende vraag voorstellen in plaats van een tweede keer te gokken, zijn eigen richtingsschatter verwijderen toen het bewijs kantelde en de pixel-diff-harness uitbreiden voordat de app werd aangeraakt. De inferentiesnelheid van Fireworks hield de lus interactief — lange Metal- en Swift-diffs, scripts voor pixelanalyse en corpustooling kwamen allemaal snel genoeg aan om de resterende bottleneck te laten waar die hoort: bij het menselijk oordeel.
Het samenwerkingspatroon was hetzelfde als in het bericht over de Metal-pijplijn, en het werkte opnieuw: het model is snel en precies; de mens bezit smaak en end-to-end-verificatie. Eén zin met gevoelsmatige feedback — “artpen, niet waterpen” — was genoeg voor het model om de precieze modelleringsfout te vinden en die te vervangen door een eenvoudiger ontwerp.
Het juiste model bleek kleiner dan het verkeerde. Versie twee werd uitgebracht met minder bewegende onderdelen dan versie één — geen schatter, geen gladstrijkvenster, geen gefitte exponent. De penpunt berekent de breedte niet. De penpunt is de breedte.