רשימות פיתוח

רומן חזותי הוא אוסף של תמונות. למה Builder נעשה מורכב כל כך?

איך אבטיפוס קטן בשם Fibber צמח לפלטפורמה מבוזרת עם שיתוף פעולה, פרסום לפי גרסאות ו-story runtime משותף.

מבחוץ רומן חזותי נראה כמעט מובן מאליו: רקע, דמות עם שקיפות, תיבת טקסט, כמה בחירות שמובילות לסצנה הבאה וקצת מוזיקה. כך בדיוק ראיתי אותו כשהתחלתי לבנות את הגרסה הראשונה.

אני עדיין חושב שהליבה פשוטה. היום, במיוחד בעזרת AI, כמעט כל מפתח יכול ליצור player לרומן חזותי. הקושי מתחיל כשהמטרה כבר אינה ליצור סיפור אחד, אלא לבנות כלי שבו אדם אחר, אולי אפילו ילד, יוכל ליצור, לבדוק, לפרסם ולהמשיך לפתח סיפור משלו.

לגרסה הראשונה היה תפקיד אחד

לפני ש-Romergo נקרא Romergo, הוא היה Fibber. הוא הרכיב quest אחת מסצנות מחוברות, אפשר לי למלא אותן בטקסט ובתמונות ותמך בבחירות מסועפות. ה-stack היה TypeScript, React, Strapi ו-Ant Design.

הגרסה הזאת עשתה את עבודתה. ה-flow graph הפך את הסיפור לגלוי, אפשר היה לערוך סצנות ולהציג את התוצאה בתצוגה מקדימה. לאבטיפוס זה הספיק. זה גם הספיק כדי לחשוף את הבעיה האמיתית.

צוואר הבקבוק האמיתי הראשון לא היה הקוד

כשהתחלנו למלא quest אמיתית בטקסט ובתמונות, הרכבת סצנה של עשר דקות יכלה לקחת כמעט יום שלם. ליצור סצנה. להעלות רקע. להעלות דמות. למקם אותם. להוסיף דיאלוג. לחבר את הסצנה הבאה. לחזור על הכול.

חלק מזה היה בעיית ממשק. שיפרתי את ה-UX, האצתי העלאות והפכתי את הפרסום לאמין יותר. אבל העלות הגדולה ביותר נותרה ללא שינוי: אדם עדיין נדרש לבצע ידנית כל פעולה קטנה.

מה אם שני אנשים יוכלו ליצור בו-זמנית?

התשובה הראשונה הייתה פשוטה: אם אדם אחד הוא צוואר הבקבוק, נאפשר לכמה אנשים לעבוד על אותו סיפור. התחלתי עם Liveblocks, למדתי מהמודל הזה ובהמשך עברתי לבניית שכבת שיתוף משלי עם Yjs בליבה.

הניסוי למטה נראה כמו שני חלונות editor שמשתנים יחד. מתחתיו נמצא שינוי גדול הרבה יותר בדרך שבה המוצר חושב: עריכות נעשות עדכונים למסמך משותף במקום שליחות נפרדות של טפסים. מכאן מגיעים presence, חיבור מחדש, טיפול בהתנגשויות, הרשאות והציפייה שהפרויקט תמיד חי.

כל קיצור דרך שימושי הפך בסוף לגבול

Strapi היה דרך מצוינת לקבל backend במהירות, אבל כל יכולת חדשה וייחודית לסיפור דרשה מה-CMS להתנהג קצת פחות כמו CMS. Ant Design נתן לממשק הראשון מהירות ועקביות, ואז הגביל בהדרגה את המוצר בתוך המערכת החזותית של מישהו אחר.

גרסת serverless מאוחרת יותר שהתבססה על Remix הייתה צעד חשוב. למרות זאת, הדרך שבה שילבתי את היישום, ה-media וה-deployment לא נתנה את החופש האופקי שהמוצר החל לדרוש. אף בחירה לא הייתה טעות. כל אחת קנתה לי מספיק זמן כדי לגלות את המגבלה הבאה.

תמונות אינן קבצים מצורפים. הן תשתית.

Media הפכה לשיעור בפני עצמו. צריך להעלות תמונת סיפור פעם אחת, לעבד אותה כשצריך, לאחסן אותה בבטחה ולספק אותה במהירות לכל player בעולם. שמירתה לצד application server שכבר גדל רק הכבידה על ה-backend, ולכן הפתרון הנפרד הראשון היה Cloudinary.

ערב אחד פתחתי את לוח הבקרה וראיתי שמשתמש יחיד צרך כ-500 MB כשהעלה את אותה תמונה מאות פעמים. התיקון הדחוף שלי השווה image hashes ודחה כפילויות. הוא עבד, אבל rate limits ומכסות היו מטפלים בהתנהגות באופן ישיר יותר. ההפתעה הגדולה הייתה bandwidth: ערב בדיקות אחד יכול היה לצרוך יותר מעשרה אחוזים מהמכסה החינמית.

האירוע הזה שינה את המודל. Media כבר לא יכלה להיות שדה בסצנה. היא נזקקה ל-pipeline משלה לאחסון, מניעת כפילויות, אספקה, cache וגישה.

איך נראה היום הרעיון הפשוט

Romergo הנוכחי מפריד בין Builder ל-player, אבל שניהם משתמשים באותו story runtime. התצוגה המקדימה בתוך ה-editor והמשחק שפורסם פועלים לפי אותם כללים, ולכן קשה הרבה יותר ליצור contract drift.

Yjs מסנכרן את הפרויקט הניתן לעריכה בין משתפי הפעולה. הפרסום יוצר snapshot בדוק של נתוני הסיפור וה-media שלו, כך שיוצרים יכולים להמשיך לשנות את הטיוטה בלי לשנות בשקט את הגרסה שהשחקנים כבר מריצים. ה-API בנוי עם Hono, הממשק משתמש ב-primitives של shadcn ו-Radix, ושכבת ה-cloud מספקת תיאום מבוזר, object storage ואספקה דרך CDN.

ה-shared runtime נושא כעת את הסיפור לדפדפן ול-PWA, ל-Telegram ולחוויה מסונכרנת ב-Discord. תאימות בין גרסאות, פעולה offline ומצב multiplayer אינם עוד תופעות לוואי סביב דף. הם מערכות מוצר בפני עצמן.

הארכיטקטורה הנוכחית

סיפור אחד הניתן לעריכה. חוזה אחד שפורסם.

תרשים כללי של הארכיטקטורה הנוכחית של Romergo.

1. פרויקט הניתן לעריכה

  • Builder UI: סצנות · flow · media
  • AI / MCP: פעולות עריכה מאומתות
  • Realtime / Yjs: Durable Objects · מסמך עבודה משותף
  • Editor API: כתיבות CAS · יצירת הטיוטה
  • Draft runtime snapshot: PublishedQuestLocalizedContentV2 · D1 / R2
  • Media של הטיוטה: אובייקטי R2 · מטא-נתונים ב-D1

2. פרסום לפי גרסאות

  • בדיקות פרסום: Runtime schema · אישור המחבר · קיום media
  • פרסום בלתי משתנה vN: Runtime snapshot · media שהועתקה ומופתה מחדש
  • חוזה נראות: ציבורי · לא רשום · פרטי

3. חוזה הפעלה אחד

  • Snapshot של הטיוטה: המצב האחרון הניתן לעריכה
  • פרסום vN: מצב player יציב
  • Shared player-runtime: סצנות · מעברים · תנאים · משתנים · שמירות
  • תצוגה מקדימה ב-Builder: מריצה את הטיוטה באותו מנוע

4. ערוצי הפעלה

  • Web / PWA: Player בדפדפן
  • Telegram Mini App: Player מוטמע
  • Discord Activity: משחק קבוצתי מסונכרן

5. תשתית הפלטפורמה

  • Clerk + OAuth: זהות וגישה ל-MCP
  • Hono Workers: שירותי API ו-MCP
  • Cloudflare D1: פרויקטים · גרסאות · מטא-נתונים
  • Cloudflare R2: Media · runtime snapshots גדולים
  • Durable Objects: חדרי Yjs · הפעלות Discord
  • התקדמות השחקן: סנכרון D1 · תור מקומי offline

הרומן עדיין אינו אלא תמונות

החלק המשעשע הוא שההנחה המקורית שרדה. רומן חזותי עדיין מורכב מרקעים, דמויות, טקסט, בחירות וצליל. Romergo נעשה מורכב כי כל מה שסביב הליבה הפשוטה הזאת היה צריך להיות אמין: שיתוף פעולה, media, פרסום, גרסאות, משחק offline וכמה דרכים לחוות את אותו סיפור.

אם ההתפתחות הזאת לימדה אותי דבר אחד, הוא למדוד את כל המסלול האנושי ולא רק את הקוד שמציג את המסך הסופי. ייתכן ש-player זקוק לחמישה רכיבים בסיסיים. מוצר שעוזר להפוך רעיון לסיפור שלם שאפשר לשחק בו זקוק למערכת.

חזרה לבלוג הפיתוח