# Controller

Game tidak mengirim kode ke HP. Game mengirim **layout**: daftar widget bawaan yang dirender controller Ralyoo. HP tidak pernah mengunduh kode game.

```ts
ctx.setController('all', {
  layout: 'pad',
  orientation: 'landscape',
  widgets: [
    { type: 'text', id: 'pesan', value: 'Dorong lawanmu!' },
    { type: 'joystick', id: 'stick' },
    { type: 'button', id: 'A', label: 'Dorong' },
  ],
});
```

- `ctx.setController('all', layout)` mengganti layout semua HP.
- `ctx.setController(2, layout)` hanya HP pemain slot 2. Layout per pemain bertahan sampai `'all'` berikutnya.
- Boleh dipanggil kapan saja dan sesering perlu. HP yang tersambung ulang menerima layout terakhirnya.
- Layout yang tidak valid melempar error di game dan tidak dikirim.

## Layout

| `layout` | Susunan                                                                                                                                |
| -------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `pad`    | Seperti gamepad. D-pad atau joystick di jempol kiri, tombol di jempol kanan, teks/kartu/voting di atas (tegak) atau di tengah (miring) |
| `stack`  | Satu kolom di tengah. Tombol besar berdampingan saat HP miring. Cocok untuk kuis dan satu tombol besar                                 |

## Widget

| Widget   | Bentuk                                                      | Yang diterima game                                    |
| -------- | ----------------------------------------------------------- | ----------------------------------------------------- |
| Teks     | `{ type: 'text', value, id?, size?: 'm' \| 'l' }`           | –                                                     |
| Tombol   | `{ type: 'button', id, label?, tone? }`                     | `press` dan `release`                                 |
| D-pad    | `{ type: 'dpad' }`                                          | `press`/`release` untuk `up`, `down`, `left`, `right` |
| Joystick | `{ type: 'joystick', id: 'stick' \| 'stick2' }`             | Dibaca lewat `ctx.input()`                            |
| Kartu    | `{ type: 'cards', id, pages: [{ title?, body }] }`          | `page` dengan nomor halaman                           |
| Voting   | `{ type: 'vote', id, question?, options: [{ id, label }] }` | `vote` dengan `id` pilihan                            |

`tone` tombol: `pearl`, `led`, `plasma`, `ultraviolet`, `aurora`, `champagne`, `arcade`.

Batas:

| Hal | Batas |
| --- | --- |
| Widget per layout | 12 |
| Teks (`text.value`, `vote.question`) | 280 karakter |
| Label tombol | 24 karakter |
| Halaman kartu | 20 |
| Isi satu halaman kartu | 600 karakter |
| Pilihan voting | 8 |
| `id` widget | 32 karakter, huruf/angka/`._-` |

Layout yang melewati batas ditolak seluruhnya.

## Dua jalur input

**Jalur cepat**: tombol ber-`id` `A`, `B`, `X`, `Y`, d-pad, dan joystick. Keadaannya dikirim sebagai satu paket kecil, paling sering 30 kali per detik.

```ts
// Kejadian: tiap tekan dan lepas.
onInput(ctx, player, input) {
  if (input.widget === 'A' && input.type === 'press') lompat(player);
}

// Keadaan: baca kapan saja, biasanya di onTick.
import { isDown } from '@ralyoo/sdk';
const pad = ctx.input(player);
if (isDown(pad, 'A')) gas(player);
gerak(player, pad.lx, pad.ly); // −1..1; ly positif = bawah
```

`stick` mengisi `lx`/`ly`, `stick2` mengisi `rx`/`ry`. Joystick **hanya** lewat `ctx.input()`.

**Jalur andal**: tombol ber-`id` lain, kartu, dan voting. Tiap kejadian dijamin sampai dan berurutan.

```ts
onInput(ctx, player, input) {
  if (input.widget === 'potong' && input.type === 'press') potongKabel(player);
  if (input.widget === 'tersangka' && input.type === 'vote') pilih(player, input.value);
  if (input.widget === 'manual' && input.type === 'page') halaman(player, input.value);
}
```

Pakai jalur cepat untuk aksi yang butuh respons (gerak, lompat, tembak) dan jalur andal untuk pilihan yang tidak boleh hilang.

Selama menu host terbuka, input tidak sampai ke game dan semua tombol dianggap dilepas.

## Orientasi

| `orientation`  | Di HP                                                                                                |
| -------------- | ---------------------------------------------------------------------------------------------------- |
| `any` (bawaan) | Ikut cara HP dipegang                                                                                |
| `landscape`    | HP yang dipegang tegak diajak memutar. Android Chrome mengunci layar miring setelah sentuhan pertama |
| `portrait`     | Kebalikannya                                                                                         |

- Input tetap jalan selama ajakan memutar tampil.
- Kunci layar hanya upaya terbaik: iPhone Safari tidak punya API-nya, jadi hanya ajakan.
- Berlaku per layout. Kirim layout lain dengan nilai berbeda kapan saja.

Di Ralystudioo, pilihan **Phones** memutar HP virtual untuk mencoba keduanya.

## Informasi rahasia

Karena layar host dilihat semua orang, rahasia dikirim ke HP pemainnya saja:

```ts
for (const p of ctx.players) {
  ctx.setController(p.id, {
    layout: 'stack',
    widgets: [{ type: 'text', value: p.id === mataMata ? 'Kamu mata-mata.' : `Katanya: ${kata}` }],
  });
}
```

Teks di layout bukan tempat menyimpan data besar: batasnya ada di tabel di atas.

## Yang selalu ada di HP

Controller selalu menampilkan slot dan nama pemain, tombol layar penuh, dan ☰ untuk membuka menu host. Bagian itu milik platform dan tidak bisa diubah game.
