Menyimulasikan runtime yang berkata tidak: cara kami membuat sandbox browser agar bertindak seperti WeChat
Oleh Oleg Sidorkin, CTO dan Co-Founder Cinevva

Kami baru-baru ini merilis mode mini game WeChat di Game Creator kami: profil build yang menjaga game buatan AI tetap berada dalam subset platform web yang dapat bertahan ketika di-porting ke runtime WeChat. Profil ini berupa serangkaian aturan pembuatan. Tanpa UI DOM, tanpa Pointer Lock, audio hanya mp3, dan tanpa kode dinamis. Model mengikuti aturan tersebut sebagaimana model biasanya mengikuti aturan, yaitu: biasanya.
“Biasanya” tidak cukup ketika setiap pelanggaran tidak terlihat di browser tetapi berakibat fatal setelah diekspor. Game dengan bilah kesehatan HTML berjalan sempurna di pratinjau kami, tetapi hanya menampilkan layar kosong di WeChat karena mini game WeChat sama sekali tidak memiliki DOM. Jadi, kami membangun sesuatu yang mengubah aturan tersebut dari sekadar saran menjadi hukum fisika: sandbox mode ketat yang membuat browser itu sendiri menolak melakukan hal-hal yang tidak dapat dilakukan WeChat. Tulisan ini membahas cara kerjanya.
Keputusan inti: simulasikan ketiadaan, bukan keberadaan
Pendekatan yang paling jelas adalah mengemulasikan WeChat: mengimplementasikan wx.createCanvas, wx.onTouchStart, wx.setStorageSync, lalu menjalankan game terhadap permukaan API wx.* yang diemulasikan. Kami memilih arah sebaliknya, dan alasannya terletak pada bentuk pipeline ekspor kami.
Game dalam mode WeChat tetap ditulis menggunakan API browser. Saat pengemasan, sebuah adaptor (dengan pendekatan weapp-adapter yang menjadi standar komunitas) memetakan API browser tersebut ke wx.*. Artinya, game kami tidak pernah memanggil wx.* secara langsung, sehingga emulator wx akan menguji jalur kode yang sebenarnya tidak ada. Penyebab port gagal bukanlah ketiadaan wx.* di browser. Penyebabnya adalah keberadaan API browser yang tidak memiliki padanan di WeChat dan diam-diam digunakan selama proses pembuatan. document.createElement('div'). requestPointerLock(). IndexedDB. File .ogg yang dapat didekode dengan baik di Chrome tetapi tidak akan pernah bisa di WeChat pada iOS.
Karena itu, sandbox menyimulasikan ketiadaan. Segala sesuatu yang tidak dimiliki WeChat dihapus, diracuni, atau ditandai dalam pratinjau, sehingga sejak frame pertama game dikembangkan di dalam irisan kemampuan kedua platform.
Tempatnya berada: service worker yang sudah tersedia
Pratinjau editor kami memiliki jalur penyajian yang tidak biasa sehingga penerapan ini nyaris tanpa biaya. Game di editor tidak disajikan dari jaringan. Sebuah service worker mencegat permintaan /game/* dan menyajikan file dari IndexedDB, sehingga iframe pratinjau dapat dimuat ulang secara instan tanpa perjalanan bolak-balik ke server. Worker tersebut sudah menyuntikkan dua file virtual ke setiap halaman game: skrip penangkap error yang meneruskan output konsol dan crash ke editor, serta pemeriksa adegan secara langsung.
Sandbox menjadi file virtual ketiga, _wechat-strict.js, yang disuntikkan ke <head> halaman tepat setelah skrip penangkap dan sebelum kode game apa pun dijalankan. Urutan ini sangat krusial karena dua alasan. Sandbox harus berjalan setelah skrip penangkap agar panggilan console.error miliknya sudah terhubung dan diteruskan ke editor, serta sebelum modul game agar racun sudah terpasang ketika baris pertama kode game dieksekusi.
Aktivasinya menggunakan trik satu baris. Toggle WeChat di kreator disimpan dalam localStorage, dan iframe pratinjau memiliki origin yang sama dengan editor, sehingga harness cukup membaca flag tersebut secara langsung:
try {
if (localStorage.getItem('cinevva-gc-profile') !== 'wechat-minigame') return;
} catch (e) { return; }Untuk setiap game non-WeChat, skrip ini hanya melakukan satu perbandingan string lalu keluar lebih awal. Tidak ada flag build, perjalanan bolak-balik ke server, maupun state yang perlu disinkronkan dengan service worker.
Daftar racun dan taksonomi dua tingkat
Tidak setiap pelanggaran layak mendapat respons yang sama, sehingga harness membedakan dua tingkat. API yang tidak tersedia di WeChat akan melempar error karena itulah yang terjadi setelah ekspor, dan crash saat pembuatan merupakan pratinjau yang jujur atas crash saat peninjauan. Hal-hal yang tersedia tetapi akan bermasalah kemudian ditandai sebagai error konsol yang mencolok tanpa mengubah perilakunya, karena memblokirnya justru akan menyembunyikan keadaan game yang sebenarnya dari orang yang sedang mengiterasinya.
Tingkat lempar error: pembuatan elemen UI HTML apa pun melalui createElement maupun createElementNS (Three.js membuat canvas melalui varian NS, sehingga kedua jalur memerlukan gerbang yang sama), eval, new Function, requestPointerLock, dan pendaftaran service worker. Setiap error yang dilempar menyertakan cara memperbaikinya di dalam pesan:
throw violate("document.createElement('" + tag + "') — Mini game WeChat tidak memiliki DOM. " +
"Gambar UI di canvas (canvas 2D offscreen -> THREE.CanvasTexture pada quad screen-space) " +
"dan lakukan hit-test ketukan sendiri.");Tingkat penandaan: membaca indexedDB (mengembalikan undefined, persis seperti WeChat, ditambah error yang mengarahkan ke localStorage), audio dalam format apa pun yang tidak dapat didekode oleh WeChat iOS (diperiksa di tiga tempat: konstruktor Audio, setter src pada prototipe elemen media, dan URL yang diambil), permintaan jaringan ke origin apa pun selain milik game atau CDN aset kami (di WeChat, origin tersebut memerlukan domain yang masuk daftar izin dan terdaftar ICP), serta kemunculan Tone.js dalam bentuk apa pun.
Ada juga audit pascapemuatan yang berjalan sesaat setelah halaman stabil: audit ini menelusuri <body> dan melaporkan elemen HTML apa pun yang berasal dari markup milik game, bukan dari pembuatan dinamis. HUD statis dapat lolos dari gerbang createElement, jadi gerbang tersebut saja tidak cukup.
Setiap laporan dideduplikasi berdasarkan pesan dan dibatasi maksimal lima puluh per sesi. Bug loop terbalik yang membuat satu div per frame hanya menghasilkan satu error, bukan banjir pesan yang menenggelamkan sinyal penting.
Empat pengecualian yang paling banyak memberi kami pelajaran
Membangun sandbox sebagian besar berarti memutuskan apa yang tidak boleh diracuni, dan setiap pengecualian yang kami buat berasal dari alat kami sendiri yang rusak selama pengembangan.
Debugger kami berjalan menggunakan eval. Alat execute_js milik kreator, yang digunakan AI untuk memeriksa game yang sedang berjalan, mengevaluasi kode melalui handler pesan milik skrip penangkap, yang memanggil eval. Meracuni eval secara naif akan membutakan AI itu sendiri. Solusinya: sebelum meracuni, harness menyimpan fungsi asli pada properti non-enumerable, lalu handler skrip penangkap menggunakannya sebagai fallback:
Object.defineProperty(window, '__cinevvaRealEval', { value: window.eval, enumerable: false });
window.eval = function () { throw violate('eval() dilarang dalam mini game WeChat.'); };
// skrip penangkap, saat menerima pesan:
returnValue = (window.__cinevvaRealEval || eval)(e.data.code);Kode game yang mencoba menggunakan eval tetap berhenti. Debugger tidak.
Perekam juga membuat elemen. Perekam reel kami disuntikkan ke iframe game sebagai <script> yang dibuat dengan document.createElement milik iframe itu sendiri, dan perekam mengunduh video yang selesai melalui klik sintetis pada <a>. Memblokir tag tersebut akan merusak perekaman secara khusus dan hanya dalam mode WeChat. Karena itu, script tetap diizinkan dan a memunculkan peringatan tanpa melempar error. Game yang benar-benar menyertakan tautan tetap akan ditandai, sementara alat kami tetap berfungsi.
Event keyboard tetap tersedia. AI memverifikasi kontrol menggunakan alat yang menyintesis penekanan tombol dan membaca kembali perubahan state game. Menonaktifkan input keyboard untuk menyimulasikan ponsel akan menghancurkan loop verifikasi kontrol yang mendeteksi bug kamera terbalik. Pendekatan touch-first diterapkan melalui aturan pembuatan dan skema kontrol profil, bukan oleh sandbox.
WebGL2 tetap tersedia, dan alasannya layak dibahas dalam bagian tersendiri di bawah. Draf daftar racun memblokir getContext('webgl2') untuk memaksakan rendering “aman untuk WebGL1” yang diwajibkan profil. Namun, Three.js menghapus dukungan WebGL1 sepenuhnya pada r163, sementara kami menetapkan r181: memblokir WebGL2 akan membuat setiap game 3D dalam mode ini menjadi kosong. Aturan rendering profil tersebut tidak koheren karena ditulis berdasarkan pemahaman usang tentang platform. Audit sandbox justru mengaudit spesifikasi sandbox itu sendiri, dan ketika kami menelusuri persoalan tersebut lebih jauh, kami menemukan sesuatu yang penting.
Pertanyaan tentang WebGL2, dijawab dengan semestinya
Membiarkan pembuatan context apa adanya memunculkan pertanyaan lanjutan yang jelas: jika engine kami memerlukan WebGL2, apakah game akan rusak pada perangkat WeChat yang hanya memiliki WebGL1? Penelusuran atas pertanyaan tersebut menghasilkan gambaran paling jelas yang kami miliki tentang batas minimum rendering platform, jadi berikut penjelasannya beserta sumber.
Di Android, runtime mini game WeChat menyediakan WebGL2 pada setiap base library modern, dan perangkat keras GLES3 yang mendasarinya pada praktiknya tersedia secara universal. iOS adalah persoalan sebenarnya. Dokumentasi teknis WeChat sendiri untuk dukungan WebGL2 dan mode high-performance+ menjelaskan bahwa pada iPhone, WebGL2 hanya tersedia dengan benar dalam runtime high-performance+, yang memerlukan klien WeChat terbaru (8.0.45 atau lebih baru, dengan versi lebih tinggi pada iOS 14) dan pada praktiknya membutuhkan iOS 15.5 atau lebih baru. Mode high-performance biasa menjalankan WebGL2 “dengan lebih banyak masalah”, menurut deskripsi Tencent sendiri, sedangkan runtime iOS normal sama sekali tidak memilikinya.
Bagian yang benar-benar berbahaya adalah bentuk kegagalannya. Dalam lingkungan yang tidak didukung, getContext('webgl2') dapat mengembalikan context yang truthy tetapi rusak, bukan null, sebuah perilaku yang telah diminta langsung oleh developer agar diperbaiki Tencent. Game yang memercayai nilai kembalian tersebut tidak akan gagal dengan bersih. Game itu akan merender gambar kacau atau layar hitam tanpa pesan.
Jadi, kami menanganinya dalam tiga lapisan. Sandbox membiarkan pembuatan context WebGL2 apa adanya karena meracuninya berarti melawan engine kami sendiri. Profil pembuatan kini mewajibkan boot gate di setiap game: pembuatan renderer dibungkus dengan try/catch, lalu dilanjutkan dengan pemeriksaan fungsional (typeof gl.createVertexArray === 'function', kemampuan khusus WebGL2 yang tidak akan dimiliki context palsu), dengan pesan ramah “silakan perbarui WeChat” yang digambar di canvas ketika gagal, alih-alih layar kosong. Ini adalah pola yang sama dengan mini game hasil konversi Unity, sehingga di penggunaan nyata Anda melihat permintaan upgrade, bukan layar hitam. Dan ketika exporter kami tersedia, exporter tersebut akan menetapkan mode high-performance+ di game.json agar jalur yang mampu menjalankannya menjadi jalur default.
Berapa besar pengguna yang terdampak oleh batas minimum WebGL2? Pengguna dengan versi di bawah sekitar iOS 15.5 atau klien WeChat yang lebih lama dari 8.0.45—persentase satu digit rendah pada 2026, tetapi terkonsentrasi pada perangkat lama, dan dampaknya lebih penting bagi genre tertentu dibandingkan genre lainnya. Jika data distribusi nyata kelak menunjukkan bahwa kelompok kecil tersebut layak dikejar, kami menyimpan jalan keluar murah sebagai cadangan: menetapkan profil WeChat ke three r162, rilis terakhir yang mendukung WebGL1. Game profil hanya menggunakan API inti yang stabil, sehingga downgrade tersebut cukup berupa perubahan satu baris pada import map, dan sengaja belum kami gunakan sampai data menuntutnya.
Menutup loop bersama model
Inilah bagian yang membuat pembangunan ini layak dilakukan untuk produk yang mengutamakan AI. Setelah setiap build, agen kreator memanggil alat yang membaca konsol game dan menunggu proses boot selesai. Skrip penangkap meneruskan console.error ke aliran tersebut. Jadi, pelanggaran [wechat-strict] bukanlah peringatan yang mungkin dilewati manusia saat menggulir. Pelanggaran itu masuk ke kanal yang sama persis dengan yang sudah diperiksa model sebelum menyatakan build selesai, pada giliran yang sama, dengan cara perbaikan yang dijelaskan langsung dalam pesannya.
Profil pembuatan menutup celah terakhir dengan satu instruksi: perlakukan setiap pesan [wechat-strict] sebagai bug yang menggagalkan build, perbaiki penyebabnya, dan jangan pernah mencoba mendeteksi atau melewati harness. Game yang hanya berjalan ketika sandbox dimatikan adalah game yang akan gagal setelah diekspor, dan model diberi tahu persis tentang hal tersebut.
Pada dasarnya, sandbox mengubah masalah kepatuhan yang kabur (“apakah model mengikuti keempat belas aturan?”) menjadi loop debugging yang sudah dikuasai sistem (“konsol menampilkan error, perbaiki”).
Hal-hal yang tidak dapat disimulasikan browser
Bagian kejujuran. Sandbox ini menangkap pelanggaran pada permukaan API, yang menurut perkiraan kami mencakup sebagian besar penyebab kegagalan port. Sandbox ini tidak dapat mendeteksi: performa pada ponsel sungguhan, iOS yang menjalankan JavaScript tanpa JIT, keunikan implementasi WebGL milik WeChat, atau perbedaan perilaku codec selain yang dapat diketahui dari ekstensi file. Semua itu memerlukan runtime asli.
Karena itu, sandbox adalah lingkar pertama dari tiga. Lingkar kedua, setelah exporter kami menghasilkan proyek WeChat DevTools, adalah simulator resmi yang dijalankan secara headless melalui DevTools CLI dan miniprogram-automator: boot paket hasil ekspor, pastikan frame pertama dirender, lalu skripkan satu ketukan. Lingkar ketiga adalah jalur perangkat resmi, yaitu kode QR pratinjau dan debugging jarak jauh pada ponsel fisik, tempat berbagai fakta yang tidak dapat diungkap oleh apa pun selain perangkat asli muncul. Setiap lingkar lebih lambat dan lebih akurat daripada lingkar sebelumnya, dan tugas masing-masing adalah membuat kebutuhan untuk masuk ke lingkar berikutnya menjadi jarang.
Jika ingin mencoba mode ini, gunakan toggle “mode mini game WeChat” di Game Creator. Untuk konteks pasar dan regulasi seputar semua ini, lihat panduan lapangan kami untuk menjangkau setengah miliar pemain WeChat.
Referensi
- Dokumentasi resmi WeChat: dukungan JavaScript dalam mini game
- Dokumentasi resmi WeChat: lapisan adaptor
- Dokumentasi resmi WeChat: mode high-performance+
- Dokumentasi rekayasa WeChat: dukungan rendering WebGL2 dalam mini game
- Dokumentasi rekayasa WeChat: mode high-performance dan high-performance+ di iOS
- Komunitas pengembang WeChat: konteks webgl2 yang rusak seharusnya mengembalikan null
- Dokumentasi resmi WeChat: miniprogram-automator
- Catatan rilis Three.js r163 (dukungan WebGL1 dihapus)