Kimi K3 ਨੇ ਆਪਣੇ ਆਪ ਡਿਜੀਟਲ ਫਾਊਂਟੇਨ ਪੈੱਨ ਦਾ ਸਟ੍ਰੋਕ ਕਿਵੇਂ ਬਣਾਇਆ
ਸਾਨੂੰ Lulucat Notes ਵਿੱਚ Freeform ਵਾਲੀ ਦਿਸ਼ਾ-ਅਧਾਰਿਤ ਫਾਊਂਟੇਨ ਪੈੱਨ ਚਾਹੀਦੀ ਸੀ। ਵਿਸ਼ੇਸ਼ਤਾ ਹੱਥ-ਲਿਖਤ ਦਾ ਇੱਕ ਸਕ੍ਰੀਨਸ਼ਾਟ ਸੀ। ਦੋ ਮਾਡਲਾਂ, ਇੱਕ ਨਿਰਣਾਇਕ ਸਵਾਲ ਅਤੇ ਇੱਕ ਅੰਡਾਕਾਰ ਨਿਬ ਤੋਂ ਬਾਅਦ, ਜਲ-ਪੈੱਨ ਉਵੇਂ ਲਿਖਦੀ ਹੈ ਜਿਵੇਂ ਲਿਖਣਾ ਚਾਹੀਦਾ ਹੈ। ਖੋਜ, ਕੋਡ ਅਤੇ ਜਾਂਚ Kimi K3 ਨੇ Fireworks ਉੱਤੇ ਕੀਤੀ।

ਇਸ ਲੇਖ ਵਿੱਚ ਜਿਸ Lulucat Notes Metal pipeline ਬਾਰੇ ਗੱਲ ਹੈ, ਉਸੇ ਨਾਲ render ਕੀਤਾ ਗਿਆ।
Lulucat Notes ਵਿੱਚ ਇੱਕ ਸਧਾਰਨ pen ਅਤੇ ਇੱਕ highlighter ਹੈ। roadmap ਉੱਤੇ ਅਗਲਾ tool ਇੱਕ ਜਲ-ਪੈੱਨ ਸੀ — Apple Notes ਅਤੇ Freeform ਤੋਂ ਜਾਣੀ-ਪਛਾਣੀ ਉਹ ਦਿਸ਼ਾ-ਵਾਲੀ ਫਾਊਂਟੇਨ ਪੈੱਨ, ਜਿਸ ਵਿੱਚ vertical stroke ਮੋਟੇ ਅਤੇ horizontal stroke ਪਤਲੇ ਨਿਕਲਦੇ ਹਨ। ਇਸ tool ਦਾ spec ਕੋਈ document ਨਹੀਂ ਸੀ। ਇਹ ਇੱਕ screenshot ਸੀ: ਹੱਥ-ਲਿਖਤ ਦੀਆਂ ਤਿੰਨ ਲਾਈਨਾਂ, ਸ਼ਬਦ “pencilkit”, “无边记” ਅਤੇ “这种有方向的水笔能力” — ਇਹ ਦਿਸ਼ਾ-ਵਾਲੀ ਜਲ-ਪੈੱਨ ਸਮਰੱਥਾ।
ਪਹਿਲਾਂ ਵਾਲੇ Metal pipeline ਵਾਂਗ, ਇਹ ਕੰਮ Kimi K3 ਨੇ ਕੀਤਾ, ਜੋ Moonshot ਦਾ open model ਹੈ ਅਤੇ Fireworks ਉੱਤੇ ਚੱਲਦਾ ਹੈ: measurement, modeling, code ਅਤੇ validation। ਮਨੁੱਖ ਨੇ screenshot ਦਿੱਤਾ, ਇੱਕ ਸਵਾਲ ਦਾ ਜਵਾਬ ਦਿੱਤਾ ਅਤੇ ਅਸਲ iPad ਉੱਤੇ feel ਦਾ ਫੈਸਲਾ ਕੀਤਾ।
Spec ਇੱਕ screenshot ਹੈ
ਇੱਕ static image ਇਹ ਨਹੀਂ ਦੱਸ ਸਕਦੀ ਕਿ stroke ਪਤਲਾ ਕਿਉਂ ਹੈ। ਇਹ ਸਿਰਫ਼ ਦੱਸ ਸਕਦੀ ਹੈ ਕਿ ਕਿੰਨਾ ਪਤਲਾ ਹੈ ਅਤੇ ਕਿੱਥੇ ਹੈ। ਇਸ ਲਈ ਪਹਿਲਾ ਕਦਮ measurement ਸੀ। ਅਸੀਂ screenshot ਨੂੰ row ਦਰ row ਅਤੇ column ਦਰ column scan ਕੀਤਾ, ਹਰ stroke ਦੀ centerline drift ਨੂੰ track ਕਰਕੇ ਉਸ ਦੀ direction ਲਈ, ਫਿਰ ਉਸ angle ਦੇ sine ਨਾਲ scan width ਨੂੰ true width ਵਿੱਚ ਬਦਲਿਆ:
| Stroke | Direction | True width |
|---|---|---|
| “l” ascender, “pencilkit” | ≈ 78° | 27.3 px |
| “k” stem, “pencilkit” | ≈ 90° | 28 px |
| 力 ਦਾ ਖੱਬੇ ਵੱਲ ਥੱਲੇ ਜਾਂਦਾ sweep | ≈ 66° | 25.7 px |
| Cursive connectors | ≈ 8° | 11 px |
| ਚੀਨੀ horizontals (横) | ≈ 0° | 6–11 px |
ਮੋਟੇ-ਤੋਂ-ਪਤਲੇ ਦਾ ਅਨੁਪਾਤ ≈ 2.5 ਹੈ। 66° point ਦੇ ਮੁਕਾਬਲੇ
Version one: direction ਤੋਂ width
ਪਹਿਲਾ model ਸਪਸ਼ਟ ਸੀ। ਹਰ input point ਲਈ direction ਗਿਣੋ, fitted curve ਰਾਹੀਂ direction ਨੂੰ width ਨਾਲ map ਕਰੋ ਅਤੇ capture ਵੇਲੇ ਨਤੀਜੇ ਨੂੰ point ਦੇ radius ਵਿੱਚ bake ਕਰੋ। Direction ਦਾ causal ਅੰਦਾਜ਼ਾ ਲਾਇਆ ਗਿਆ — arc ਦੇ ਆਖਰੀ ਕੁਝ points ਉੱਤੇ exponentially decaying weighted average, doubled-angle space ਵਿੱਚ compute ਕੀਤਾ ਗਿਆ ਤਾਂ ਜੋ movement ਦਾ reversal ਅੰਦਾਜ਼ੇ ਨੂੰ cancel ਨਾ ਕਰੇ। ਸਿਰਫ਼ ਪਿਛਲੇ points ਵਰਤੇ ਜਾਂਦੇ ਹਨ, ਇਸ ਲਈ live stroke ਅਤੇ committed stroke byte ਦਰ byte ਇੱਕੋ ਜਿਹੇ ਰਹਿੰਦੇ ਹਨ।
Builder ਆਪਣੀ unit check ਪਾਸ ਕਰ ਗਿਆ: synthetic horizontal, vertical, 45° ਅਤੇ reversal paths ਨੇ ਸਾਰੀਆਂ theoretical widths bake ਕੀਤੀਆਂ — 0.99, 2.52, 2.00 ਅਤੇ 0.99 points। Rendering ਲਈ ਕੋਈ ਨਵਾਂ code ਨਹੀਂ ਚਾਹੀਦਾ ਸੀ: baked-radius stroke ਸਿਰਫ਼ round stamp ਦੀ chain ਹੈ, ਅਤੇ ਸਾਡਾ point-sprite pipeline ਉਹਨਾਂ ਨੂੰ ਪਹਿਲਾਂ ਹੀ draw ਕਰਦਾ ਸੀ।
iPad ਉੱਤੇ ਮਨੁੱਖ ਨੇ ਇਸ ਨੂੰ ਲਗਭਗ ਦਸ ਸਕਿੰਟ ਵਿੱਚ ਰੱਦ ਕਰ ਦਿੱਤਾ: “ਇਹ art pen ਹੈ, ਜਲ-ਪੈੱਨ ਨਹੀਂ”।
ਦੋ ਵਿਆਖਿਆਵਾਂ ਇੱਕ ਤਸਵੀਰ ਵਿੱਚ ਫਿੱਟ ਹੁੰਦੀਆਂ ਹਨ
ਇਹ ਗਲਤ ਕਿਉਂ ਮਹਿਸੂਸ ਹੋਇਆ? ਦੋ ਸੰਭਾਵੀ ਵਿਆਖਿਆਵਾਂ ਸਨ ਅਤੇ screenshot ਉਨ੍ਹਾਂ ਨੂੰ ਵੱਖ ਨਹੀਂ ਕਰ ਸਕਦਾ ਸੀ:
- Direction lock। Nib ਦੀ geometry horizontals ਨੂੰ ਪਤਲਾ ਅਤੇ verticals ਨੂੰ ਮੋਟਾ ਰੱਖਦੀ ਹੈ, ਹੱਥ ਭਾਵੇਂ ਜੋ ਵੀ ਕਰੇ। Version one ਨੇ ਇਹੀ implement ਕੀਤਾ ਸੀ।
- Pressure ਅਤੇ speed। Pen pressure-driven ਹੈ, ਅਤੇ sample ਦਾ pattern ਸਿਰਫ਼ handwriting dynamics ਹੈ: downstroke ਉੱਤੇ ਕੁਦਰਤੀ ਤੌਰ ਉੱਤੇ ਵੱਧ ਦਬਾਅ ਹੁੰਦਾ ਹੈ, connectors ਕੁਦਰਤੀ ਤੌਰ ਉੱਤੇ ਤੇਜ਼ ਅਤੇ ਹਲਕੇ ਹੁੰਦੇ ਹਨ।
ਦੋਵੇਂ ਵਿਆਖਿਆਵਾਂ ਪਤਲੇ horizontals ਅਤੇ ਮੋਟੇ verticals ਵਾਲਾ screenshot ਬਣਾਉਂਦੀਆਂ ਹਨ। ਫ਼ਰਕ ਇਹ ਹੈ ਕਿ ਜਦੋਂ ਤੁਸੀਂ horizontal stroke ਉੱਤੇ ਜ਼ੋਰ ਨਾਲ ਦਬਾਉਂਦੇ ਹੋ ਤਾਂ ਕੀ ਹੁੰਦਾ ਹੈ। Version one ਇਸ ਨੂੰ ਪਤਲਾ ਰੱਖਦਾ ਹੈ। Pressure pen ਇਸ ਨੂੰ ਮੋਟਾ ਕਰਦੀ ਹੈ। ਇਸ ਲਈ ਅਸੀਂ ਮਨੁੱਖ ਨੂੰ ਇੱਕ ਸਵਾਲ ਪੁੱਛਿਆ: ਕੀ ਜ਼ੋਰ ਨਾਲ ਦਬਾਇਆ horizontal stroke ਹੋਰ ਮੋਟਾ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ?
“ਨਹੀਂ। Horizontals ਪਤਲੇ ਰਹਿੰਦੇ ਹਨ।”
Direction lock ਪੱਕਾ ਹੋ ਗਿਆ। ਪਰ ਕੁਝ ਹੋਰ ਗਲਤ ਸੀ, ਕਿਉਂਕਿ version one ਵੀ direction-locked ਸੀ।
ਜਵਾਬ stroke ਦੇ ਅੰਤਾਂ ਵਿੱਚ ਸੀ
ਅਗਲਾ clue ascender ਦੇ ਸਿਖਰ ਉੱਤੇ ਸੀ। Sample ਵਿੱਚ “l” ਅਤੇ “k” stems ਨੂੰ zoom ਕਰਨ ਨਾਲ ਦੋ ਗੱਲਾਂ ਦਿਖੀਆਂ: ਇੱਕ ਸਿੱਧਾ stroke ਸ਼ੁਰੂ ਤੋਂ ਅੰਤ ਤੱਕ ਇੱਕੋ width ਰੱਖਦਾ ਹੈ, ਅਤੇ stroke ends flat diagonal cuts ਹਨ — ਕਾਗਜ਼ ਤੋਂ ਉੱਠਦੀ chisel nib ਦਾ ਆਕਾਰ। Round dots ਨਹੀਂ। Pressure tapers ਨਹੀਂ।

“Art pen feel” ਦਾ ਅਸਲ ਮਤਲਬ ਇਹੀ ਸੀ। Version one ਨੇ sample ਦੇ look ਨੂੰ model ਕੀਤਾ — estimated direction ਦੇ function ਵਜੋਂ width — ਪਰ pen ਨੂੰ ਨਹੀਂ। Direction estimator ਇੱਕ sensor ਹੈ: noisy input ਉੱਤੇ jitter ਕਰਦਾ ਹੈ, corners ਨੇੜੇ lag ਕਰਦਾ ਹੈ ਅਤੇ ਹਰ stroke end ਨੂੰ circle ਬਣਾ ਦਿੰਦਾ ਹੈ। ਅਸਲੀ nib ਨੂੰ ਇਹਨਾਂ ਵਿੱਚੋਂ ਕੋਈ ਸਮੱਸਿਆ ਨਹੀਂ, ਕਿਉਂਕਿ ਇਹ ਕੁਝ compute ਨਹੀਂ ਕਰਦੀ। Width geometry ਹੈ।
Version two: ਇੱਕ ellipse nib
Final model ਵਿੱਚ direction estimator ਬਿਲਕੁਲ ਨਹੀਂ ਹੈ। Nib ਇੱਕ oriented ellipse ਹੈ: long axis horizontal, short axis fixed। ਇਸ ellipse ਦੇ stamp stroke ਦੇ path ਉੱਤੇ ਘਣੇ ਤੌਰ ਉੱਤੇ ਰੱਖੇ ਜਾਂਦੇ ਹਨ, ਅਤੇ ਬਾਕੀ ਸਭ geometry ਤੋਂ ਨਿਕਲਦਾ ਹੈ:
-
Horizontal stroke open edge ਦੇ ਨਾਲ ਚਲਦਾ ਹੈ, ਇਸ ਲਈ ਇਹ ਹਮੇਸ਼ਾ
ਚੌੜਾ ਹੁੰਦਾ ਹੈ — ਕਿਸੇ ਵੀ pressure ਉੱਤੇ ਇੱਕ constant thin line। Confirmed spec ਬਿਲਕੁਲ ਇਹੀ ਹੈ। -
Vertical stroke ਪੂਰੇ long axis ਨੂੰ ਪਾਰ ਕਰਦਾ ਹੈ:
, ਮੋਟਾ ਸਿਰਾ। -
Diagonal stroke movement ਦੇ perpendicular ellipse ਦੀ chord width ਲੈਂਦਾ ਹੈ,
-
Stroke ends elliptical cuts ਹਨ — sample ਵਿੱਚ nib-ਵਰਗੇ flat ends, ਬਿਨਾਂ ਕਿਸੇ ਵਾਧੂ ਕੰਮ ਦੇ।
-
Pressure ਸਿਰਫ਼ long axis ਨੂੰ scale ਕਰਦਾ ਹੈ,
, ਇਸ ਲਈ downstroke ਉੱਤੇ ink volume ਵਧਦਾ ਹੈ ਅਤੇ horizontal ਕਦੇ ਮੋਟਾ ਨਹੀਂ ਹੋ ਸਕਦਾ।
Sample ਦੀਆਂ measurements ਤੋਂ ਅਸੀਂ
Rendering ਲਈ ਇੱਕ ਨਵਾਂ fragment shader ਚਾਹੀਦਾ ਸੀ, ਹੋਰ ਕੁਝ ਨਹੀਂ। Vertex format — position, diameter, color — ਪਹਿਲਾਂ ਹੀ ਸਭ ਕੁਝ ਲੈ ਕੇ ਚੱਲ ਰਿਹਾ ਸੀ: diameter long axis ਹੈ ਅਤੇ short axis ਹਰ pass ਲਈ ਇੱਕ uniform ਹੈ। Shader ਸਾਡੇ round stamps ਵਾਲੀ ਉਹੀ half-pixel coverage ramp ਵਰਤ ਕੇ ellipse SDF evaluate ਕਰਦਾ ਹੈ, ਜਿਸ ਕਰਕੇ ਇਹ Core Graphics reference implementation (fillEllipse per stamp) ਨਾਲ pixel-ਤੁਲਨਾਯੋਗ ਰਹਿੰਦਾ ਹੈ। Validation ਆਮ gate ਵਿੱਚੋਂ ਲੰਘੀ: ਦੋ fountain strokes ਵਾਲਾ synthetic corpus, ਦੋਵੇਂ ਤਰੀਕਿਆਂ ਨਾਲ render ਕਰਕੇ pixel ਦਰ pixel compare ਕੀਤਾ — zero structural differences, ਅਤੇ spot-check ਕੀਤਾ horizontal stroke ਦੋਵੇਂ renderers ਵਿੱਚ 12 px ਮਾਪਿਆ ਗਿਆ।

Version one ਖੱਬੇ, version two ਸੱਜੇ। ਇੱਕੋ handwritten input, ਇੱਕੋ pipeline। Ends ਹੀ ਕਹਾਣੀ ਦੱਸਦੇ ਹਨ।
iPad ਉੱਤੇ ਨਵੀਂ pen ਤੁਰੰਤ ਪਾਸ ਹੋ ਗਈ: “好,很好” — ਚੰਗਾ। ਬਹੁਤ ਚੰਗਾ।
ਇਸ miss ਨੂੰ ਰੱਖਣਾ
Version one ਕੂੜੇ ਵਿੱਚ ਨਹੀਂ ਗਿਆ। ਇਹ ਇੱਕ ਦਿਲਚਸਪ brush ਹੈ — ਸਿਰਫ਼ ਜਲ-ਪੈੱਨ ਨਹੀਂ। ਇਸ ਲਈ ਇਸ ਨੂੰ ਨਵੇਂ experimental-brush menu ਵਿੱਚ ਪਹਿਲੀ entry ਵਜੋਂ ship ਕੀਤਾ ਗਿਆ, ਉਸ ਨਾਮ ਹੇਠ ਜੋ ਮਨੁੱਖ ਨੇ ਦਿੱਤਾ: 漏水的圆珠笔, ਲੀਕ ਕਰਦੀ ballpoint। Toolbar ਨੂੰ ਇੱਕ flask button ਮਿਲਿਆ ਜੋ experiments ਦੀ text list ਖੋਲ੍ਹਦਾ ਹੈ; ਅਗਲਾ ਜੋੜਨ ਲਈ registry ਵਿੱਚ ਇੱਕ line ਕਾਫ਼ੀ ਹੈ। ਰੱਦ ਕੀਤਾ model ਬਰਬਾਦ ਕੰਮ ਨਹੀਂ ਹੁੰਦਾ, ਜੇ ਇਸ ਨੂੰ ਸਪਸ਼ਟ ਤੌਰ ਉੱਤੇ label ਕੀਤੇ experiment ਵਜੋਂ ship ਕੀਤਾ ਜਾ ਸਕੇ।
loops ਦੀ ਇੱਕ ਸ਼ਾਮ
ਪੂਰਾ arc — measure, model, build, device, question, remodel, rebuild, validate — ਇੱਕ ਸ਼ਾਮ ਲੈ ਗਿਆ। Fireworks ਉੱਤੇ Kimi K3 ਨੇ loop ਦਾ ਸਾਰਾ technical ਪਾਸਾ ਚਲਾਇਆ: measurement scans ਬਣਾਏ, ਦੂਜੀ ਵਾਰ ਅੰਦਾਜ਼ਾ ਲਗਾਉਣ ਦੀ ਬਜਾਏ ਨਿਰਣਾਇਕ ਸਵਾਲ ਸੁਝਾਇਆ, evidence ਬਦਲਣ ਉੱਤੇ ਆਪਣਾ direction estimator delete ਕੀਤਾ ਅਤੇ app ਨੂੰ ਛੂਹਣ ਤੋਂ ਪਹਿਲਾਂ pixel-diff harness ਨੂੰ ਵਧਾਇਆ। Fireworks ਦੀ inference speed ਨੇ loop ਨੂੰ interactive ਰੱਖਿਆ — ਲੰਬੇ Metal ਅਤੇ Swift diffs, pixel-analysis scripts ਅਤੇ corpus tooling ਇੰਨੀ ਤੇਜ਼ੀ ਨਾਲ ਆਏ ਕਿ bottleneck ਉੱਥੇ ਹੀ ਰਿਹਾ ਜਿੱਥੇ ਰਹਿਣਾ ਚਾਹੀਦਾ ਸੀ: ਮਨੁੱਖੀ ਫੈਸਲਾ।
ਸਹਿਯੋਗ ਦਾ pattern ਉਹੀ ਸੀ ਜਿਸ ਬਾਰੇ Metal pipeline ਵਾਲੀ post ਵਿੱਚ ਲਿਖਿਆ ਸੀ, ਅਤੇ ਉਹ ਫਿਰ ਕੰਮ ਕਰ ਗਿਆ: model ਤੇਜ਼ ਅਤੇ precise ਹੈ; ਮਨੁੱਖ taste ਅਤੇ end-to-end verification ਦਾ ਮਾਲਕ ਹੈ। Feel ਉੱਤੇ ਆਧਾਰਿਤ feedback ਦੀ ਇੱਕ ਲਾਈਨ — “art pen, ਜਲ-ਪੈੱਨ ਨਹੀਂ” — model ਲਈ ਸਹੀ modeling error ਲੱਭਣ ਅਤੇ ਇਸ ਨੂੰ ਇੱਕ ਸੌਖੇ design ਨਾਲ ਬਦਲਣ ਲਈ ਕਾਫ਼ੀ ਸੀ।
ਸਹੀ model ਗਲਤ model ਨਾਲੋਂ ਛੋਟਾ ਨਿਕਲਿਆ। Version two ਵਿੱਚ version one ਨਾਲੋਂ ਘੱਟ moving parts ਸਨ — ਕੋਈ estimator ਨਹੀਂ, ਕੋਈ smoothing window ਨਹੀਂ, ਕੋਈ fitted exponent ਨਹੀਂ। Nib width compute ਨਹੀਂ ਕਰਦੀ। Nib ਹੀ width ਹੈ।