# Aset, pustaka, dan budget

## Folder `assets/`

Semua isi `assets/` disalin apa adanya ke bundel. Baca dengan `ctx.assets.url()`:

```ts
const gambar = new Image();
gambar.src = ctx.assets.url('sprites/yoo.webp');

const data = await fetch(ctx.assets.url('level/1.json')).then((r) => r.json());
```

File yang di-`import` dari kode (CSS dengan `?inline`, JSON) ikut masuk ke `main.js`; itu cocok untuk yang kecil. Gambar, audio, dan model sebaiknya di `assets/`.

Aturan path: huruf, angka, `_`, `-`, `.`; tanpa spasi; tanpa folder berawalan titik. Tipe file yang boleh ada di [Referensi bundel](referensi-bundel.md).

## Ikon dan cover

Di `assets/meta/`, dirujuk dari `media` di manifest:

|         | Bentuk                             | Catatan                                                                                                |
| ------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `icon`  | Persegi, SVG (disarankan) atau PNG | Dipakai sebagai _mask_: satu warna di atas latar transparan. Platform mewarnainya dengan `media.color` |
| `cover` | 16:9, minimal 1280×720             | Tampil utuh di lobi di samping judul                                                                   |
| `color` | `#rrggbb`                          | Warna game di katalog. Harus terbaca di latar gelap host (kontras minimal 3:1)                         |

## Pustaka bersama

PixiJS dan Rapier **tidak ikut di-bundle**. Platform menyajikannya sekali untuk semua game, dan build game membiarkannya sebagai impor:

```ts
import 'pixi.js/unsafe-eval'; // wajib, sebelum membuat Application
import { Application } from 'pixi.js';
import RAPIER from '@dimforge/rapier2d-compat';
```

- Pasang sebagai devDependency dengan versi yang **sama persis** dengan platform (`pixi.js@8.22.0`, `@dimforge/rapier2d-compat@0.21.0`), supaya tipenya cocok dengan yang berjalan.
- `pixi.js/unsafe-eval` wajib: sandbox melarang `eval`, dan tanpa patch itu Pixi menolak jalan.
- Rapier: panggil `await RAPIER.init()` di `preload`.
- Pustaka lain (tween, util) boleh, tetapi ikut masuk ke `main.js` dan menambah ukurannya.

Daftar lengkap impor yang tersedia ada di [Referensi bundel](referensi-bundel.md). `lint` gagal kalau `main.js` mengimpor paket yang tidak disediakan platform dan tidak ikut di-bundle.

## Ukuran

Ada **batas keras** (bundel ditolak) dan **budget muat** (peringatan). Angkanya di [Referensi bundel](referensi-bundel.md). Yang paling berpengaruh ke waktu tunggu pemain:

- **Gambar**: WebP atau AVIF, ukuran sesuai tampilnya. Sprite sheet untuk yang banyak dan kecil.
- **Audio**: Ogg/Opus. Musik biasanya file terbesar; pertimbangkan bitrate dan durasinya.
- **Urutan muat**: muat yang dibutuhkan layar pertama di `preload`, sisanya saat dibutuhkan.

Panel **Bundle** di Ralystudioo dan keluaran `ralystudioo lint` menunjukkan ukuran total, JavaScript, dan file terbesar.

## fps

Sasaran: 60 fps di laptop dengan GPU terintegrasi.

- Baris `GAME` di HUD Ralystudioo menunjukkan fps, waktu frame rata-rata/p95/maks, dan jumlah frame di atas 50 ms, diukur di dalam game.
- Batasi resolusi render. Di atas 1,5× piksel perangkat, GPU terintegrasi membayar untuk piksel yang tidak terlihat dari sofa.
- Untuk game berat, `ResolutionGovernor` menurunkan resolusi saat fps jatuh dan menaikkannya lagi dengan hati-hati:

```ts
import { ResolutionGovernor } from '@ralyoo/sdk/governor';

const governor = new ResolutionGovernor({ max: Math.min(devicePixelRatio, 1.5) });
// tiap frame:
const berikut = governor.frame(frameMs);
if (berikut !== null) renderer.setPixelRatio(berikut);
```

Performa game adalah tanggung jawab pembuat game; latensi input dan koneksi adalah tanggung jawab Ralyoo.
