განვითარების ჩანაწერები
ვიზუალური ნოველა სურათების ნაკრებია. რატომ გახდა Builder ასეთი რთული?
როგორ გადაიქცა პატარა პროტოტიპი Fibber განაწილებულ პლატფორმად, რომელსაც აქვს თანამშრომლობა, ვერსიული გამოქვეყნება და ისტორიების საერთო runtime.
გარედან ვიზუალური ნოველა თითქმის ელემენტარული ჩანს: ფონი, გამჭვირვალე პერსონაჟი, ტექსტის ველი, რამდენიმე არჩევანი მომდევნო სცენაზე გადასასვლელად და მუსიკა. პირველ ვერსიაზე მუშაობის დაწყებისას მეც ზუსტად ასე ვუყურებდი.
დღემდე ვფიქრობ, რომ ბირთვი მარტივია. დღეს, განსაკუთრებით AI-ის დახმარებით, თითქმის ნებისმიერ დეველოპერს შეუძლია ვიზუალური ნოველის player-ის შექმნა. სირთულე იწყება მაშინ, როცა მიზანი ერთი ისტორიის გაკეთება კი არა, ისეთი ხელსაწყოს შექმნაა, რომელშიც სხვა ადამიანი, შესაძლოა ბავშვიც, საკუთარ ისტორიას შექმნის, შეამოწმებს, გამოაქვეყნებს და განავითარებს.
პირველ ვერსიას ერთი ამოცანა ჰქონდა
სანამ Romergo-ს Romergo ერქმეოდა, ის Fibber იყო. დაკავშირებული სცენებიდან ერთ quest-ს აწყობდა, მათ ტექსტითა და სურათებით შევსების საშუალებას მაძლევდა და განშტოებულ არჩევანს უჭერდა მხარს. Stack იყო TypeScript, React, Strapi და Ant Design.
ვერსია თავის საქმეს ასრულებდა. Flow graph ისტორიას ხილულს ხდიდა, სცენები რედაქტირდებოდა და შედეგის preview შეიძლებოდა. პროტოტიპისთვის ეს საკმარისი იყო და ნამდვილი პრობლემის დასანახადაც საკმარისი აღმოჩნდა.
პირველი ნამდვილი შეზღუდვა კოდი არ იყო
როცა რეალური quest-ის ტექსტითა და სურათებით შევსება დავიწყეთ, ათწუთიანი სცენის აწყობას თითქმის მთელი დღე სჭირდებოდა. შექმენი სცენა. ატვირთე ფონი. ატვირთე პერსონაჟი. განათავსე ისინი. დაამატე დიალოგი. დააკავშირე შემდეგ სცენასთან. გაიმეორე.
ნაწილობრივ ეს interface-ის პრობლემა იყო. გავაუმჯობესე UX, დავაჩქარე ატვირთვა და გამოქვეყნება უფრო საიმედო გავხადე. მაგრამ მთავარი ხარჯი უცვლელი დარჩა: ყველა პატარა მოქმედება ადამიანს ისევ ხელით უნდა შეესრულებინა.
რა მოხდება, თუ ორი ადამიანი ერთდროულად შექმნის?
პირველი პასუხი მარტივი იყო: თუ ერთი ადამიანია bottleneck, ერთ ისტორიაზე რამდენიმე ადამიანს ვამუშაოთ. Liveblocks-ით დავიწყე, ამ model-ისგან ვისწავლე და შემდეგ Yjs-ზე დაფუძნებული საკუთარი collaboration layer-ისკენ გადავედი.
ქვემოთ ექსპერიმენტი ორ სინქრონულად ცვალებად editor window-ს ჰგავს. მის უკან უფრო დიდი ცვლილებაა: edit-ები იზოლირებული form submission-ის ნაცვლად shared document-ის update ხდება. აქედან ჩნდება presence, reconnect, conflict handling, permission და მოლოდინი, რომ project ყოველთვის ცოცხალია.
ყოველი სასარგებლო მოკლე გზა საბოლოოდ საზღვარი გახდა
Strapi backend-ის სწრაფად მიღების შესანიშნავი გზა იყო, მაგრამ ისტორიის ყოველი ახალი ფუნქცია CMS-ს აიძულებდა, ნაკლებად დამსგავსებოდა CMS-ს. Ant Design-მა პირველ interface-ს სისწრაფე და თანმიმდევრულობა მისცა, შემდეგ კი product სხვის visual system-ში თანდათან შეზღუდა.
Remix-ზე დაფუძნებული შემდგომი serverless iteration მნიშვნელოვანი ნაბიჯი იყო. მიუხედავად ამისა, application-ის, media-სა და deployment-ის გაერთიანება product-ს საჭირო ჰორიზონტალურ თავისუფლებას არ აძლევდა. არცერთი არჩევანი არ იყო შეცდომა: თითოეულმა მომდევნო შეზღუდვის აღმოსაჩენად დრო მომცა.
სურათები დანართები არ არის. ისინი ინფრასტრუქტურაა.
Media ცალკე გაკვეთილად იქცა. ისტორიის სურათი ერთხელ უნდა აიტვირთოს, საჭიროებისას გარდაიქმნას, უსაფრთხოდ შეინახოს და მსოფლიოს ნებისმიერ player-ს სწრაფად მიეწოდოს. მზარდ application server-თან შენახვა backend-ს ამძიმებდა, ამიტომ პირველი ცალკე გადაწყვეტა Cloudinary იყო.
ერთ საღამოს dashboard-ზე ვნახე, რომ ერთმა user-მა ერთი და იგივე სურათის ასობით ატვირთვით დაახლოებით 500 MB დახარჯა. გადაუდებელი fix image hash-ებს ადარებდა და duplicate-ებს უარყოფდა. მუშაობდა, თუმცა rate limit და quota ქცევას უფრო პირდაპირ მოაგვარებდა. კიდევ უფრო მოულოდნელი bandwidth იყო: ერთ საღამოს testing-ს უფასო ლიმიტის ათ პროცენტზე მეტი შეეძლო დაეხარჯა.
ამ შემთხვევამ model შეცვალა. Media სცენის უბრალო field ვეღარ იქნებოდა. მას storage, deduplication, delivery, caching და access-ის საკუთარი pipeline სჭირდებოდა.
როგორ გამოიყურება მარტივი იდეა დღეს
დღევანდელი Romergo Builder-ს player-ისგან გამოყოფს, თუმცა ორივე ერთ story runtime-ს იყენებს. Editor-ის preview და გამოქვეყნებული playthrough ერთ წესებს ემორჩილება, ამიტომ contract drift-ის გაჩენა გაცილებით რთულია.
Yjs რედაქტირებად project-ს მონაწილეებს შორის ასინქრონებს. გამოქვეყნება story data-სა და media-ს შემოწმებულ snapshot-ს ქმნის, ამიტომ creators draft-ს ცვლიან ისე, რომ მოთამაშეების მიმდინარე version ჩუმად არ იცვლება. API აგებულია Hono-ზე, interface იყენებს shadcn და Radix primitives-ს, cloud layer კი distributed coordination-ს, object storage-სა და CDN delivery-ს უზრუნველყოფს.
Shared runtime ისტორიას browser და PWA-ში, Telegram-სა და synchronized Discord experience-ში მიაქვს. Version compatibility, offline behavior და multiplayer state გვერდის გარშემო side effect აღარ არის. ისინი დამოუკიდებელი product system-ებია.
მიმდინარე architecture
ერთი რედაქტირებადი ისტორია. ერთი გამოქვეყნებული contract.
Romergo-ს მიმდინარე architecture-ის განზოგადებული სქემა.
1. რედაქტირებადი project
- Builder UI: სცენები · flow · media
- AI / MCP: ავტორიზებული editing operations
- Realtime / Yjs: Durable Objects · shared working document
- Editor API: CAS writes · draft materialization
- Draft runtime snapshot: PublishedQuestLocalizedContentV2 · D1 / R2
- Draft media: R2 objects · D1 metadata
2. ვერსიული გამოქვეყნება
- გამოქვეყნების შემოწმება: Runtime schema · ავტორის დადასტურება · media-ს არსებობა
- უცვლელი გამოქვეყნება vN: Runtime snapshot · copied და remapped media
- ხილვადობის contract: Public · unlisted · private
3. ერთი execution contract
- Draft snapshot: უახლესი რედაქტირებადი მდგომარეობა
- გამოქვეყნება vN: სტაბილური player state
- Shared player-runtime: სცენები · transitions · conditions · variables · saves
- Builder preview: Draft-ს იმავე engine-ში უშვებს
4. Playback channels
- Web / PWA: Browser player
- Telegram Mini App: Embedded player
- Discord Activity: სინქრონული ჯგუფური თამაში
5. Platform infrastructure
- Clerk + OAuth: იდენტობა და MCP access
- Hono Workers: API და MCP services
- Cloudflare D1: Projects · versions · metadata
- Cloudflare R2: Media · დიდი runtime snapshots
- Durable Objects: Yjs rooms · Discord sessions
- მოთამაშის პროგრესი: D1 sync · local offline queue
ნოველა კვლავ მხოლოდ სურათებია
სასაცილოა, რომ თავდაპირველი ვარაუდი გადარჩა. ვიზუალური ნოველა კვლავ ფონი, პერსონაჟები, ტექსტი, არჩევანი და ხმაა. Romergo გართულდა, რადგან ამ მარტივი ბირთვის გარშემო ყველაფერი საიმედო უნდა გამხდარიყო: collaboration, media, publishing, versioning, offline play და ერთი ისტორიის განცდის რამდენიმე გზა.
ამ განვითარებამ ერთი რამ მასწავლა: უნდა გავზომოთ ადამიანის მთელი გზა და არა მხოლოდ კოდი, რომელიც საბოლოო ეკრანს აჩვენებს. Player-ს შეიძლება ხუთი primitive ეყოს. Product-ს, რომელიც იდეის დასრულებულ, სათამაშო ისტორიად ქცევაში გვეხმარება, system სჭირდება.