Ringkasan Utama
The Archipelago adalah game portofolio 3D yang dibangun ChatGPT Astra dari brief produk sepanjang 1.015 baris: enam fase yang masing-masing punya gate, loop screenshot headless agar agent bisa melihat hasil kerjanya sendiri, dan tiga langkah yang tetap dikerjakan manusia. Tulisan ini mengurai ideasi, kerangka kerja, proses build, serta bug gerakan dan warna yang hanya terlihat oleh test.
Setiap kali membuka halaman portofolio saya sendiri, yang terlihat selalu sama: grid kartu, dua belas proyek, dan tidak ada alasan untuk mengklik satu pun. Karya di bawah lipatan layar — ERP yang menjalankan lantai pabrik, dua produk dengan pengguna nyata, enam sertifikat — sama saja disimpan di laci. September 2026 saya memutuskan portofolio ini harus menjadi tempat yang dijelajahi, bukan halaman yang di-scroll, dan saya memutuskan untuk tidak membangunnya dengan tangan.
Ini catatan bagaimana halaman itu menjadi The Archipelago, game 3D yang bisa dijelajahi di /play, dan bagaimana ChatGPT Astra membangunnya: dari brief yang saya tulis, melewati enam fase dengan gate, sampai pass kedua yang memperbaiki hal-hal yang hanya bisa dilihat test. Commit, output test, dan screenshot-nya saya simpan, jadi setiap angka di sini punya sumber.

Idenya: portofolio yang dijelajahi, bukan di-scroll
Konsepnya lahir sebelum satu baris kode pun, dari satu aturan: sebuah game butuh satu tujuan dalam tiga puluh detik pertama. Tujuan kami: nyalakan mercusuar sebelum gelap. Anda tiba dengan perahu saat fajar, bermain sebagai saya, dan cahaya siang adalah progress bar-nya: fajar, pagi, sore, senja, satu langkah untuk setiap pulau yang selesai. Setiap pulau adalah satu bagian situs, jadi metaforanya sekaligus menentukan rekayasanya: satu pulau adalah satu chunk yang di-stream saat Anda berjalan ke arahnya.
- Empat pulau di rilis pertama: Pelabuhan, Pulau Bengkel dengan dua belas proyek, Teluk Produk dengan dua produk, dan Mercusuar dengan enam sertifikasi — total 22 exhibit.
- Commit dan merge sebagai koleksi: membuka satu exhibit mencatat satu commit di logbook, menyelesaikan satu pulau adalah satu merge, dan langit bergerak maju.
- Tiga babak, sekitar tujuh menit dari awal sampai akhir, dengan kartu finale yang berubah menjadi ajakan untuk menghubungi saya.
- Baris narasi dalam sudut pandang orang pertama, present tense, paling banyak 22 kata, ditulis langsung dalam bahasa Inggris dan Indonesia, bukan diterjemahkan.
Satu aturan desain yang saya pertahankan adalah aturan paritas: exhibit diturunkan dari modul yang sama dengan yang dirender halaman situs — lib/projects.ts, lib/certifications.ts, dan lib/products.ts — dan tidak pernah diketik ulang. Sebuah gate prebuild menggagalkan build jika ada proyek tanpa penempatan di dunia game, atau penempatan tanpa proyek. Satu aturan itulah yang membuat game tidak bisa menyimpang dari situs.
Brief adalah prompt-nya: PRD 1.015 baris
Saya tidak memberi prompt 'buatkan game portofolio 3D'. Saya minta agent riset dulu, lalu menulis product requirements document bersamanya: 1.015 baris, enam belas bagian plus lampiran. Masalah, tujuan yang diurutkan, non-goal, referensi yang benar-benar sudah dicek (versi library dari registry npm, dukungan browser dari caniuse, vendor avatar yang domainnya masih hidup), konsep, dunia, pengalaman pemain, arah seni, arsitektur, performa, keandalan, aksesibilitas, SEO, privasi, metrik, rencana pengiriman, risiko, dan pertanyaan terbuka untuk saya. PRD adalah prompt-nya; semua setelahnya adalah eksekusi.

Dua hal di brief itu bekerja lebih keras dari yang lain. Non-goal menjaga rilis pertama tetap empat pulau dengan tiga pulau lain terlihat di horizon, jadi scope tidak punya ruang untuk merayap. Dan setiap budget ditulis sebagai gate CI, bukan paragraf: import guard yang hanya mengizinkan three.js di-import di bawah folder game sendiri, sehingga sisa situs tidak bisa diam-diam menumbuhkan dependensi 3D; pemeriksaan paritas penempatan; smoke test Playwright dan soak test memori yang berjalan di setiap pull request; serta notice gate yang memberi tahu pengunjung ukuran unduhan, dihitung dari manifest build, sebelum satu byte engine pun terkirim.
Tulis brief agar mesin bisa memverifikasinya. Kalimat seperti 'game harus tetap ringan' tidak berbuat apa-apa; 'three hanya boleh di-import di bawah app/components/game dan lib/game, diperiksa saat prebuild' menjadi script yang dijalankan agent setelah setiap perubahan.
Enam fase, masing-masing dengan gate
Rencana pengiriman di PRD membagi pekerjaan menjadi enam fase, masing-masing diakhiri sesuatu yang bisa diperiksa, bukan perasaan. Estimasinya mengasumsikan agent mengerjakan fase kode dan tiga langkah tetap milik manusia: selfie untuk avatar, art pass, dan melihat hasilnya.
| Fase | Hasil | Gate |
|---|---|---|
| 0 — Fondasi | Rute tanpa chrome situs, dynamic import, probe kemampuan perangkat, notice gate, fallback Peta, import guard, smoke test CI | HTML hasil build setiap halaman lama tidak berubah; smoke test hijau |
| 1 — Vertical slice | Pelabuhan, avatar pengganti, controller kapsul di atas BVH, kamera, HUD, satu kartu exhibit nyata, deep link, state tersimpan | Seluruh loop bisa dimainkan |
| 2 — Avatar | Selfie Avaturn ke GLB, klip Mixamo, pembersihan di Blender, paling besar 3 MB | Empat dari lima orang yang mengenal saya bisa menyebut nama saya dari satu still |
| 3 — Bengkel + Teluk | Seni pulau, 14 exhibit, paritas penempatan, tekstur screenshot, merge, pergantian waktu | Budget hijau; visual review pertama |
| 4 — Mercusuar | Enam lampu, pendakian, senja, sinar mercusuar, kartu finale, share | Satu permainan penuh di bawah delapan menit, tanpa jalan buntu |
| 5 — Peluncuran | Kontrol sentuh, aksesibilitas, salinan bahasa Indonesia, still OG, SEO, matriks perangkat, analytics, dokumentasi | Setiap baris performa dan keandalan hijau |
Fase 0 dan 1, plus sisi kode dari fase 3 dan 4, selesai dalam satu sesi pada 12 September 2026 dengan dunia prosedural dan manekin sebagai pengganti avatar. Dua penyimpangan dari brief layak disebut, karena keduanya adalah agent yang membantah dengan bukti: ia menyingkirkan header dan footer dari rute game lewat pengecekan pathname, bukan pemisahan route group yang diminta PRD, karena 32 script dan dokumen menuliskan folder pages secara hardcode; dan ia mengunci React di 19.2.7 karena React Three Fiber 9.7 mendeklarasikan rentang versi yang tidak mencakup 19.3.
Dari kotak menjadi tempat
Yang tidak bisa diukur gate adalah hal pertama yang saya lihat pada 18 September: game-nya benar dan jelek. Pulau seperti chip poker, pohon kelapa lima kerucut di atas tongkat, setiap bangunan kotak dengan atap lempengan, dan saya adalah kapsul hitam. Maka pass kedua mendapat brief-nya sendiri, dan agent membangun ulang dunia tanpa mengunduh satu model pun: tanah pulau sebagai satu geometri organik yang dibaca renderer sekaligus collider, sehingga pantai yang Anda lihat adalah pantai yang Anda tabrak; pohon kelapa dengan batang melengkung berbuku dan pelepah yang menjuntai; rumput, semak, dan batu yang disebar generator berseed yang menghindari setiap jejak bangunan dan jalur; tekstur yang digambar di canvas saat runtime — papan kayu, atap rumbia, seng bergelombang; air yang tahu di mana setiap pantai berada, dengan perairan dangkal dan garis buih; serta tujuh bangunan yang dibangun ulang menjadi warung beratap rumbia, kios beratap limas, gudang ERP berdinding seng, gym berkaca, bay cuci mobil, tiang lampu, dan mercusuar bergaris.

Instancing adalah alasan biayanya tetap masuk akal: setiap benda statis di satu pulau — ratusan rumpun rumput, setiap pelepah, setiap batu — adalah satu instanced draw yang matriksnya ditulis sekali saat mount, bukan dekomposisi ulang per frame yang dilakukan helper library secara default. Soak test memori setelah pass ini memutar pulau tiga kali: heap 21,2 MB pada baseline, 21,9 MB setelah siklus, 19,1 MB setelah keluar dan kembali, dengan jumlah geometri dan tekstur kembali ke baseline. Test itu ada karena brief menuntutnya; kali ini ia tidak menangkap apa pun, dan justru itulah intinya.
Loop-nya: baca, bangun, screenshot, lihat, perbaiki
Setiap perubahan di pass kedua melewati loop yang sama. Agent membaca file yang akan diubah dan semua yang meng-import-nya, merencanakan, mengimplementasikan, menjalankan typechecker, lalu melakukan langkah yang membuat perbedaan: merender game yang sebenarnya secara headless dan melihat gambarnya. Chromium dalam mode headless bisa menjalankan WebGL2 lewat SwiftShader tanpa GPU, dan game ini sudah mengekspos debug bridge kecil di development untuk soak test-nya — teleport, pergi ke satu pulau, membaca counter renderer. Dua puluh baris Playwright mengubahnya menjadi mata.
// scratch/shoot.mjs — the agent's eyes. Headless Chromium renders WebGL2
// through SwiftShader, so no GPU is needed; the debug bridge the game exposes
// in development (window.__archipelago) moves the player without walking.
const browser = await chromium.launch({
headless: true,
args: ["--use-gl=angle", "--use-angle=swiftshader", "--enable-unsafe-swiftshader"],
});
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
await page.goto("http://localhost:3000/en/play/", { waitUntil: "networkidle" });
await page.getByRole("button", { name: /set sail/i }).click();
await page.locator("canvas").first().waitFor({ state: "visible" });
for (const island of ["workshop", "cove", "lighthouse"]) {
await page.evaluate((id) => window.__archipelago.goto(id), island);
// The follow camera is critically damped: give it two seconds to settle,
// or every still is a smear of the previous position.
await page.waitForTimeout(2000);
await page.screenshot({ path: "shots/" + island + ".png" });
}
// stats() returns draw calls, triangles, geometry and texture counts —
// the numbers the soak test watches, read back after every change.
console.log(await page.evaluate(() => window.__archipelago.stats()));Di bawah software renderer, game berjalan sekitar tiga frame per detik, jadi script menunggu kamera yang teredam berhenti bergoyang sebelum setiap still, dan agent membaca kembali setiap gambar sebelum memutuskan perubahan berikutnya. Begitulah ia menyadari pita pucat di horizon (warna fog tidak cocok dengan langit), rumpun rumput yang terlihat seperti paku, dan buih yang menggumpal di perairan dangkal — tidak satu pun akan dilaporkan oleh gate mana pun.

Bug yang hanya terlihat oleh test
Gerakannya terasa salah dan saya tidak bisa menjelaskan mengapa, maka alih-alih mendeskripsikannya saya meminta reproduksi. Agent menulis script Playwright yang menahan tombol selama enam detik dan mencuplik posisi pemain serta yaw kamera lewat bridge. Run baseline-nya memberi angka pada tiga perasaan sekaligus.
Berjalan mundur memutar kamera 7,4 radian dalam enam detik dan pemain tidak pernah kembali, karena kamera otomatis menyelaraskan diri di belakang avatar saat mundur: tekan mundur, avatar berbalik menghadap kamera, kamera berayun, arah depan terbalik, dan Anda berjalan melingkar. Jalan lurus ke Pulau Bengkel macet di tepi pelabuhan selama 55 detik karena akselerasi di-clamp per sumbu, sehingga arah melenceng delapan belas derajat di setengah detik pertama dan jalurnya melengkung keluar dari jalan pasir ke air. Dan bibir pulau serta tepi jalan pasir berperilaku seperti tembok, karena kapsul tabrakan dimulai dari kaki.
// Wrong: each axis is clamped on its own, so a diagonal target ramps the
// shorter axis to full speed first. For half a second the velocity points
// along the diagonal, the avatar turns to face it, the camera aligns behind
// the avatar, and "forward" has quietly rotated. Over 50 m the path curves
// off a 4 m causeway onto the beach, where the water blocks both axes.
velocity.x += clamp(wanted.x - velocity.x, -maxChange, maxChange);
velocity.z += clamp(wanted.z - velocity.z, -maxChange, maxChange);
// Right: clamp the change *vector*, so the velocity ramps along the wanted
// direction from the first step and the heading never drifts.
let dx = wanted.x - velocity.x;
let dz = wanted.z - velocity.z;
const change = Math.hypot(dx, dz);
if (change > maxChange) {
dx *= maxChange / change;
dz *= maxChange / change;
}
velocity.x += dx;
velocity.z += dz;Perbaikannya kecil setelah angkanya ada: selaraskan kamera hanya saat input mendorong ke depan, clamp vektor perubahannya, mulai kapsul tabrakan 0,4 meter di atas kaki sehingga apa pun yang lebih rendah adalah anak tangga yang dinaiki oleh ground snap, dan lebarkan jalan pasir dari empat meter menjadi lima. Sore yang sama, run di ponsel menemukan dua bug lagi yang tidak akan ditemukan test desktop mana pun: panel logbook duduk tepat di tempat jempol kiri mendarat dan menelan setiap sentuhan, sehingga stik tidak pernah muncul; dan menu jeda lebih tinggi dari ponsel landscape dan tidak bisa di-scroll, sehingga tombol Resume berada di luar layar tanpa cara menjangkaunya. Tiga script kini berjaga:
- move-test: jalan mundur menelusuri jalur semula tanpa putaran kamera, meluncur di sepanjang pantai, mundur menjauh, melintasi jalan pasir sampai berakhir di pulau seberang, dan lompatan yang memuncak sekitar 0,9 meter lalu mendarat.
- touch-test: di Pixel 7 emulasi, stik dari sudut kiri bawah, berjalan, seret untuk melihat sekeliling, serta tombol lompat dan interaksi, semuanya lewat touch event sungguhan.
- flow-test: seluruh game dari notice sampai finale dan kembali — menu, logbook, semua 22 exhibit dengan kartunya, setiap merge dan pergantian waktu, pintu, pendakian, reset, dan deep link — di desktop dengan keyboard dan di ponsel dengan ketukan.

Bloom, satu color grade, dan yang tidak bisa dilakukan agent
Langkah grafis terakhir adalah bloom di tier tinggi agar lampu, lentera, dan matahari bersinar. Alih-alih hanya memoles, ia membuka bug sungguhan: dengan composer post-processing di dalam rantai, setiap still kembali pucat dan pudar. Ambang bloom adalah tersangka pertama, dan tersangka yang salah. Shader langit dan air tidak pernah menyertakan transformasi output renderer, sehingga tanpa composer keduanya tampil terlalu gelap dan jenuh — dan palet warnanya disetel terhadap tampilan itu — sementara lewat composer keduanya dikonversi dengan benar dan terlihat pucat. Dua tier menampilkan dua game yang berbeda. Perbaikannya: rutekan kedua shader lewat tone mapping dan konversi ruang warna yang sama dengan setiap material lain, lalu nyatakan ulang paletnya sekali; tier hemat baterai dan tier desktop kini merender gambar yang sama.
Tiga hal tetap milik manusia, persis seperti kata brief. Agent tidak bisa mengambil selfie yang dibutuhkan avatar asli, sehingga pengelana berransel itu adalah pengganti. Ia tidak bisa mengerjakan art pass di Blender yang akan menggantikan pulau prosedural dengan pulau buatan tangan. Dan ia tidak bisa berdiri di ruangan dengan ponsel sungguhan; setiap hasil mobile di sini berasal dari Pixel 7 emulasi. Di luar itu, satu pelajaran dari proyek sebelumnya tetap berlaku: gate memverifikasi budget dan tidak bisa melihat kebosanan, jadi visual review adalah langkah terjadwal dengan tanggal, bukan sesuatu yang saya harapkan terjadi sendiri.
- Reproduksi sebelum memperbaiki: menahan tombol enam detik dengan posisi yang dicuplik mengubah tiga perasaan samar menjadi tiga angka, dan setiap perbaikan kini adalah pemeriksaan yang berjalan lagi.
- Buat agent melihat: satu screenshot headless setelah setiap perubahan menangkap lebih banyak daripada typechecker, linter, dan semua gate digabungkan.
- Membantah dengan bukti: dua penyimpangan dari brief keduanya benar, dan keduanya datang dengan angka yang membenarkannya.

Game-nya sudah tayang di matthewswong.com/play, dalam bahasa Inggris dan Indonesia, di desktop, layar sentuh, dan gamepad. Setiap exhibit di dalamnya adalah proyek, produk, atau sertifikat nyata dari situs ini, dan kotak surat di pelabuhan adalah halaman kontak.
Aturan yang saya bawa pulang: tulis brief agar mesin bisa memverifikasinya, buat mesin melihat hasil kerjanya sendiri, dan biarkan tiga langkah yang hanya bisa dikerjakan manusia tetap dikerjakan manusia. Agent yang diberi satu paragraf akan membangun satu paragraf. Diberi PRD dengan gate, loop screenshot, dan ruang untuk membantah, ia membangun dunia yang tidak akan saya selesaikan sendirian — dan memberi tahu saya, dengan angka, di mana ia keliru.
Sumber dan bacaan lanjutan
- Dokumentasi three.js — renderer, material, dan pass post-processing yang dipakai di sini
- Dokumentasi React Three Fiber — renderer React untuk three.js yang menjadi dasar game ini
- three-mesh-bvh di GitHub — bounding-volume hierarchy di balik raycast tabrakan dan kamera
- Dokumentasi screenshot Playwright — penangkapan headless yang dipakai agent sebagai matanya
- Procedural generation di Wikipedia — pendekatan yang menggantikan model unduhan


