diff --git a/data-structure-block-coding.md b/data-structure-block-coding.md new file mode 100644 index 0000000..cc1c279 --- /dev/null +++ b/data-structure-block-coding.md @@ -0,0 +1,159 @@ +# Block Coding — Database Save Format + +This document describes the JSON structure stored in the Supabase `rooms` table under the `graphical_coding` column for the block coding system (replacing the old node-graph format). + +--- + +## Top-level shape + +```json +{ + "scripts": [ ...BlockScript ] +} +``` + +The entire block coding state is one JSON blob — a flat array of scripts. Each script is self-contained (trigger + effects) with no cross-script references. + +--- + +## `BlockScript` + +```json +{ + "id": "bl-1-1234567890", + "trigger": { ...TriggerBlock }, + "effects": [ ...EffectBlock ] +} +``` + +| Field | Type | Description | +|---|---|---| +| `id` | `string` | Unique script identifier, format `bl--` | +| `trigger` | `TriggerBlock` | The single trigger block at the top of the script | +| `effects` | `EffectBlock[]` | Ordered list of effect blocks stacked below the trigger | + +--- + +## `TriggerBlock` + +```json +{ + "id": "bl-2-1234567890", + "event": "clicked", + "sourceItemId": 3, + "radius": 5, + "exitRadius": 5, + "cooldown": 500 +} +``` + +| Field | Type | Description | +|---|---|---| +| `id` | `string` | Unique block identifier | +| `event` | `"clicked" \| "proximity_enter" \| "proximity_exit"` | The interaction event to watch for | +| `sourceItemId` | `number \| null` | Scene item ID to watch (`null` = no item selected yet) | +| `radius` | `number` | Proximity enter radius in metres (only used when `event` is `"proximity_enter"`) | +| `exitRadius` | `number` | Proximity exit radius in metres (only used when `event` is `"proximity_exit"`) | +| `cooldown` | `number` | Minimum milliseconds between consecutive firings | + +--- + +## `EffectBlock` + +All fields are always present regardless of `effectProp`. Fields that are irrelevant to the chosen property are stored but ignored at runtime. + +```json +{ + "id": "bl-3-1234567890", + "effectProp": "color", + "targetObjectType": "item", + "targetItemId": 7, + "targetColor": "#ff3366", + "positionAxis": "x", + "targetPositionValue": 0, + "rotationAxis": "x", + "targetRotationValue": 0, + "targetScale": 1, + "targetVisibility": true, + "toggle": true +} +``` + +| Field | Type | Description | +|---|---|---| +| `id` | `string` | Unique block identifier | +| `effectProp` | `"color" \| "position" \| "rotation" \| "scale" \| "visibility"` | Which property this block modifies | +| `targetObjectType` | `"item" \| "sky"` | Whether to target a scene item or the sky color | +| `targetItemId` | `number \| null` | Scene item ID (only relevant when `targetObjectType` is `"item"`) | +| `targetColor` | `string` | Target hex color (used when `effectProp` is `"color"`) | +| `positionAxis` | `"x" \| "y" \| "z"` | Axis to move along (used when `effectProp` is `"position"`) | +| `targetPositionValue` | `number` | Target position on the chosen axis in world units | +| `rotationAxis` | `"x" \| "y" \| "z"` | Axis to rotate around (used when `effectProp` is `"rotation"`) | +| `targetRotationValue` | `number` | Target rotation angle in degrees | +| `targetScale` | `number` | Uniform scale multiplier (used when `effectProp` is `"scale"`) | +| `targetVisibility` | `boolean` | `true` = visible, `false` = hidden (used when `effectProp` is `"visibility"`) | +| `toggle` | `boolean` | When `true`, each trigger alternates between current state and target value instead of always applying the target. Not available for `"visibility"`. | + +--- + +## Full example + +A script that changes a cube's color and moves it along X when a visitor clicks it, using toggle mode so each click alternates between states: + +```json +{ + "scripts": [ + { + "id": "bl-1-1700000000001", + "trigger": { + "id": "bl-2-1700000000002", + "event": "clicked", + "sourceItemId": 4, + "radius": 5, + "exitRadius": 5, + "cooldown": 300 + }, + "effects": [ + { + "id": "bl-3-1700000000003", + "effectProp": "color", + "targetObjectType": "item", + "targetItemId": 4, + "targetColor": "#c05580", + "positionAxis": "x", + "targetPositionValue": 0, + "rotationAxis": "x", + "targetRotationValue": 0, + "targetScale": 1, + "targetVisibility": true, + "toggle": true + }, + { + "id": "bl-4-1700000000004", + "effectProp": "position", + "targetObjectType": "item", + "targetItemId": 4, + "targetColor": "#ffffff", + "positionAxis": "x", + "targetPositionValue": 3.0, + "rotationAxis": "x", + "targetRotationValue": 0, + "targetScale": 1, + "targetVisibility": true, + "toggle": true + } + ] + } + ] +} +``` + +--- + +## Persistence notes + +- **Column:** `graphical_coding` (JSONB) in the Supabase `rooms` table — same column as the old node-graph format; the schema is distinguished by the presence of `scripts` (block coding) vs `nodes`/`connections` (legacy). +- **Save:** the Zustand `blockCoding` store value is JSON-stringified and sent as `graphical_coding` in the `FormData` of `PATCH /api/save-room/[id]`, which writes it directly to the `graphical_coding` column. +- **Load:** `roomData.graphical_coding` is read in `creator.tsx` and hydrated into `setBlockCoding(gc)` in the Zustand store. +- **Local cache:** Zustand's `persist` middleware also writes the value to `localStorage` under the key `"scene-storage"`, so edits survive a page refresh before an explicit save. +- **TypeScript types:** defined inline in `components/creator/graphical-coding/graphical-coding-panel.tsx` as `TriggerBlock`, `EffectBlock`, and `BlockScript`.