# Item Schema ## World Item (server-authoritative) ```json { "id": "string", "type": "radio_station | dice | wheel | clock | widget | piano", "title": "string", "x": 0, "y": 0, "createdBy": "user-id", "createdByName": "username", "updatedBy": "user-id", "updatedByName": "username", "createdAt": 1735689600000, "updatedAt": 1735689600000, "version": 1, "capabilities": ["editable", "carryable", "deletable", "usable"], "useSound": "sounds/roll.ogg", "emitSound": "sounds/clock.ogg", "params": {}, "carrierId": null } ``` - `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). - `createdBy` / `updatedBy` are stable user IDs. - `createdByName` / `updatedByName` are display-name snapshots used for inspect/readout text. - `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=10`, `dice=15`, `wheel=15`, `clock=10`, `widget=15`, `piano=15`). - `radio_station` can override this per instance via `params.emitRange` (`5..20`). - `directional`: global directional attenuation flag per item type (`radio_station=true`, others `false`); `widget` can override per instance via `params.directional`. ## Persisted Item State (`server/runtime/items.json`) ```json { "id": "string", "type": "radio_station | dice | wheel | clock | widget | piano", "title": "string", "x": 0, "y": 0, "createdBy": "user-id", "createdByName": "username", "updatedBy": "user-id", "updatedByName": "username", "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`. - 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`. - Client-side add/edit metadata is consumed from `welcome.uiDefinitions` via `client/src/items/itemRegistry.ts` (no local fallback definitions). - End-to-end add-item template: `docs/item-type-template.md`. ## Type Params ### `radio_station` ```json { "streamUrl": "", "enabled": true, "mediaChannel": "stereo", "mediaVolume": 50, "mediaEffect": "off", "mediaEffectValue": 50, "stationName": "", "nowPlaying": "", "facing": 0, "emitRange": 10 } ``` - `streamUrl`: string, empty allowed until configured. - `enabled`: boolean on/off flag. - UI behavior: in property menu, `Enter` toggles on/off directly. - `mediaVolume`: integer, range `0-100`, default `50`. - `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`. - UI visibility: `mediaEffectValue` is shown only when `mediaEffect != off` (`visibleWhen: {"mediaEffect": "!off"}`). - `stationName`: server-managed station label derived from ICY metadata when available. - `nowPlaying`: server-managed stream title derived from ICY metadata when available. - `facing`: number, range `0-360`, step `1` (used when `directional=true`). - UI visibility: `facing` is shown only when `directional=true` (`visibleWhen` metadata). - `emitRange`: integer, range `5-20`, default `10`. ### `dice` ```json { "sides": 6, "number": 2 } ``` - `sides`: integer, range `1-100`. - `number`: integer, range `1-100`. ### `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 ### `clock` ```json { "timeZone": "America/Detroit", "use24Hour": false, "topOfHourAnnounce": true, "alarmEnabled": false, "alarmTime": "12:00 AM" } ``` - `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`. - `use24Hour`: boolean (or `on/off` in updates), default `false`. - `topOfHourAnnounce`: boolean (or `on/off` in updates), default `true`. - `alarmEnabled`: boolean (or `on/off` in updates), default `false`. - `alarmTime`: default `12:00 AM`; accepts `HH:MM` (24-hour mode) or `H:MM AM/PM` (12-hour mode). - UI visibility: `alarmTime` is shown only when `alarmEnabled=true` (`visibleWhen` metadata). - Global defaults: `useSound=none`, `emitSound=sounds/clock.ogg`. - Clock speech announcement audio is emitted via `item_clock_announce` packets using `/sounds/clock/el640/*.ogg`. ### `widget` ```json { "enabled": true, "directional": false, "facing": 0, "emitRange": 15, "emitVolume": 100, "emitSoundSpeed": 50, "emitSoundTempo": 50, "emitInitialDelay": 0, "emitLoopDelay": 0, "emitEffect": "off", "emitEffectValue": 50, "useSound": "", "emitSound": "" } ``` - `enabled`: boolean (or `on/off` in updates), default `true`. - `directional`: boolean (or `on/off` in updates), default `false`. - `facing`: number, range `0-360`, step `1`. - UI visibility: `facing` is shown only when `directional=true` (`visibleWhen` metadata). - `emitRange`: integer, range `1-20`, default `15`. - `emitVolume`: integer, range `0-100`, default `100`. - `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`). - `emitInitialDelay`: number, range `0-300`, precision `0.1`, default `0`; delay in seconds before emitted audio starts after enable. - `emitLoopDelay`: number, range `0-300`, precision `0.1`, default `0`; delay in seconds between each emitted playback. - `emitEffect`: one of `reverb | echo | flanger | high_pass | low_pass | off`, default `off`. - `emitEffectValue`: number, range `0-100`, precision `0.1`, default `50`. - `useSound`: empty, filename (assumed under `sounds/`), or full URL. - `emitSound`: empty, filename (assumed under `sounds/`), or full URL. ### `piano` ```json { "instrument": "piano", "voiceMode": "poly", "octave": 0, "attack": 15, "decay": 45, "release": 35, "brightness": 55, "emitRange": 15 } ``` - `instrument`: one of `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. - `attack`: integer, range `0-100`, default `15`. - `decay`: integer, range `0-100`, default `45`. - `release`: integer, range `0-100`, default `35`. - `brightness`: integer, range `0-100`, default `55`. - `emitRange`: integer, range `5-20`, default `15`. - `songId`: server-managed song reference used for piano demo/playback content. - 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]` ## 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 } ``` - `item_clock_announce`: ```json { "type": "item_clock_announce", "itemId": "item-id", "sounds": ["/sounds/clock/el640/its.ogg", "/sounds/clock/el640/2.ogg", "/sounds/clock/el640/PM.ogg"], "x": 12, "y": 8 } ``` - `item_piano_note`: ```json { "type": "item_piano_note", "itemId": "item-id", "senderId": "user-id", "keyId": "KeyA", "midi": 60, "on": true, "instrument": "piano", "voiceMode": "poly", "octave": 0, "attack": 15, "decay": 45, "release": 35, "brightness": 55, "x": 12, "y": 8, "emitRange": 15 } ```