Kimi K3 ने डिजिटल फाउंटन पेनचा स्ट्रोक कसा तयार केला
Lulucat Notes मध्ये Freeform चे दिशाधारित फाउंटन पेन हवे होते. विशिष्टता हाताने लिहिलेल्या मजकुराचा एक स्क्रीनशॉट होती. दोन मॉडेल्स, एक भेद करणारा प्रश्न आणि एक लंबवर्तुळाकार पेननिब यांनंतर, वॉटर पेन जसा लिहायला हवा तसा लिहू लागला. संशोधन, कोड आणि प्रमाणीकरण Kimi K3 ने Fireworks वर केले.

या लेखाचा विषय असलेल्या Lulucat Notes Metal pipeline ने render केलेले.
Lulucat Notes मध्ये एक साधा पेन आणि एक हायलायटर आहे. Roadmap वरील पुढचे tool होते water pen — Apple Notes आणि Freeform मधून परिचित असलेले दिशाधारित fountain pen, ज्यात vertical strokes जाड आणि horizontal strokes पातळ येतात. या tool ची spec एखादा document नव्हती. ती एक screenshot होती: handwritten तीन ओळी, “pencilkit”, “无边记” आणि “这种有方向的水笔能力” हे शब्द — ही directional water-pen ability.
आधीच्या Metal pipeline प्रमाणे, हे काम Fireworks वर चालणाऱ्या Moonshot च्या open model Kimi K3 ने केले: measurement, modeling, code आणि validation सगळे. माणसाने screenshot दिला, एका प्रश्नाचे उत्तर दिले आणि खऱ्या iPad वर त्याचा feel तपासला.
Spec म्हणजे एक screenshot
एक static image एखादा stroke पातळ का आहे हे सांगू शकत नाही. तो फक्त तो किती पातळ आहे आणि कुठे पातळ आहे हे सांगू शकतो. म्हणून पहिली पायरी measurement होती. आम्ही screenshot row by row आणि column by column scan केला, प्रत्येक stroke चा centerline drift track करून त्याची direction मिळवली, आणि मग त्या angle च्या sine ने scan width चे true width मध्ये रूपांतर केले:
| स्ट्रोक | दिशा | खरी रुंदी |
|---|---|---|
| “l” चा अॅसेंडर, “pencilkit” | ≈ 78° | 27.3 px |
| “k” चा दांडा, “pencilkit” | ≈ 90° | 28 px |
| 力 चा डावीकडे उतरणारा स्विप | ≈ 66° | 25.7 px |
| कर्सिव्ह जोडरेषा | ≈ 8° | 11 px |
| चिनी आडव्या रेषा (横) | ≈ 0° | 6–11 px |
जाड-पातळ गुणोत्तर ≈ 2.5. 66° बिंदूशी
आवृत्ती एक: दिशेपासून रुंदी
पहिले model साहजिक वाटणारे होते. प्रत्येक input point साठी direction calculate करायची, fitted curve द्वारे direction ला width मध्ये map करायचे, आणि capture वेळी result point च्या radius मध्ये bake करायचा. Direction चे causal estimate काढले गेले — arc वरील शेवटच्या काही points वर exponentially decaying weighted average, travel उलटले तरी estimate cancel होऊ नये म्हणून doubled-angle space मध्ये calculate केलेला. फक्त past points वापरले जातात, त्यामुळे live stroke आणि committed stroke byte for 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 stamps ची फक्त एक chain, आणि आमचे point-sprite pipeline ते आधीच draw करत होते.
iPad वर तो नाकारायला माणसाला सुमारे दहा सेकंद लागले: “ही art pen आहे, water pen नाही.”
एका चित्राला दोन स्पष्टीकरणे लागू पडतात
ते चुकीचे का वाटले? दोन candidate explanations होत्या, आणि screenshot त्यांच्यात फरक करू शकत नव्हता:
- दिशेचा लॉक. हात काहीही करो, nib geometry horizontals पातळ आणि verticals जाड ठेवते. Version one ने हेच implement केले.
- दाब आणि वेग. Pen pressure-driven आहे, आणि sample मधील pattern म्हणजे फक्त handwriting dynamics: downstrokes स्वाभाविकपणे दाबलेले, connectors स्वाभाविकपणे जलद आणि हलके.
दोन्ही thin horizontals आणि thick verticals असलेला screenshot तयार करतात. फरक असा की horizontal stroke वर जोरात दाबल्यावर काय होते. Version one तो पातळ ठेवते. Pressure pen तो जाड करते. म्हणून आम्ही माणसाला एक प्रश्न विचारला: जोरात दाबलेला horizontal अधिक जाड व्हायला हवा का?
“नाही. Horizontals पातळच राहतात.”
Direction lock निश्चित झाला. पण अजून काहीतरी चुकत होते, कारण version one देखील direction-locked होते.
उत्तर stroke ends मध्ये होते
पुढचा clue ascenders च्या वरच्या टोकावर होता. Sample मधील “l” आणि “k” stems zoom केल्यावर दोन गोष्टी दिसल्या: straight stroke सुरुवातीपासून शेवटपर्यंत एकच width राखतो, आणि stroke ends flat diagonal cuts आहेत — वर उचलल्या जाणाऱ्या chisel nib चा आकार. Round dots नाहीत. Pressure tapers नाहीत.

“Art pen feel” चा खरा अर्थ हा होता. Version one ने sample चा look — estimated direction चे function म्हणून width — model केला, पण pen model केला नाही. Direction estimator हा sensor आहे: noisy input वर तो jitter करतो, corners जवळ lag करतो आणि प्रत्येक stroke end ला circle मध्ये round करतो. खऱ्या nib ला यापैकी कोणतीही अडचण नसते, कारण तो काहीही compute करत नाही. Width ही geometry आहे.
आवृत्ती दोन: एक लंबवर्तुळाकार पेननिब
Final model मध्ये direction estimator अजिबात नाही. Nib हा oriented ellipse आहे: long axis horizontal, short axis fixed. या ellipse चे stamps stroke च्या path वर दाटीने ठेवले जातात, आणि बाकी सगळे geometry मधून मिळते:
-
Horizontal stroke open edge वरून पुढे जातो, त्यामुळे तो नेहमी
रुंदीचा येतो — कोणत्याही pressure मध्ये constant thin line. Confirm झालेली spec अगदी हीच. -
Vertical stroke संपूर्ण long axis ओलांडतो:
, जाड टोक. -
Diagonal stroke ला travel च्या perpendicular असलेली ellipse ची chord width मिळते,
-
Stroke ends हे elliptical cuts असतात — sample मधील flat nib-shaped ends, कोणतेही अतिरिक्त काम न करता.
-
Pressure फक्त long axis scale करते,
, त्यामुळे pressure downstrokes वर ink volume वाढवते आणि horizontal ला कधीही fatten करू शकत नाही.
Sample measurements मधून आम्ही
Rendering ला एक नवीन fragment shader आणि त्यापलीकडे काहीही लागले नाही. Vertex format — position, diameter, color — आधीच सर्व काही वाहून नेत होता: diameter हा long axis आहे आणि short axis हा per-pass uniform आहे. Shader आमच्या round stamps प्रमाणेच half-pixel coverage ramp वापरून ellipse SDF evaluate करतो. त्यामुळे तो Core Graphics reference implementation (fillEllipse per stamp) शी pixel-comparable राहतो. Validation ने नेहमीचा gate पार केला: दोन fountain strokes असलेला synthetic corpus दोन्ही पद्धतींनी render करून pixel by pixel compare केला — रचनात्मक फरक शून्य; तपासलेला आडवा स्ट्रोक दोन्ही रेंडरर्समध्ये 12 px मोजला गेला.

Version one डावीकडे, version two उजवीकडे. तोच handwritten input, तेच pipeline. Ends सगळे सांगतात.
iPad वर नवीन pen लगेच pass झाला: “好,很好” — चांगले. खूप चांगले.
चुकलेला प्रयत्न ठेवून घेणे
Version one कचऱ्यात गेला नाही. तो एक रंजक brush आहे — फक्त water pen नाही. म्हणून माणसाने दिलेल्या नावाखाली, नवीन experimental-brush menu मधील पहिली entry म्हणून तो ship झाला: 漏水的圆珠笔, leaky ballpoint. Toolbar ला flask button मिळाले; ते experiments ची text list उघडते. पुढचा experiment add करण्यासाठी registry मध्ये एक line पुरेशी आहे. स्पष्ट label असलेल्या experiment म्हणून ship करता येत असेल, तर rejected model हे वाया गेलेले काम नाही.
एका संध्याकाळी पुनरावृत्ती
संपूर्ण arc — measure, model, build, device, question, remodel, rebuild, validate — एका संध्याकाळी पूर्ण झाला. Fireworks वरील Kimi K3 ने loop ची संपूर्ण technical बाजू चालवली: measurement scans आखणे, दुसऱ्यांदा अंदाज बांधण्याऐवजी discriminating question मांडणे, evidence बदलल्यावर स्वतःचा direction estimator काढून टाकणे, आणि app ला हात लावण्यापूर्वी pixel-diff harness वाढवणे. Fireworks च्या inference speed मुळे loop interactive राहिला — लांब Metal आणि Swift diffs, pixel-analysis scripts आणि corpus tooling इतक्या वेगाने आले की उरलेला bottleneck human judgment होता.
Metal pipeline वरील post मध्ये दिसलेला collaboration pattern हाच होता, आणि तो इथेही टिकला: model वेगवान आणि अचूक आहे; taste आणि end-to-end verification माणसाच्या जबाबदारीत आहेत. Feel-based feedback चे एक वाक्य — “art pen, not water pen” — model ला अचूक modeling error शोधून त्याऐवजी अधिक साधी design ठेवण्यासाठी पुरेसे ठरले.
योग्य model चुकीच्या model पेक्षा लहान निघाला. Version two ने version one पेक्षा कमी moving parts सह ship केले — estimator नाही, smoothing window नाही, fitted exponent नाही. Nib width compute करत नाही. Nib म्हणजेच width.