phibelle-kit 1.0.21 → 1.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* AGENTS.md content written into the scene directory when cloning.
|
|
3
3
|
* Explains how the Phibelle engine works for AI agents and developers.
|
|
4
4
|
*/
|
|
5
|
-
export declare const AGENTS_MD = "# Phibelle Engine \u2014 How It Works\n\nThis document describes how the Phibelle 3D engine runs your scene and entity scripts. Use it when editing `scene-script.tsx`, `scene-properties.json`, and entity folders (`script.tsx`, `properties.json`, `transforms.json`) or when an AI agent needs to understand the runtime.\n\n## Overview\n\n- **Phibelle** is a React Three Fiber based 3D engine used in the [Phibelle Studio](https://phibelle.studio) editor.\n- A **scene** has one **scene script** (`scene-script.tsx`) and **scene properties** (`scene-properties.json`) in `app/`, and zero or more **entities**. Each entity is a folder `<entity-name>-<engineId>` (e.g. `main-enemy-1`) containing `script.tsx`, `properties.json`, and `transforms.json`. Entity names are lowercased with hyphens (e.g. \"Main Enemy\" \u2192 `main-enemy-1`).\n- You can **create new entities** by adding a new entity folder with `script.tsx` (and optional `properties.json`, `transforms.json`); when the watcher is running, they are synced and the new entities appear in the scene.\n- The engine **concatenates** these scripts and runs them in a single live-editing environment with pre-injected globals (no `import`/`export`).\n\n## Runtime Model\n\n1. **Scene script** must define a component named `SceneRender` that receives `sceneProperties` and `children` and renders an `R3F.Canvas` (or equivalent). It **must** render `{children}` inside the canvas; that is where entity trees are rendered.\n2. **Entity scripts** each define a component named `Render<engineId>` (e.g. `Render1`, `Render42`). The engine builds a registry mapping `engineId` \u2192 render component and mounts entities in a tree (root entities first, then children).\n3. **Live code** is produced by: (1) scene script, (2) all entity scripts, (3) a small bootstrap that wraps the scene in an error boundary, passes `entityRenderRegistry` into `PhibelleRoot`, and calls `render(<PhibelleScene />)`. Your scripts run inside that bootstrap with no direct imports.\n\n## What You Can Use in Scripts (Namespaces)\n\nScripts run with these globals; **do not** use `import` or `export`.\n\n| Namespace | Purpose |\n|-----------|---------|\n| `React` | Hooks and utilities: `React.useState`, `React.useEffect`, `React.useRef`, `React.memo`, etc. |\n| `THREE` | Three.js: `THREE.Vector3`, `THREE.Euler`, `THREE.Mesh`, `THREE.Color`, etc. |\n| `R3F` | React Three Fiber: `R3F.Canvas`, `R3F.useThree`, `R3F.useFrame`, `R3F.extend` |\n| `DREI` | @react-three/drei: `DREI.Environment`, `DREI.OrbitControls`, `DREI.Text`, `DREI.Html`, `DREI.Float`, `DREI.MeshTransmissionMaterial`, etc. |\n| `UIKIT` | @react-three/uikit for 3D UI |\n| `PHI` | Engine-specific: `PHI.useEngineMode`, `PHI.usePlayModeFrame`, `PHI.useGamepad`, `PHI.useEntityThreeObject`, `PHI.useGlobalStore`, `PHI.phibelleResetSelectedEntityIds` |\n\n## PHI package (engine API)\n\nThe `PHI` global is the engine API for scene and entity scripts. It provides types, hooks, and utilities. **Do not** use `import`; always call via `PHI.` (e.g. `PHI.useEngineMode()`).\n\n### PHI types\n\n| Type | Description |\n|------|-------------|\n| `PHI.Transform` | `{ position: THREE.Vector3; rotation: THREE.Euler; scale: THREE.Vector3 }` \u2014 entity transform. |\n| `PHI.Property` | `{ name: string; type: PropertyType; value: unknown }` \u2014 a single scene or entity property. |\n| `PHI.PropertyType` | `string` \u2014 property type identifier (e.g. `\"number\"`, `\"color\"`). |\n| `PHI.EntityData` | `{ name, engineId, threeId?, parentId?, childrenIds, transform, properties }` \u2014 data passed to `Render<engineId>` as `entityData`. |\n| `PHI.GamepadInfo` | `{ connected, buttonA, buttonB, buttonX, buttonY, joystick, joystickRight, RB, LB, RT, LT, start, select, up, down, left, right }` \u2014 from `PHI.useGamepad()`. |\n| `PHI.EngineMode` | `{ editMode: boolean; playMode: boolean }` \u2014 from `PHI.useEngineMode()`. |\n| `PHI.SceneScriptState` | `{ sceneProperties: Record<string, unknown> }` \u2014 from `PHI.useSceneScriptStore()`. |\n\n### PHI hooks and functions\n\n| API | Description |\n|-----|-------------|\n| `PHI.useEngineMode()` | Returns `{ editMode, playMode }`. Use to branch UI (e.g. show helpers only in `editMode`) and to guard play-only logic. |\n| `PHI.usePlayModeFrame(callback)` | `callback(state, delta)` runs once per frame **only in play mode**. Use for game logic; do **not** use `R3F.useFrame` for that (it runs in edit mode too). |\n| `PHI.useGamepad()` | Returns `PHI.GamepadInfo`: `connected`, `joystick` `[x,y]`, `joystickRight`, `buttonA/B/X/Y`, `RB`/`LB`/`RT`/`LT`, `start`/`select`, `up`/`down`/`left`/`right`. |\n| `PHI.useEntityThreeObject(entityData, threeScene)` | Returns the `THREE.Object3D` for the entity in the given scene, or `null` if not yet mounted. Use this to get the actual 3D object of the entity for movement or other interactions. |\n| `PHI.phibelleResetSelectedEntityIds()` | Clears entity selection. Call from the scene script Canvas `onPointerMissed` to deselect when clicking empty space. |\n| `PHI.useGlobalStore()` | Zustand hook for cross-entity state. Call `PHI.useGlobalStore(s => s.globalStore)` to subscribe, or `PHI.useGlobalStore.getState().addToGlobalStore(key, value)` to set from outside React. |\n| `PHI.useSceneScriptStore()` | `useSceneScriptStore()` or `useSceneScriptStore(selector)` \u2014 returns scene script state (e.g. `sceneProperties`). Mainly for scene script. |\n| `PHI.useEntity(entityId)` | Returns `{ entityData: PHI.EntityData }` for the given engine entity id. |\n| `PHI.editEntities(edits)` | `edits: Array<{ id: number; newData: Partial<PHI.EntityData> }>` \u2014 apply partial updates to entities (used by engine; use with care in scripts). |\n| `PHI.PROPERTY_TYPES` | Readonly array of `{ type: PHI.PropertyType; defaultValue: unknown }` for supported property types. |\n\n## Scene Script (`scene-script.tsx`) and Scene Properties (`scene-properties.json`)\n\n- **Component name**: Must be `SceneRender`.\n- **Props**: `sceneProperties: PHI.Property[]`, `children: React.ReactNode`.\n- **Responsibility**: Create the single `R3F.Canvas`, set up lights/environment, and render `{children}`. Attach `onPointerMissed={PHI.phibelleResetSelectedEntityIds}` on the Canvas to clear selection when clicking empty space.\n\nExample:\n\n```tsx\nconst SceneRender = ({\n sceneProperties,\n children,\n}: {\n sceneProperties: PHI.Property[];\n children: React.ReactNode;\n}) => (\n <R3F.Canvas\n shadows\n frameloop=\"always\"\n gl={{ preserveDrawingBuffer: true }}\n onPointerMissed={PHI.phibelleResetSelectedEntityIds}\n >\n <DREI.Environment preset=\"city\" />\n {children}\n </R3F.Canvas>\n);\n```\n\n## Entity Scripts (`<entity-name>-<engineId>/script.tsx` and `properties.json`)\n\n- **Folder name**: `<entity-name>-<engineId>` where entity name is lowercased with hyphens (e.g. `main-enemy-1` for an entity named \"Main Enemy\" with engineId 1). Each entity folder contains:\n - `script.tsx`: The entity render component (required).\n - `properties.json`: Entity properties as a JSON array of `{ name, type, value }` (optional; defaults to `[]`).\n - `transforms.json`: Transform (position, rotation, scale) as a JSON object `{ position, rotation, scale }` (optional; defaults to identity).\n- **Component name**: Must be `Render<engineId>` (e.g. `Render1` for `main-enemy-1`).\n- **Props**: `entityData: PHI.EntityData`.\n- **entityData** contains: `name`, `engineId`, `threeId`, `parentId`, `childrenIds`, `transform`, `properties`.\n- **Properties**: Use `entityData.properties` **by index** (order is fixed in the editor), e.g. `const [colorProp, speedProp] = entityData.properties;`.\n- **Edit vs Play**: Use `PHI.useEngineMode()` \u2192 `{ editMode, playMode }`. For per-frame game logic use **`PHI.usePlayModeFrame(callback)`** only (not `R3F.useFrame`), so logic does not run in edit mode. Show helpers/gizmos only when `editMode === true`.\n\nExample:\n\n```tsx\nconst Render1 = ({ entityData }: { entityData: PHI.EntityData }) => {\n const [colorProp] = entityData.properties;\n const { editMode, playMode } = PHI.useEngineMode();\n\n PHI.usePlayModeFrame((state, delta) => {\n // Game logic here; runs only in play mode\n });\n\n return (\n <group>\n <mesh>\n <boxGeometry args={[1, 1, 1]} />\n <meshStandardMaterial color={colorProp.value} />\n </mesh>\n {editMode && <DREI.Helper type={THREE.BoxHelper} args={[\"yellow\"]} />}\n </group>\n );\n};\n```\n\n## Creating New Entities\n\nYou can add new entities to the scene by creating a new entity folder. Create a folder named `<entity-name>-<engineId>` (e.g. `player-2`, `main-enemy-3`) with a `script.tsx` that defines `Render<engineId>` (e.g. `Render2`, `Render3`). Optionally add `properties.json` (a JSON array of `{ name, type, value }`) and `transforms.json` (object with `position`, `rotation`, `scale`). The entity name in the folder name is lowercased with hyphens (e.g. \"Main Enemy\" \u2192 `main-enemy`). When **watch** is running (`npm run watch` or `npx phibelle-kit watch <sceneId>`), the watcher detects the new folder and creates the corresponding entity in the Phibelle Studio scene. Use a unique `engineId` that does not already exist in `manifest.json`.\n\n## File Layout After Clone\n\n- `scene.json`: Full scene state (from the app); used by the CLI, not by the runtime.\n- `manifest.json`: Maps engine IDs to entity folder names for the watch/sync process.\n- `package.json`: Scripts: `watch` (sync edits to the app), and dependencies for types/IntelliSense.\n- `global.d.ts`: Declares global `PHI`, `THREE`, `R3F`, `DREI`, `UIKIT` for type checking.\n- `app/scene-script.tsx`: Scene script (single `SceneRender`).\n- `app/scene-properties.json`: Scene properties (JSON array; optional).\n- `app/<entity-name>-<engineId>/script.tsx`: Entity script (component `Render<engineId>`).\n- `app/<entity-name>-<engineId>/properties.json`: Entity properties (JSON array; optional).\n- `app/<entity-name>-<engineId>/transforms.json`: Entity transform position/rotation/scale (JSON object; optional).\n\n## Sync Flow (phibelle-kit)\n\n- **clone**: Fetches scene from the app, creates the folder, writes `scene.json`, `package.json`, `global.d.ts`, `AGENTS.md`, and unpacks `scene-script.tsx`, `scene-properties.json`, entity folders (`script.tsx`, `properties.json`, `transforms.json`), and `manifest.json`.\n- **watch**: Watches the scene directory; when you change `scene-script.tsx`, `scene-properties.json`, or any entity file (`script.tsx`, `properties.json`, `transforms.json`), it pushes the change to the scene in the app (and can create new entities when you add a new entity folder with `script.tsx`).\n\nEdits you make in `scene-script.tsx`, `scene-properties.json`, or entity files are synced to the Phibelle Studio scene when the watcher is running (`npm run watch` or `npx phibelle-kit watch <sceneId>`).\n";
|
|
5
|
+
export declare const AGENTS_MD = "# Phibelle Engine \u2014 How It Works\n\nThis document describes how the Phibelle 3D engine runs your scene and entity scripts. Use it when editing `scene-script.tsx`, `scene-properties.json`, and entity folders (`script.tsx`, `properties.json`, `transforms.json`) or when an AI agent needs to understand the runtime.\n\n## Overview\n\n- **Phibelle** is a React Three Fiber based 3D engine used in the [Phibelle Studio](https://phibelle.studio) editor.\n- A **scene** has one **scene script** (`scene-script.tsx`) and **scene properties** (`scene-properties.json`) in `app/`, and zero or more **entities**. Each entity is a folder `<entity-name>-<engineId>` (e.g. `main-enemy-1`) containing `script.tsx`, `properties.json`, and `transforms.json`. Entity names are lowercased with hyphens (e.g. \"Main Enemy\" \u2192 `main-enemy-1`).\n- You can **create new entities** by adding a new entity folder with `script.tsx` (and optional `properties.json`, `transforms.json`); when the watcher is running, they are synced and the new entities appear in the scene.\n- The engine **concatenates** these scripts and runs them in a single live-editing environment with pre-injected globals (no `import`/`export`).\n\n## Runtime Model\n\n1. **Scene script** must define a component named `SceneRender` that receives `sceneProperties` and `children` and renders an `R3F.Canvas` (or equivalent). It **must** render `{children}` inside the canvas; that is where entity trees are rendered.\n2. **DOM UI placement** should be done in `SceneRender` as absolute HTML outside the canvas (canvas + overlay UI in the same parent container). Prefer this shape:\n - `<div style={{ position: \"relative\" }}>`\n - `<R3F.Canvas>{children}</R3F.Canvas>`\n - `<div style={{ position: \"absolute\", inset: 0 }}>...UI...</div>`\n - `</div>`\n3. **Scene-level globals** should be defined from `SceneRender` and stored in `PHI.useGlobalStore` so entities and UI can share state safely.\n4. **Entity scripts** each define a component named `Render<engineId>` (e.g. `Render1`, `Render42`). The engine builds a registry mapping `engineId` \u2192 render component and mounts entities in a tree (root entities first, then children).\n5. **Live code** is produced by: (1) scene script, (2) all entity scripts, (3) a small bootstrap that wraps the scene in an error boundary, passes `entityRenderRegistry` into `PhibelleRoot`, and calls `render(<PhibelleScene />)`. Your scripts run inside that bootstrap with no direct imports.\n\n## What You Can Use in Scripts (Namespaces)\n\nScripts run with these globals; **do not** use `import` or `export`.\n\n| Namespace | Purpose |\n|-----------|---------|\n| `React` | Hooks and utilities: `React.useState`, `React.useEffect`, `React.useRef`, `React.memo`, etc. |\n| `THREE` | Three.js: `THREE.Vector3`, `THREE.Euler`, `THREE.Mesh`, `THREE.Color`, etc. |\n| `R3F` | React Three Fiber: `R3F.Canvas`, `R3F.useThree`, `R3F.useFrame`, `R3F.extend` |\n| `DREI` | @react-three/drei: `DREI.Environment`, `DREI.OrbitControls`, `DREI.Text`, `DREI.Html`, `DREI.Float`, `DREI.MeshTransmissionMaterial`, etc. |\n| `UIKIT` | @react-three/uikit for 3D UI |\n| `PHI` | Engine-specific: `PHI.useEngineMode`, `PHI.usePlayModeFrame`, `PHI.useGamepad`, `PHI.useEntityThreeObject`, `PHI.useGlobalStore`, `PHI.phibelleResetSelectedEntityIds` |\n\n## PHI package (engine API)\n\nThe `PHI` global is the engine API for scene and entity scripts. It provides types, hooks, and utilities. **Do not** use `import`; always call via `PHI.` (e.g. `PHI.useEngineMode()`).\n\n### PHI types\n\n| Type | Description |\n|------|-------------|\n| `PHI.Transform` | `{ position: THREE.Vector3; rotation: THREE.Euler; scale: THREE.Vector3 }` \u2014 entity transform. |\n| `PHI.Property` | `{ name: string; type: PropertyType; value: unknown }` \u2014 a single scene or entity property. |\n| `PHI.PropertyType` | `string` \u2014 property type identifier (e.g. `\"number\"`, `\"color\"`). |\n| `PHI.EntityData` | `{ name, engineId, threeId?, parentId?, childrenIds, transform, properties }` \u2014 data passed to `Render<engineId>` as `entityData`. |\n| `PHI.GamepadInfo` | `{ connected, buttonA, buttonB, buttonX, buttonY, joystick, joystickRight, RB, LB, RT, LT, start, select, up, down, left, right }` \u2014 from `PHI.useGamepad()`. |\n| `PHI.EngineMode` | `{ editMode: boolean; playMode: boolean }` \u2014 from `PHI.useEngineMode()`. |\n| `PHI.SceneScriptState` | `{ sceneProperties: Record<string, unknown> }` \u2014 from `PHI.useSceneScriptStore()`. |\n\n### PHI hooks and functions\n\n| API | Description |\n|-----|-------------|\n| `PHI.useEngineMode()` | Returns `{ editMode, playMode }`. Use to branch UI (e.g. show helpers only in `editMode`) and to guard play-only logic. |\n| `PHI.usePlayModeFrame(callback)` | `callback(state, delta)` runs once per frame **only in play mode**. Use for game logic; do **not** use `R3F.useFrame` for that (it runs in edit mode too). |\n| `PHI.useGamepad()` | Returns `PHI.GamepadInfo`: `connected`, `joystick` `[x,y]`, `joystickRight`, `buttonA/B/X/Y`, `RB`/`LB`/`RT`/`LT`, `start`/`select`, `up`/`down`/`left`/`right`. |\n| `PHI.useEntityThreeObject(entityData, threeScene)` | Returns the `THREE.Object3D` for the entity in the given scene, or `null` if not yet mounted. Use this to get the actual 3D object of the entity for movement or other interactions. |\n| `PHI.phibelleResetSelectedEntityIds()` | Clears entity selection. Call from the scene script Canvas `onPointerMissed` to deselect when clicking empty space. |\n| `PHI.useGlobalStore()` | Zustand hook for cross-entity state. Call `PHI.useGlobalStore(s => s.globalStore)` to subscribe, or `PHI.useGlobalStore.getState().addToGlobalStore(key, value)` to set from outside React. |\n| `PHI.useSceneScriptStore()` | `useSceneScriptStore()` or `useSceneScriptStore(selector)` \u2014 returns scene script state (e.g. `sceneProperties`). Mainly for scene script. |\n| `PHI.useEntity(entityId)` | Returns `{ entityData: PHI.EntityData }` for the given engine entity id. |\n| `PHI.editEntities(edits)` | `edits: Array<{ id: number; newData: Partial<PHI.EntityData> }>` \u2014 apply partial updates to entities (used by engine; use with care in scripts). |\n| `PHI.PROPERTY_TYPES` | Readonly array of `{ type: PHI.PropertyType; defaultValue: unknown }` for supported property types. |\n\n## Scene Script (`scene-script.tsx`) and Scene Properties (`scene-properties.json`)\n\n- **Component name**: Must be `SceneRender`.\n- **Props**: `sceneProperties: PHI.Property[]`, `children: React.ReactNode`.\n- **Responsibility**: Create the single `R3F.Canvas`, set up lights/environment, and render `{children}`. Attach `onPointerMissed={PHI.phibelleResetSelectedEntityIds}` on the Canvas to clear selection when clicking empty space.\n- **UI convention (preferred)**: Put regular DOM UI outside the canvas as absolute overlay elements in the same parent wrapper.\n- **Scene globals (preferred)**: Define scene-level shared data in `SceneRender` and store it with `PHI.useGlobalStore` so entities and overlay UI can use the same values.\n- **Edit mode lockout (required)**: Scene UI must be disabled in edit mode (non-interactive), and scene gameplay interactions should only be accessible in play mode.\n\nExample:\n\n```tsx\nconst SceneRender = ({\n sceneProperties,\n children,\n}: {\n sceneProperties: PHI.Property[];\n children: React.ReactNode;\n}) => {\n const { editMode } = PHI.useEngineMode();\n const globalStore = PHI.useGlobalStore((s) => s.globalStore);\n\n React.useEffect(() => {\n PHI.useGlobalStore.getState().addToGlobalStore(\"sceneInfo\", {\n title: \"My Scene\",\n hudVisible: true,\n });\n }, []);\n\n return (\n <div style={{ position: \"relative\", width: \"100%\", height: \"100%\" }}>\n <R3F.Canvas\n shadows\n frameloop=\"always\"\n gl={{ preserveDrawingBuffer: true }}\n onPointerMissed={PHI.phibelleResetSelectedEntityIds}\n >\n <DREI.Environment preset=\"city\" />\n {children}\n </R3F.Canvas>\n\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n pointerEvents: editMode ? \"none\" : \"auto\",\n opacity: editMode ? 0.65 : 1,\n }}\n >\n {globalStore.sceneInfo?.hudVisible && (\n <div style={{ position: \"absolute\", top: 12, left: 12, color: \"white\" }}>\n {globalStore.sceneInfo?.title}\n </div>\n )}\n </div>\n </div>\n );\n};\n```\n\n## Entity Scripts (`<entity-name>-<engineId>/script.tsx` and `properties.json`)\n\n- **Folder name**: `<entity-name>-<engineId>` where entity name is lowercased with hyphens (e.g. `main-enemy-1` for an entity named \"Main Enemy\" with engineId 1). Each entity folder contains:\n - `script.tsx`: The entity render component (required).\n - `properties.json`: Entity properties as a JSON array of `{ name, type, value }` (optional; defaults to `[]`).\n - `transforms.json`: Transform (position, rotation, scale) as a JSON object `{ position, rotation, scale }` (optional; defaults to identity).\n- **Component name**: Must be `Render<engineId>` (e.g. `Render1` for `main-enemy-1`).\n- **Props**: `entityData: PHI.EntityData`.\n- **entityData** contains: `name`, `engineId`, `threeId`, `parentId`, `childrenIds`, `transform`, `properties`.\n- **Properties**: Use `entityData.properties` **by index** (order is fixed in the editor), e.g. `const [colorProp, speedProp] = entityData.properties;`.\n- **Edit vs Play**: Use `PHI.useEngineMode()` \u2192 `{ editMode, playMode }`. For per-frame game logic use **`PHI.usePlayModeFrame(callback)`** only (not `R3F.useFrame`), so logic does not run in edit mode. Show helpers/gizmos only when `editMode === true`.\n- **Interaction gating**: Any interactive/gameplay entity behavior should be inaccessible in edit mode and only run in play mode.\n\n## Editing Workflow (AI + Human)\n\n- **Explore first**: Before changing scripts, inspect the current entity tree plus each target entity's properties and transforms so edits match existing scene structure.\n- **Orientation awareness**: Be mindful of entity orientation and placement; if behavior/visuals are off, adjust transform values (position/rotation/scale) intentionally.\n- **Incremental edits**: Prefer small, targeted updates instead of large rewrites.\n- **Shared state strategy**: When scene script, UI, and/or multiple entities need shared data, prefer `PHI.useGlobalStore` as the common state bridge.\n\nExample:\n\n```tsx\nconst Render1 = ({ entityData }: { entityData: PHI.EntityData }) => {\n const [colorProp] = entityData.properties;\n const { editMode, playMode } = PHI.useEngineMode();\n\n PHI.usePlayModeFrame((state, delta) => {\n // Game logic here; runs only in play mode\n });\n\n return (\n <group>\n <mesh>\n <boxGeometry args={[1, 1, 1]} />\n <meshStandardMaterial color={colorProp.value} />\n </mesh>\n {editMode && <DREI.Helper type={THREE.BoxHelper} args={[\"yellow\"]} />}\n </group>\n );\n};\n```\n\n## Creating New Entities\n\nYou can add new entities to the scene by creating a new entity folder. Create a folder named `<entity-name>-<engineId>` (e.g. `player-2`, `main-enemy-3`) with a `script.tsx` that defines `Render<engineId>` (e.g. `Render2`, `Render3`). Optionally add `properties.json` (a JSON array of `{ name, type, value }`) and `transforms.json` (object with `position`, `rotation`, `scale`). The entity name in the folder name is lowercased with hyphens (e.g. \"Main Enemy\" \u2192 `main-enemy`). When **watch** is running (`npm run watch` or `npx phibelle-kit watch <sceneId>`), the watcher detects the new folder and creates the corresponding entity in the Phibelle Studio scene. Use a unique `engineId` that does not already exist in `manifest.json`.\n\n## File Layout After Clone\n\n- `scene.json`: Full scene state (from the app); used by the CLI, not by the runtime.\n- `manifest.json`: Maps engine IDs to entity folder names for the watch/sync process.\n- `package.json`: Scripts: `watch` (sync edits to the app), and dependencies for types/IntelliSense.\n- `global.d.ts`: Declares global `PHI`, `THREE`, `R3F`, `DREI`, `UIKIT` for type checking.\n- `app/scene-script.tsx`: Scene script (single `SceneRender`).\n- `app/scene-properties.json`: Scene properties (JSON array; optional).\n- `app/<entity-name>-<engineId>/script.tsx`: Entity script (component `Render<engineId>`).\n- `app/<entity-name>-<engineId>/properties.json`: Entity properties (JSON array; optional).\n- `app/<entity-name>-<engineId>/transforms.json`: Entity transform position/rotation/scale (JSON object; optional).\n\n## Sync Flow (phibelle-kit)\n\n- **clone**: Fetches scene from the app, creates the folder, writes `scene.json`, `package.json`, `global.d.ts`, `AGENTS.md`, and unpacks `scene-script.tsx`, `scene-properties.json`, entity folders (`script.tsx`, `properties.json`, `transforms.json`), and `manifest.json`.\n- **watch**: Watches the scene directory; when you change `scene-script.tsx`, `scene-properties.json`, or any entity file (`script.tsx`, `properties.json`, `transforms.json`), it pushes the change to the scene in the app (and can create new entities when you add a new entity folder with `script.tsx`).\n\nEdits you make in `scene-script.tsx`, `scene-properties.json`, or entity files are synced to the Phibelle Studio scene when the watcher is running (`npm run watch` or `npx phibelle-kit watch <sceneId>`).\n";
|
|
@@ -16,8 +16,14 @@ This document describes how the Phibelle 3D engine runs your scene and entity sc
|
|
|
16
16
|
## Runtime Model
|
|
17
17
|
|
|
18
18
|
1. **Scene script** must define a component named \`SceneRender\` that receives \`sceneProperties\` and \`children\` and renders an \`R3F.Canvas\` (or equivalent). It **must** render \`{children}\` inside the canvas; that is where entity trees are rendered.
|
|
19
|
-
2. **
|
|
20
|
-
|
|
19
|
+
2. **DOM UI placement** should be done in \`SceneRender\` as absolute HTML outside the canvas (canvas + overlay UI in the same parent container). Prefer this shape:
|
|
20
|
+
- \`<div style={{ position: "relative" }}>\`
|
|
21
|
+
- \`<R3F.Canvas>{children}</R3F.Canvas>\`
|
|
22
|
+
- \`<div style={{ position: "absolute", inset: 0 }}>...UI...</div>\`
|
|
23
|
+
- \`</div>\`
|
|
24
|
+
3. **Scene-level globals** should be defined from \`SceneRender\` and stored in \`PHI.useGlobalStore\` so entities and UI can share state safely.
|
|
25
|
+
4. **Entity scripts** each define a component named \`Render<engineId>\` (e.g. \`Render1\`, \`Render42\`). The engine builds a registry mapping \`engineId\` → render component and mounts entities in a tree (root entities first, then children).
|
|
26
|
+
5. **Live code** is produced by: (1) scene script, (2) all entity scripts, (3) a small bootstrap that wraps the scene in an error boundary, passes \`entityRenderRegistry\` into \`PhibelleRoot\`, and calls \`render(<PhibelleScene />)\`. Your scripts run inside that bootstrap with no direct imports.
|
|
21
27
|
|
|
22
28
|
## What You Can Use in Scripts (Namespaces)
|
|
23
29
|
|
|
@@ -68,6 +74,9 @@ The \`PHI\` global is the engine API for scene and entity scripts. It provides t
|
|
|
68
74
|
- **Component name**: Must be \`SceneRender\`.
|
|
69
75
|
- **Props**: \`sceneProperties: PHI.Property[]\`, \`children: React.ReactNode\`.
|
|
70
76
|
- **Responsibility**: Create the single \`R3F.Canvas\`, set up lights/environment, and render \`{children}\`. Attach \`onPointerMissed={PHI.phibelleResetSelectedEntityIds}\` on the Canvas to clear selection when clicking empty space.
|
|
77
|
+
- **UI convention (preferred)**: Put regular DOM UI outside the canvas as absolute overlay elements in the same parent wrapper.
|
|
78
|
+
- **Scene globals (preferred)**: Define scene-level shared data in \`SceneRender\` and store it with \`PHI.useGlobalStore\` so entities and overlay UI can use the same values.
|
|
79
|
+
- **Edit mode lockout (required)**: Scene UI must be disabled in edit mode (non-interactive), and scene gameplay interactions should only be accessible in play mode.
|
|
71
80
|
|
|
72
81
|
Example:
|
|
73
82
|
|
|
@@ -78,17 +87,46 @@ const SceneRender = ({
|
|
|
78
87
|
}: {
|
|
79
88
|
sceneProperties: PHI.Property[];
|
|
80
89
|
children: React.ReactNode;
|
|
81
|
-
}) =>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
}) => {
|
|
91
|
+
const { editMode } = PHI.useEngineMode();
|
|
92
|
+
const globalStore = PHI.useGlobalStore((s) => s.globalStore);
|
|
93
|
+
|
|
94
|
+
React.useEffect(() => {
|
|
95
|
+
PHI.useGlobalStore.getState().addToGlobalStore("sceneInfo", {
|
|
96
|
+
title: "My Scene",
|
|
97
|
+
hudVisible: true,
|
|
98
|
+
});
|
|
99
|
+
}, []);
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div style={{ position: "relative", width: "100%", height: "100%" }}>
|
|
103
|
+
<R3F.Canvas
|
|
104
|
+
shadows
|
|
105
|
+
frameloop="always"
|
|
106
|
+
gl={{ preserveDrawingBuffer: true }}
|
|
107
|
+
onPointerMissed={PHI.phibelleResetSelectedEntityIds}
|
|
108
|
+
>
|
|
109
|
+
<DREI.Environment preset="city" />
|
|
110
|
+
{children}
|
|
111
|
+
</R3F.Canvas>
|
|
112
|
+
|
|
113
|
+
<div
|
|
114
|
+
style={{
|
|
115
|
+
position: "absolute",
|
|
116
|
+
inset: 0,
|
|
117
|
+
pointerEvents: editMode ? "none" : "auto",
|
|
118
|
+
opacity: editMode ? 0.65 : 1,
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
{globalStore.sceneInfo?.hudVisible && (
|
|
122
|
+
<div style={{ position: "absolute", top: 12, left: 12, color: "white" }}>
|
|
123
|
+
{globalStore.sceneInfo?.title}
|
|
124
|
+
</div>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
92
130
|
\`\`\`
|
|
93
131
|
|
|
94
132
|
## Entity Scripts (\`<entity-name>-<engineId>/script.tsx\` and \`properties.json\`)
|
|
@@ -102,6 +140,14 @@ const SceneRender = ({
|
|
|
102
140
|
- **entityData** contains: \`name\`, \`engineId\`, \`threeId\`, \`parentId\`, \`childrenIds\`, \`transform\`, \`properties\`.
|
|
103
141
|
- **Properties**: Use \`entityData.properties\` **by index** (order is fixed in the editor), e.g. \`const [colorProp, speedProp] = entityData.properties;\`.
|
|
104
142
|
- **Edit vs Play**: Use \`PHI.useEngineMode()\` → \`{ editMode, playMode }\`. For per-frame game logic use **\`PHI.usePlayModeFrame(callback)\`** only (not \`R3F.useFrame\`), so logic does not run in edit mode. Show helpers/gizmos only when \`editMode === true\`.
|
|
143
|
+
- **Interaction gating**: Any interactive/gameplay entity behavior should be inaccessible in edit mode and only run in play mode.
|
|
144
|
+
|
|
145
|
+
## Editing Workflow (AI + Human)
|
|
146
|
+
|
|
147
|
+
- **Explore first**: Before changing scripts, inspect the current entity tree plus each target entity's properties and transforms so edits match existing scene structure.
|
|
148
|
+
- **Orientation awareness**: Be mindful of entity orientation and placement; if behavior/visuals are off, adjust transform values (position/rotation/scale) intentionally.
|
|
149
|
+
- **Incremental edits**: Prefer small, targeted updates instead of large rewrites.
|
|
150
|
+
- **Shared state strategy**: When scene script, UI, and/or multiple entities need shared data, prefer \`PHI.useGlobalStore\` as the common state bridge.
|
|
105
151
|
|
|
106
152
|
Example:
|
|
107
153
|
|