فهرست مستندات

زمان اجرای رندرینگ#

Environment یک سند مرورگرِ خنثی نسبت به موتور است، نه مترادف R3F، Three.js، WebGPU یا حتی سه‌بعدی. می‌تواند DOM، Canvas 2D، WebGL، WebGPU، WASM یا هر موتور قابل‌اجرای دیگری در مرورگر، مانند Three/R3F، Babylon، PlayCanvas، Phaser، Pixi، Unity Web یا Godot Web را ترکیب کند. همهٔ این انتخاب‌ها پشت همان قراردادهای میزبان، چرخهٔ عمر، هویت و امنیت Environment باقی می‌مانند.

نمایهٔ رسمی R3F الامر (al-amr.r3f-webgpu@1) که در ADR-0048 ثبت شده، یک نمایهٔ اختیاری نویسندگی و رندرینگ است. این نمایه از Three.js r185 با WebGPURenderer ساخته‌شده فقط داخل @al-amr/r3f و مواد سفارشی نوشته‌شده با TSL استفاده می‌کند. نمایه WebGPU-first است، نه WebGPU-only: وقتی WebGPU در دسترس نیست، همان renderer روی backend داخلی و واقعی WebGL2 خود اجرا می‌شود و یک دستگاه فقط برای این نمایه و زمانی پشتیبانی‌نشده است که هر دو backend شکست بخورند.

عضویت در نمایه، metadata محلی ابزارهاست. یک package در workspace وقتی عضو است که نام خودش @al-amr/r3f یا @react-three/fiber باشد، یا یکی از این markerها را در هر بخش dependency اعلام کند. Registry و schema عمومی Environment از این انتخاب موتور را استنتاج نمی‌کنند و هیچ فیلد engine، renderer یا threeVersion در manifest ندارند.

مرز conformance نیز همین تفکیک را دارد. runEnvironmentConformance عمومی قرارداد خنثی نسبت به موتور Environment را اعتبارسنجی می‌کند. یک Environment که این نمایه را bind می‌کند، علاوه بر آن runR3fEnvironmentConformance را به کار می‌برد؛ این runner بررسی‌های عمومی را با قواعد ترتیب gate در R3F و نبود canvas پیش از gate ترکیب می‌کند. انتخاب این نمایه بررسی‌های نویسندگی را اضافه می‌کند؛ تعریف Environment را تغییر نمی‌دهد.

اثبات کران‌دار PAP 0.1 مجموعهٔ عمومی را روی یک Environment فقط-DOM و بسته‌بندی‌شده اجرا می‌کند و سپس همان digest دقیق artifact را از مسیر میزبان product-runtime و web adapter ساخته‌شدهٔ Desktop باز می‌کند. وقتی Electron WebContents در دسترس نیست، اجرای system Chrome می‌تواند seam چرخهٔ عمر مرورگر را اثبات کند؛ بااین‌حال receipt باید این محدودیت را نام ببرد و نمی‌تواند جای conformance ویژهٔ Electron یا نمایهٔ R3F را بگیرد.

نسخه‌های cohort دقیقاً پین شده‌اند — three/@types/three نسخهٔ 0.185.1، @react-three/fiber نسخهٔ 9.6.1، @react-three/drei نسخهٔ 10.7.7 و react/react-dom نسخهٔ 19.2.7 — و پکیج‌های عمومی محدوده‌های peer کران‌دار اعلام می‌کنند. هر importer عضو نمایه در lockfile باید دقیقاً نسخهٔ Three.js همان نمایه را resolve کند. quality/rendering-profile.json و یک اسکریپت guardrail سیاست، cohort، قاعدهٔ ساخت renderer فقط از factory، بررسی Canvas خام و ممنوعیت GLSL را به همین اعضای نمایه محدود می‌کنند و Environmentهای عمومی را scan نمی‌کنند.

حالت‌های renderer#

رندرینگ در این نمایه از یک factory مشترک در @al-amr/r3f عبور می‌کند که three/webgpu را به‌صورت تنبل بارگذاری می‌کند و پیش از برگرداندن handle، renderer.init() را await می‌کند. دو حالت دائمی وجود دارد:

  • auto (پیش‌فرض) WebGPU را ترجیح می‌دهد و backend داخلی WebGL2 همان renderer را تنها fallback عملیاتی می‌داند. WebGL1 پشتیبانی نمی‌شود و WebGLRenderer کلاسیک دیگر در کد زمان اجرای جهان وجود ندارد.
  • force-webgl2 backend WebGL2 را (forceWebGL: true) برای fallback، آزمایش و بازیابی کاربر پین می‌کند.

نادیده‌گرفتن حالت توسط کاربر پیش از هر بوت resolve می‌شود: پارامتر query ‏al-amr-rendering (مانند ?al-amr-rendering=force-webgl2) بر مقدار پایدار al-amr:rendering-mode در localStorage — که پس از یک بازیابی موفق در حالت سازگار اجباری نوشته می‌شود — اولویت دارد. هر مقدار ناشناخته نادیده گرفته می‌شود، پس پیش‌فرض WebGPU-first همیشه پابرجاست.

قرارداد دروازه و بوم#

هیچ عضو نمایه‌ای new WebGPURenderer() نمی‌سازد و گزینهٔ خصوصی <Canvas gl={...}> پاس نمی‌دهد. هاب، گالری، سالن کنفرانس و قالب شروع CLI همگی همان دو مؤلفه را از @al-amr/r3f ترکیب می‌کنند:

  • EnvironmentRenderingGate یک probe قابلیت سریع و غیرمرجع — secure context، ساخت context WebGL2 و یک درخواست محدود adapter مربوط به WebGPU — پیش از فرزندانش و در نتیجه پیش از mountشدن حضور، پلاگین‌ها یا کد صحنه اجرا می‌کند. درخواست محدود adapter نخستین تلاش WebGPU همان بوت است. اگر این درخواست نتواند adapter بدهد ولی WebGL2 در دسترس باشد، حالت درخواستی auto باقی می‌ماند، اما نخستین تلاش Canvas با forceWebGL: true آغاز می‌شود و fallbackUsed: true گزارش می‌کند. به این ترتیب همان درخواست بدون محدودیت زمانی داخل Three تکرار نمی‌شود. حکم نهایی همچنان از مقداردهی واقعی backend انتخاب‌شده به دست می‌آید.
  • EnvironmentCanvas تنها نقطه‌ای است که renderer جهان را می‌سازد. انتخاب backend، نخستین فریم، گم‌شدن دستگاه و شکست را از طریق context مشترک رندرینگ به دروازه گزارش می‌کند.

R3F 9.6 می‌تواند تا وقتی configure() پیشین منتظر renderer ناهمگام است و state.gl هنوز خالی است، factory مربوط به gl را دوباره فراخوانی کند. Canvas مشترک همهٔ فراخوانی‌های یک attempt، عنصر canvas و پیکربندی renderer یکسان را در یک Promise و یک نسل renderer ادغام می‌کند. پذیرش R3F از مسیر onCreated نقطهٔ commit برای callbackهای انتخاب backend و آماده‌شدن renderer است. cleanup نیز با نسل attempt کلیدگذاری می‌شود تا cleanup دیرهنگام Canvas قدیمی نتواند نسل جایگزین را abort یا dispose کند. وقتی پیکربندی واقعاً تغییر می‌کند، نسل جایگزین پیش از شروع منتظر پایان task مقداردهی و dispose نسل قبلی می‌ماند؛ callbackهای device-loss و نخستین فریم نیز به نسل دقیق renderer قفل می‌شوند.

مقداردهی دقیقاً یک تلاش مجدد محدود دارد: وقتی probe محدود WebGPU یک adapter می‌دهد، شکست واقعی بعدی در حالت auto باعث disposeشدن تلاش و تکرار یک‌باره روی بومی تازه با forceWebGL: true می‌شود. اگر probe نتواند adapter بدهد، همان بوت مستقیماً با تلاش WebGL2 آغاز می‌شود. وقتی هر دو backend شکست بخورند، دروازه صفحهٔ پشتیبانی‌نشده را رندر می‌کند. دستگاه GPU گم‌شده یک دنبالهٔ بازیابی محدود را طی می‌کند — یک تلاش مجدد در حالت فعلی و سپس در حالت auto یک تلاش اجباری WebGL2 — پیش از آنکه دستگاه device_lost_unrecovered اعلام شود. هر تلاش قابل لغو است: تلاش‌های جایگزین‌شده یا unmountشده یک abort signal می‌گیرند که حول بارگذاری تنبل ماژول و مقداردهی بررسی می‌شود و هر renderer دقیقاً یک بار dispose می‌شود.

بوم یک سطح بیرونیِ مالکِ layout دارد و اندازهٔ منطقی و DPR آن را پیش از WebGPURenderer.init() اعمال می‌کند؛ همان زمانی که Three پیوست عمق پیش‌فرض را می‌سازد. R3F اندازهٔ اندازه‌گیری‌شدهٔ Canvas را تا resolveشدن factory ناهمگام gl اعمال نمی‌کند، بنابراین مقداردهی با اندازهٔ HTML پیش‌فرضِ canvas داخلی می‌تواند texture عمق کهنه را با نخستین پیوست رنگ واقعی ترکیب کند. سطح پس از مقداردهی دوباره اندازه‌گیری می‌شود و اگر ابعاد فیزیکی هنگام شروع تغییر کرده باشد، آن نسل renderer در یک حلقهٔ تثبیت محدود dispose و از نو ساخته می‌شود. هر تلاش بوم یک دوربین perspective واقعی و memoشده را به تمام ادامه‌های بازگشتیِ configure در R3F می‌دهد؛ ارسال گزینه‌های دوربین می‌تواند به یک ادامهٔ ناهمگام کهنه اجازه دهد دوربین اندازه‌گذاری‌شده را با دوربینی تازه و aspect صفر جایگزین کند. تلاش بازیابی نسل تازه‌ای از دوربین دریافت می‌کند. پذیرش R3F و نخستین فریم post-render اندازهٔ نهایی backing store، aspect دوربین و متناهی‌بودن ماتریس projection را دوباره می‌سنجند و پیش از readiness یک فریم اصلاح‌شدهٔ بعدی را لازم می‌دانند. همان probe پس از post-render در تمام عمر نسل renderer فعال می‌ماند و تغییر دیرهنگام اندازه، backing store، DPR یا projection دوربین را با بودجه‌ای محدود اصلاح می‌کند. اگر یک اندازه‌گیری ثابت همگرا نشود، به‌جای بازنویسی backing store و سیاه‌کردن هر فریم کنار گذاشته می‌شود؛ یک اندازه‌گیری تازه بودجه‌ای تازه می‌گیرد. بوم مشترک frameloop در R3F را عمداً روی "always" ثابت نگه می‌دارد و prop عمومی frameloop ندارد؛ کار on-demand باید invalidate() یا advance() را از داخل درخت canvas فراخوانی کند، جایی که store زنده است نه snapshot کهنهٔ پیکربندی ناهمگام.

بالای همین پین، راهِ مجازِ کاهش هزینهٔ رندر برای دنیاهایی که تمام روز باز می‌مانند قرار دارد: دندهٔ سکونِ رندر، به‌صورت opt-in. یک Environment یک IdleCadenceController را بیرون از canvas می‌سازد و از راه prop به نام cadence به آن می‌دهد؛ پس از نخستین فریمِ نشسته به‌علاوهٔ یک مهلت، canvas ارسالِ رندر را به‌دست می‌گیرد و تا وقتی بازدیدکننده بی‌کار است بیشتر tickها را رد می‌کند، و با ورودی، حرکت دوربین، تغییر سطح، فراخوانی wake() از سوی دامنه، یا یک اجارهٔ پیوستهٔ نگه‌داشته‌شده بی‌درنگ بیدار می‌شود. خودِ حلقه هم‌چنان tick می‌خورد — کادنس ارسالِ رندر را گیت می‌کند، نه frameloop را؛ پس تصمیم تاریخیِ کنارگذاشتن رندرِ تقاضامحور سر جایش می‌ماند و کادنس نقضش نمی‌کند. مالکِ RenderPipeline روی بومِ کادنس‌دار به‌جای useFrame با اولویت مثبت از راه useCadenceRenderDelegate() ثبت می‌شود، و probeهای سطح کیفیت و زمان‌سنجی GPU فقط فریم‌های رندرشدهٔ فازِ کامل را اندازه می‌گیرند تا فاصله‌گذاریِ سکون تصمیم‌های سطح را منحرف نکند. بدون این prop هیچ‌چیز mount نمی‌شود و رفتار ارسال دست‌نخورده می‌ماند.

منابع تولیدشدهٔ Three که با memoization در React ساخته می‌شوند از useDisposableRenderResource استفاده می‌کنند. این hook منابع مشترک را reference-count می‌کند و dispose نهایی را از cleanup آزمایشی StrictMode عبور می‌دهد. منبعی که کاملاً داخل یک effect ساخته می‌شود می‌تواند cleanup همان effect را نگه دارد. dispose مستقیم geometry یا material memoشده در cleanup عادی ممنوع است، چون StrictMode ممکن است همان شیء جاوااسکریپت را پس از نابودی GPU buffer آن دوباره مصرف کند.

آمادگی صادقانه است. محیط فقط پس از پذیرش renderer و تکمیل نخستین فریم در فاز post-render مربوط به R3F، یعنی پس از gl.render() و نه از subscriber پیش‌رندر useFrame، ready گزارش می‌کند و overlay بوت هرگز صفحهٔ پشتیبانی‌نشده را نمی‌پوشاند. هاب پیش از اعلام آمادگی بوت محیط، این فریم معتبر canvas را با سیگنال post-render مربوط به Active Zone جاری ترکیب می‌کند. تلاش مجدد یا mount دوبارهٔ renderer هرگز نشست‌های شبکه را از بین نمی‌برد: Multiplayer، SDK، حضور و وضعیت نشست بیرون از مرز Canvas زندگی می‌کنند و از mount مجدد renderer جان سالم به در می‌برند. مجوز حساب به Shell دسکتاپ و بیرون از نمای Environment تعلق دارد. یک bundle هرگز callback یا tokenهای OAuth را مدیریت نمی‌کند. وقتی رندرینگ پشتیبانی‌نشده از آب درمی‌آید، gate providerهای متعلق به Environment را unmount می‌کند، در حالی که نشست کنترل‌شده توسط میزبان در Shell بیرون از این lifecycle می‌ماند.

پشتیبانی و صفحهٔ پشتیبانی‌نشده#

پشتیبانی این نمایه مبتنی بر قابلیت است و هرگز فهرست مجاز user-agent نیست. یک دستگاه وقتی پشتیبانی‌شده است که محیط عملیاتی در secure context اجرا شود، WebGPURenderer واقعاً روی WebGPU یا WebGL2 مقداردهی شود و نخستین فریم بدون خطای اعتبارسنجی رندر شود. کندی هرگز پشتیبانی‌نشدن نیست؛ دستگاه‌های کند در عوض به سطح کیفیت پایین‌تر می‌افتند. موتورهای دیگر مالک سیاست پشتیبانی و fallback خودشان‌اند.

وقتی هیچ backend واقعی نتواند آغاز شود، gate نمایه یک صفحهٔ پشتیبانی‌نشدهٔ فقط-DOM و سازگار با RTL با راهنمای فارسی و انگلیسی، کنش‌های تلاش دوباره و سازگاری اجباری و یک کد دلیل قابل‌کپی نمایش می‌دهد. هیچ Canvas، پلاگین یا حضوری روی آن صفحه mount نمی‌شود. کدهای دلیل پایدار و ماشین‌خوانا که در packages/r3f/src/rendering-types.ts تعریف شده‌اند عبارت‌اند از:

کدمعنی
insecure_contextصفحه در context امن (HTTPS) اجرا نمی‌شود.
webgpu_init_failedمقداردهی WebGPU روی این دستگاه شکست خورد.
webgl2_unavailableهیچ context WebGL2ای ساخته نشد.
webgl2_init_failedتلاش اجباری سازگاری WebGL2 نیز شکست خورد.
hardware_acceleration_unavailableهیچ‌کدام از backendها در دسترس نیست؛ شتاب‌دهندهٔ سخت‌افزاری غیرفعال یا غایب است.
device_lost_unrecoveredدستگاه GPU گم شد و بازیابی محدود موفقیت‌آمیز نبود.
renderer_initialization_failedتلاش خودکار و تلاش اجباری هر دو در مقداردهی renderer شکست خوردند.
renderer_unknownشکست قابل دسته‌بندی نبود.

سیاست پشتیبانی رو‌به‌روی کاربر، ماتریس مرورگرهای آزمایش‌شده و راهنمای بازیابی در پشتیبانی از رندرینگ آمده است.

سطح‌های کیفیت#

backend (webgpu یا webgl2)، سطح کیفیت (compatible، standard یا enhanced) و در دسترس‌بودن قابلیت‌ها محورهایی مستقل از هم‌اند. انتخاب سطح با زمان فریم پایدار همراه با یک warm-up کوتاه و hysteresis انجام می‌شود — هرگز با مدل GPU یا VRAM، که شرط قطعی نیستند. پس از warm-up، کنترل‌کنندهٔ کیفیت میانگین پیراستهٔ زمان فریم هر پنجره را ارزیابی می‌کند — پنجره مرتب می‌شود، ۵٪ سریع‌ترین و ۵٪ کندترین کنار گذاشته می‌شوند و از بقیه میانگین گرفته می‌شود — و standard را فقط پس از پنجره‌های متوالیِ عبورکرده از آستانه به compatible تنزل می‌دهد. این سنجه عمداً صدک نیست: نمایشگر قفل‌شده به vsync فقط بازه‌های کامل نوسازی را گزارش می‌کند، پس روی ۶۰ هرتز هر نمونه یا حدود ۱۶.۷ms است یا حدود ۳۳.۳ms و نرخ فریم در نسبت میان این دو زندگی می‌کند — چیزی که صدک نمی‌تواند بخواند و میانگین می‌تواند. هر آستانه‌ای که اینجا تنظیم شود باید از کف ۱۶.۶۷ میلی‌ثانیه‌ایِ vsync هم فاصله بگیرد، وگرنه سخت‌گیر نیست بلکه دست‌نیافتنی است.

دستگاهِ تنزل‌یافته پس از یک دورهٔ پایدارِ پنجره‌های راحت دوباره به standard ارتقا می‌یابد. چون ارتقا دقیقاً همان باری را روشن می‌کند که سطح سنگین‌تر را تعریف می‌کند، سه سازوکار جلوی نوسان را می‌گیرند: یک cooldown پس از هر تغییر خودکار، یک backoff دوبرابرشونده برای هر ارتقایی که دوام نیاورد، و درز notifySceneChange که محیط هنگام تعویض Zone صدا می‌زند تا هزینهٔ ساختِ صحنه — کامپایل شیدر، آپلود بافت، تولید زمین — هرگز به‌جای هزینهٔ اجرای آن سنجیده نشود.

محیطی که تنظیم کیفیت ارائه می‌دهد آن را از مسیر setQualityPreference می‌فرستد، نه setQuality: یک سطح مشخص کنترل‌کننده را پین می‌کند و هر دو لِین خودکار را می‌خواباند، و "auto" اختیارشان را بازمی‌گرداند. نوشتن یک سطح در حالی که لِین‌ها زنده‌اند دعوا است نه ترجیح — لِین تنزل می‌دهد، همگام‌سازیِ تنظیماتِ محیط سطح را دوباره می‌نویسد، و جهان هر cooldown یک‌بار پلک می‌زند.

مسیر بازیابی سازگاری اجباری مستقیماً در compatible آغاز می‌شود. سطح enhanced فقط plumbing است: هرگز به‌صورت خودکار انتخاب نمی‌شود و تا رسیدن قابلیت‌های WebGPU-nativeاش به‌عنوان پشتیبانی عملیاتی معرفی نمی‌شود. تنزل سطح هرگز DPR بوم را وسط سشن تغییر نمی‌دهد: backing store یک‌بار و بر اساس سطح اولیه هنگام آغاز سشن renderer resolve می‌شود، بنابراین drawing buffer به‌خاطر تغییر سطح بازتخصیص (و برای لحظه‌ای خالی) نمی‌شود. سطح‌های پایین‌تر هزینه را از مسیر پیچیدگی صحنه — tessellation، instancing، سایه‌ها و post-processing — کم می‌کنند، نه از مسیر وضوح تصویر.

تشخیص و تله‌متری#

readRendererStats و detectRendererBackend تشخیص بک‌اند-خنثی روی هر دو backend فراهم می‌کنند (شمارش draw call، مثلث، geometry و texture و backend فعال)، و پل‌های end-to-end هاب و سالن کنفرانس backend فعال را با همین واژگان گزارش می‌کنند. حلقهٔ WebGPU شمارنده‌های هر فریم را پیش از useFrame در R3F صفر می‌کند؛ بنابراین تشخیص از useAfterRenderFrame و پس از gl.render() نمونه می‌گیرد. خواندن info.render.calls به‌عنوان مقدار هر فریم نیز ممنوع است، چون در WebGPU تجمعی است و readRendererStats مقدار drawCalls را انتخاب می‌کند. رخدادهای تله‌متری امن برای حریم خصوصی و محدود به یک فهرست مجاز بسته‌اند: نوع رخداد (backend-selected، first-frame، init-failed، unsupported، device-lost، recovered)، backend، استفاده از fallback، سطح کیفیت، سطل مدت مقداردهی، کد دلیل و نتیجهٔ بازیابی. فروشنده و مدل GPU، رشته‌های maskنشدهٔ renderer، VRAM، اطلاعات خام adapter، خطاهای خام درایور و سورسهای shader هرگز جمع‌آوری یا نمایش داده نمی‌شوند؛ صفحهٔ پشتیبانی‌نشده فقط کدهای دلیل پالایش‌شده را نشان می‌دهد.

زمان‌سنجی تجمیعی GPU#

محیط‌هایی که به مدت GPU مربوط به renderer نیاز دارند با گزینهٔ gpuTiming: "aggregate" روی EnvironmentCanvas فعالش می‌کنند؛ factory مشترک این گزینه را به ردیابی timestamp در renderer نگاشت می‌کند. پیش‌فرض off است که در آن هیچ پرس‌وجوی زمانی‌ای هرگز تخصیص نمی‌یابد. پس از مقداردهی، خودِ backend ردیابی را وقتی دستگاه فاقد قابلیت timestamp است غیرفعال می‌کند، پس فعال‌کردن این گزینه روی دستگاه پشتیبانی‌نشده امن است و صرفاً هیچ مقداری تولید نمی‌کند.

نمونه‌ها بدون اینکه حلقهٔ رندر هرگز متوقف شود resolve می‌شوند: هر ۴۵ فریم رندرشده (حدود ۷۵۰ میلی‌ثانیه در ۶۰ فریم بر ثانیه) زمان اجرای بوم یک resolve ناهمگام timestamp برای صف‌های render و compute آغاز می‌کند و نتیجه را هر زمان که readback برسد تحویل می‌دهد. هر نمونهٔ resolve‌شده مدت‌های کلِ renderer را گزارش می‌کند — { sampledFrameRenderMs, sampledFrameComputeMs, framesSinceLastSample } — یعنی میلی‌ثانیه‌های GPU یک فریم، آخرین فریم دسته‌ای که three رزالو کرده، بدون هیچ مقیاس‌گذاری. framesSinceLastSample تراکم نمونه‌برداری است و هرگز مخرج نیست؛ تقسیم مدت‌ها بر آن، زمان GPU را به اندازهٔ کل آهنگ نمونه‌برداری کمتر از واقع گزارش می‌کند. این‌ها زمان‌های کل renderer هستند، هرگز زمان اختصاصی یک Plugin یا Avatar نیستند. روی backend داخلی WebGL2 وقتی extension زمان‌سنج آن موجود باشد مقادیر جریان می‌یابند؛ EXT_disjoint_timer_query هم‌زمان فقط یک پرس‌وجوی فعال می‌پذیرد، پس انتظار می‌رود render context های تودرتو آنجا پرس‌وجوی مستقل نگیرند — محدودیتی که استدلال کرده‌ایم ولی اندازه نگرفته‌ایم. صفی که هیچ کاری ثبت نکرده (compute در صحنه‌ای فقط-رندر) صفر گزارش می‌کند؛ resolveی که وسط بازیابی شکست بخورد نمونه‌اش را حذف می‌کند تا عدد ساختگی گزارش نشود.

مصرف‌کننده‌ها آخرین نمونه را از طریق callback مربوط به onGpuTimingWindow در بوم یا hook عمومی useRendererGpuTiming() می‌خوانند که تا وقتی گزینه خاموش است، قابلیت موجود نیست یا هنوز هیچ پنجره‌ای resolve نشده، null برمی‌گرداند؛ دروازه مقدار را هنگام گم‌شدن دستگاه، پشتیبانی‌نشدن و تلاش دوباره نیز صفر می‌کند تا مدت‌های کهنه از یک نسل renderer به نسل بعدی سرایت نکند. resolver همراه با نسل renderer خود dispose می‌شود، پس پس از unmount یا گم‌شدن دستگاه هیچ readback معلقی باقی نمی‌ماند.

زمان‌سنجی GPU یک تشخیص محلی است، نه تله‌متری: مقادیر فقط مدت به میلی‌ثانیه‌اند، تا وقتی محیط میزبان خودش آن‌ها را ارسال نکند از پروسه خارج نمی‌شوند و هیچ اطلاعاتی از فروشنده، مدل، VRAM یا adapter مربوط به GPU در آن‌ها نیست. چون هیچ رخداد تله‌متری‌ای منتشر نمی‌شود، telemetryAllowlist در quality/rendering-profile.json آگاهانه بدون تغییر باقی می‌ماند.

مواد TSL-first#

WebGPURenderer اجرا نمی‌کند: ShaderMaterial، RawShaderMaterial، وصله‌های onBeforeCompile و EffectComposer قدیمی را — حتی روی backend داخلی WebGL2 خود. بنابراین مواد سفارشی تازه فقط با TSL/NodeMaterial (three/tsl) نوشته می‌شوند. مواد عملیاتی نخستین خارج از Park — ذرات مسیر قدسی هاب، پردهٔ دروازهٔ درخشان هاب و آسمان گرادیانی — با برابری بصری در فرمول‌ها، رنگ‌ها، blending، شفافیت، depthWrite و depthTest تبدیل شده‌اند. یک بررسی ایستای سیاست، نقاط ورود GLSL تازه را در کد زمان اجرای جهانِ عضو نمایه رد می‌کند. بازطراحی WebGPU-native — compute shader، storage buffer، GPU culling، indirect draw و post-processing با TSL — آگاهانه به فاز بعدی موکول شده است.

دارایی‌های باینری glTF#

جهان‌های first-party رویه‌ای‌اند و مخزن هیچ دارایی سه‌بعدی باینری منتشر نمی‌کند. این یک انتخاب است، نه محدودیت: برای محصولی که از ایران سرو می‌شود، وابستگی به CDN، دانلود transcoder از میزبانی که ممکن است در دسترس نباشد و ایست بوت روی کش سرد را حذف می‌کند. با این حال محیط‌ها و پلاگین‌های شخص ثالث به یک مسیر بارگذاری دارند، بنابراین ADR-0079 دقیقاً یک مسیر اضافه می‌کند: useGltfPack از @al-amr/r3f، که فقط داخل درخت EnvironmentCanvas قابل استفاده است. GLTFLoader پیکربندی‌شده export نمی‌شود.

هیچ چیز از یک شبکهٔ توزیع محتوا گرفته نمی‌شود. KTX2Loader و DRACOLoader به ASSET_DECODER_BASE_PATH (/al-amr/decoders/) روی مبدأ خودِ محیط اشاره می‌کنند، که از باینری‌های vendor‌شده در پکیج سرو می‌شود و اسکریپتی که پکیج ارائه می‌دهد آن‌ها را در public/ کپی می‌کند. هیچ مسیر دیکودری هرگز از import.meta.url مشتق نمی‌شود، چون آن مسیر زیر باندل تولیدی حل می‌شود و زیر سرور توسعه نه. useGLTF از @react-three/drei اینجا قابل استفاده نیست: در نسخهٔ pin‌شدهٔ cohort، Draco را از gstatic و transcoder مربوط به Basis را از jsDelivr می‌گیرد.

GLTFLoader، KTX2Loader، DRACOLoader و MeshoptDecoder تنها در نخستین بارگذاری و با import پویا وارد می‌شوند — دقیقاً همان‌طور که factory رندرر three/webgpu را می‌آورد — پس هیچ‌کدام به گراف اولیهٔ جاوااسکریپت نمی‌رسند. هر بارگذاری کران زمانی دارد (پیش‌فرض ۲۰ ثانیه) و AbortSignal را رعایت می‌کند، چون WorkerPool در درخت افزونه‌های Three هیچ listener خطایی ثبت نمی‌کند و هیچ rejectی نگه نمی‌دارد: workerای که می‌میرد، promise خود را تا ابد بی‌نتیجه رها می‌کند. بارگذاری دارایی مجاز است شکست بخورد؛ مجاز نیست بوت را معلق کند.

هر لودر به یک رندرر تعلق دارد. detectSupport فقط روی رندرر مقداردهی‌شده معتبر است، WebGPU فرمت‌هایش را از hasFeature گزارش می‌کند، و دو backend به فرمت‌های GPU متفاوتی transcode می‌کنند — پس هویت خودِ شیء رندرر، نسل کش است. این هویت دقیقاً هنگام رویدادهایی عوض می‌شود که یک تکسچر رمزگشایی‌شده را باطل می‌کنند، از جمله رویدادهایی که هیچ شمارندهٔ دروازه‌ای ردشان را نمی‌گیرد: تلاش مجدد کران‌دار روی WebGL2 اجباری، حلقهٔ تثبیت سطح، و بازیابی از دست رفتن دستگاه. با تغییر آن، لودر و worker poolش dispose می‌شوند، ورودی‌های کش آن رندرر آزاد می‌شوند و لودر از نو ساخته می‌شود. پک‌ها با useDisposableRenderResource شمارش مرجع می‌شوند، پس دو زون که یک URL را می‌خواهند یک‌بار دانلود و transcode می‌کنند و منابع با رفتن آخرین مصرف‌کننده آزاد می‌شود.

بارگذاری ناموفق یک خطای برنامه است. AssetLoadError یک reason پایدار حمل می‌کند — network، decode، timeout، aborted، renderer_unavailable — و به RenderingErrorBoundary می‌رسد؛ نیامدن یک مدل هرگز به معنی دستگاه پشتیبانی‌نشده نیست، پس کدهای دلیل بالا تغییر نکرده‌اند و صفحهٔ پشتیبانی‌نشده هرگز برای آن نمایش داده نمی‌شود. متن خطا از همان پاک‌سازی خطاهای رندرر عبور می‌کند و هیچ مسیر داخلی یا جزئیات دستگاهی حمل نمی‌کند.

scripts/check-performance-budget.mjs کنار بودجهٔ جاوااسکریپت، یک لِین دارایی سه‌بعدی برای هر محیط اندازه می‌گیرد: مجموع بایت glTF، مجموع بایت تکسچر، و رد کردن هر تکسچری در یک بستهٔ دارایی سه‌بعدی که نه KTX2 است و نه WebP. دیکودرهای vendor‌شده بیرون هر دو لِین‌اند — بایت پلتفرم‌اند و هرگز در گراف اولیه نیستند.

استثناها و موارد مستثنا#

  • boot loader یک سطح بوت است، نه renderer جهان، و اصلاً هیچ‌چیزی از پشتهٔ گرافیکی نمی‌خواهد. مارک‌آپ و CSS و یک اسکریپت درون‌خطی بود که alAmrEnvironmentHandshake() آن را در سند هر محیط می‌نوشت، پس در اولین فریم سند نقاشی می‌شد نه بعد از رسیدن یک باندل. ADR-0094 حذفش کرد: کلاینت دسکتاپ تنها میزبان است و خودش بوت را در یک view بالای سندِ جهان می‌کشد. خاصیتش عوض نشده — چیزی که اول نقاشی می‌شود هنوز از پشتهٔ گرافیکی هیچ نمی‌خواهد.

    این جایگزین دیورامایی از جنس WebGPU شد که در یک Worker روی OffscreenCanvas رندر می‌کرد و دستگاهش را فقط پس از پخش کاملِ کوریوگرافی پس می‌داد — یعنی روی هر رفرشِ گرم دو دستگاه حدود ۵٫۵ ثانیه زنده بودند و هرکدام دیگری را کند می‌کرد، دقیقاً روی سخت‌افزاری که کمترین توان پرداخت هیچ‌کدام را داشت. آن استثنای renderer که ADR-0048 برای آن موتور ثبت کرده بود با خودش برداشته می‌شود: حالا در تمام طول یک بوت دقیقاً یک دستگاه گرافیکی روی صفحه است، و docs/design/boot-experience.md (قاعدهٔ R9) همان‌جا نگهش می‌دارد.

  • قابلیت‌های WebGPU-native و هر قرارداد سازگاری موتور در سطح Registry، درست همان‌طور که در ADR-0048 ثبت شده، به تعویق مانده‌اند.