2026-02-20 08:16:43 -05:00
# Item Schema
## World Item (server-authoritative)
```json
{
"id": "string",
2026-02-22 23:42:17 -05:00
"type": "radio_station | dice | wheel | clock | widget | piano",
2026-02-20 08:16:43 -05:00
"title": "string",
"x": 0,
"y": 0,
"createdBy": "user-id",
"createdAt": 1735689600000,
"updatedAt": 1735689600000,
"version": 1,
"capabilities": ["editable", "carryable", "deletable", "usable"],
2026-02-21 16:13:48 -05:00
"useSound": "sounds/roll.ogg",
"emitSound": "sounds/clock.ogg",
2026-02-20 08:16:43 -05:00
"params": {},
"carrierId": null
}
```
2026-02-21 16:13:48 -05:00
- `useSound` : optional client-played one-shot sound when item `use` succeeds; global item field and not user-editable in V1.
- `emitSound` : optional continuously-looping spatial sound emitted from the item on the grid; global item field and not user-editable in V1.
- `capabilities` , `useSound` , and `emitSound` are derived from global item-type definitions at runtime (not stored per-instance in persisted state).
2026-02-22 23:42:17 -05:00
- `useCooldownMs` : global per item type (`radio_station=1000` , `dice=1000` , `wheel=4000` , `clock=1000` , `widget=1000` , `piano=1000` ), not per-instance editable.
- `emitRange` : global spatial range default per item type (`radio_station=20` , `dice=15` , `wheel=15` , `clock=10` , `widget=15` , `piano=15` ).
2026-02-21 20:31:34 -05:00
- `radio_station` can override this per instance via `params.emitRange` (`5..20` ).
2026-02-21 22:20:15 -05:00
- `directional` : global directional attenuation flag per item type (`radio_station=true` , others `false` ); `widget` can override per instance via `params.directional` .
2026-02-20 08:16:43 -05:00
## Persisted Item State (`server/runtime/items.json`)
```json
{
"id": "string",
2026-02-22 23:42:17 -05:00
"type": "radio_station | dice | wheel | clock | widget | piano",
2026-02-20 08:16:43 -05:00
"title": "string",
"x": 0,
"y": 0,
"createdBy": "user-id",
"createdAt": 1735689600000,
"updatedAt": 1735689600000,
"version": 1,
"params": {},
"carrierId": null
}
```
- Persisted state stores only instance data.
- Global/type-level properties are loaded from server registry in `server/app/item_catalog.py` .
2026-02-24 18:56:42 -05:00
- Per-type use/update validation and message behavior are implemented in per-item modules under `server/app/items/types/*/definition.py` , `validator.py` , and `actions.py` , discovered via plugins in `server/app/items/types/*/plugin.py` .
2026-02-24 02:49:17 -05:00
- Client-side add/edit metadata is consumed from `welcome.uiDefinitions` via `client/src/items/itemRegistry.ts` (no local fallback definitions).
2026-02-21 18:57:02 -05:00
- End-to-end add-item template: `docs/item-type-template.md` .
2026-02-20 08:16:43 -05:00
## Type Params
### `radio_station`
```json
{
"streamUrl": "",
"enabled": true,
2026-02-21 22:55:20 -05:00
"mediaChannel": "stereo",
2026-02-21 22:38:48 -05:00
"mediaVolume": 50,
2026-02-21 22:55:20 -05:00
"mediaEffect": "off",
"mediaEffectValue": 50,
2026-02-21 20:31:34 -05:00
"facing": 0,
"emitRange": 20
2026-02-20 08:16:43 -05:00
}
```
- `streamUrl` : string, empty allowed until configured.
- `enabled` : boolean on/off flag.
- UI behavior: in property menu, `Enter` toggles on/off directly.
2026-02-21 22:38:48 -05:00
- `mediaVolume` : integer, range `0-100` , default `50` .
2026-02-21 22:55:20 -05:00
- `mediaChannel` : one of `stereo | mono | left | right` , default `stereo` .
- `mediaEffect` : one of `reverb | echo | flanger | high_pass | low_pass | off` , default `off` .
- `mediaEffectValue` : number, range `0-100` , precision `0.1` .
2026-02-24 02:49:17 -05:00
- `facing` : number, range `0-360` , step `1` (used when `directional=true` ).
- UI visibility: `facing` is shown only when `directional=true` (`visibleWhen` metadata).
2026-02-21 20:31:34 -05:00
- `emitRange` : integer, range `5-20` , default `20` .
2026-02-20 08:16:43 -05:00
### `dice`
```json
{
"sides": 6,
"number": 2
}
```
- `sides` : integer, range `1-100` .
- `number` : integer, range `1-100` .
2026-02-21 03:43:27 -05:00
### `wheel`
```json
{
"spaces": "yes, no"
}
```
- `spaces` : comma-delimited string of values.
- Server validation:
- must include at least 1 value
- max 100 values
- each value max 80 chars
2026-02-21 16:01:40 -05:00
### `clock`
```json
{
"timeZone": "America/Detroit",
"use24Hour": false
}
```
2026-02-21 16:04:55 -05:00
- `timeZone` : one representative IANA zone per world UTC offset. Includes:
`America/Anchorage` , `America/Argentina/Buenos_Aires` , `America/Chicago` , `America/Detroit` ,
`America/Halifax` , `America/Indiana/Indianapolis` , `America/Kentucky/Louisville` ,
`America/Los_Angeles` , `America/St_Johns` , `Asia/Bangkok` , `Asia/Dhaka` , `Asia/Dubai` ,
`Asia/Hong_Kong` , `Asia/Kabul` , `Asia/Karachi` , `Asia/Kathmandu` , `Asia/Kolkata` ,
`Asia/Seoul` , `Asia/Singapore` , `Asia/Tehran` , `Asia/Tokyo` , `Asia/Yangon` ,
`Atlantic/Azores` , `Atlantic/South_Georgia` , `Australia/Brisbane` , `Australia/Darwin` ,
`Australia/Eucla` , `Australia/Lord_Howe` , `Europe/Berlin` , `Europe/Helsinki` ,
`Europe/London` , `Europe/Moscow` , `Pacific/Apia` , `Pacific/Auckland` , `Pacific/Chatham` ,
`Pacific/Honolulu` , `Pacific/Kiritimati` , `Pacific/Noumea` , `Pacific/Pago_Pago` , `UTC` .
2026-02-21 16:01:40 -05:00
- `use24Hour` : boolean (or `on/off` in updates), default `false` .
2026-02-21 16:13:48 -05:00
- Global defaults: `useSound=none` , `emitSound=sounds/clock.ogg` .
2026-02-21 16:01:40 -05:00
2026-02-21 22:20:15 -05:00
### `widget`
```json
{
"enabled": true,
"directional": false,
"facing": 0,
"emitRange": 15,
2026-02-21 22:38:48 -05:00
"emitVolume": 100,
2026-02-21 23:10:17 -05:00
"emitSoundSpeed": 50,
2026-02-21 23:17:18 -05:00
"emitSoundTempo": 50,
2026-02-21 22:55:20 -05:00
"emitEffect": "off",
"emitEffectValue": 50,
2026-02-21 22:20:15 -05:00
"useSound": "",
"emitSound": ""
}
```
- `enabled` : boolean (or `on/off` in updates), default `true` .
- `directional` : boolean (or `on/off` in updates), default `false` .
2026-02-24 02:49:17 -05:00
- `facing` : number, range `0-360` , step `1` .
- UI visibility: `facing` is shown only when `directional=true` (`visibleWhen` metadata).
2026-02-21 22:20:15 -05:00
- `emitRange` : integer, range `1-20` , default `15` .
2026-02-21 22:38:48 -05:00
- `emitVolume` : integer, range `0-100` , default `100` .
2026-02-21 23:17:18 -05:00
- `emitSoundSpeed` : integer, range `0-100` , default `50` ; controls emitted sound speed/pitch (`0=0.5x` , `50=1.0x` , `100=2.0x` ).
- `emitSoundTempo` : integer, range `0-100` , default `50` ; controls emitted sound tempo (`0=0.5x` , `50=1.0x` , `100=2.0x` ).
2026-02-21 22:55:20 -05:00
- `emitEffect` : one of `reverb | echo | flanger | high_pass | low_pass | off` , default `off` .
- `emitEffectValue` : number, range `0-100` , precision `0.1` , default `50` .
2026-02-21 22:20:15 -05:00
- `useSound` : empty, filename (assumed under `sounds/` ), or full URL.
- `emitSound` : empty, filename (assumed under `sounds/` ), or full URL.
2026-02-22 23:42:17 -05:00
### `piano`
```json
{
"instrument": "piano",
2026-02-23 00:22:36 -05:00
"voiceMode": "poly",
"octave": 0,
2026-02-22 23:42:17 -05:00
"attack": 15,
"decay": 45,
2026-02-23 00:05:01 -05:00
"release": 35,
"brightness": 55,
2026-02-22 23:42:17 -05:00
"emitRange": 15
}
```
- `instrument` : one of
2026-02-23 00:22:36 -05:00
`piano | electric_piano | guitar | organ | bass | violin | synth_lead | brass | nintendo | drum_kit` .
- `voiceMode` : one of `poly | mono` .
- `octave` : integer, range `-2..2` (default `0` ; bass defaults to `-1` ).
- Selecting a new instrument resets `voiceMode` /`octave` /`attack` /`decay` /`release` /`brightness` to that instrument's defaults.
2026-02-22 23:42:17 -05:00
- `attack` : integer, range `0-100` , default `15` .
- `decay` : integer, range `0-100` , default `45` .
2026-02-23 00:05:01 -05:00
- `release` : integer, range `0-100` , default `35` .
- `brightness` : integer, range `0-100` , default `55` .
2026-02-22 23:42:17 -05:00
- `emitRange` : integer, range `5-20` , default `15` .
2026-02-23 01:43:02 -05:00
- `songId` : server-managed song reference used for piano demo/playback content.
2026-02-23 01:49:27 -05:00
- Recorded/demo song payload is stored in server song registry (`runtime/piano_songs.json` ) using compact format:
- `meta` : shared synth parameters
- `keys` : keyId dictionary
- `states` : parameter-state dictionary (for mid-song instrument/param changes)
- `events` : `[t, keyIndex, midi, on, stateIndex]`
2026-02-22 23:42:17 -05:00
2026-02-20 08:16:43 -05:00
## Packet Shapes
- `item_upsert` :
```json
{
"type": "item_upsert",
"item": { "..." : "World Item" }
}
```
- `item_remove` :
```json
{
"type": "item_remove",
"itemId": "item-id"
}
```
- `item_action_result` :
```json
{
"type": "item_action_result",
"ok": true,
"action": "add | pickup | drop | delete | use | update",
"message": "human-readable status",
"itemId": "optional-item-id"
}
```
- `item_use_sound` :
```json
{
"type": "item_use_sound",
"itemId": "item-id",
"sound": "sounds/roll.ogg",
"x": 12,
"y": 8
}
```
2026-02-22 23:42:17 -05:00
- `item_piano_note` :
```json
{
"type": "item_piano_note",
"itemId": "item-id",
"senderId": "user-id",
"keyId": "KeyA",
"midi": 60,
"on": true,
"instrument": "piano",
2026-02-23 00:22:36 -05:00
"voiceMode": "poly",
"octave": 0,
2026-02-22 23:42:17 -05:00
"attack": 15,
"decay": 45,
2026-02-23 00:22:36 -05:00
"release": 35,
"brightness": 55,
2026-02-22 23:42:17 -05:00
"x": 12,
"y": 8,
"emitRange": 15
}
```