@vgai/editor 0.4.0 → 0.4.1
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.
- package/dist/assets/{axe-BJjvBeOZ.js → axe-N4cNyzfs.js} +1 -1
- package/dist/assets/{browserAll-Dn0pgD87.js → browserAll-BIBT0Z2H.js} +1 -1
- package/dist/assets/{create-runtime-BF9OD-U8.js → create-runtime-DxLhdk4U.js} +1 -1
- package/dist/assets/{game-u6c2Patu.js → game-4NbNRROm.js} +1 -1
- package/dist/assets/{game-umrHIqFB.js → game-B3UH5LjS.js} +1 -1
- package/dist/assets/{game-BR0GBNgF.js → game-ByxRLJi1.js} +1 -1
- package/dist/assets/{game-DeTGdgYt.js → game-CJ5BqOYG.js} +1 -1
- package/dist/assets/{game-DQxhCYsZ.js → game-CSJm1kwB.js} +1 -1
- package/dist/assets/{game-CLNm2g0y.js → game-D9Mo4L3Q.js} +1 -1
- package/dist/assets/{game-B346oj_f.js → game-DJmU9BQF.js} +1 -1
- package/dist/assets/{game-BCNN63uV.js → game-ZqGLgxRj.js} +1 -1
- package/dist/assets/{game-loop-t5W_oqxz.js → game-loop-9YP76JB1.js} +1 -1
- package/dist/assets/{game-source-DzE9taML.js → game-source-DLKG9pmj.js} +1 -1
- package/dist/assets/{index-DbQzGLxv.js → index-8A4JLNIp.js} +902 -892
- package/dist/assets/{index-CgcH1RpF.js → index-Brzr17q0.js} +1 -1
- package/dist/assets/{index-D3tcNB2T.js → index-CnKtvrru.js} +1 -1
- package/dist/assets/{index-Fc8Ezr20.js → index-D-WR11Jw.js} +1 -1
- package/dist/assets/index-DiUacMXR.css +1 -0
- package/dist/assets/{ingest-siblings-CW_xbvrF.js → ingest-siblings-Cmx39OBK.js} +1 -1
- package/dist/assets/{mount-manifest-DgUscyaD.js → mount-manifest-kzdZgkkc.js} +1 -1
- package/dist/assets/{react-18-C4tVCii_.js → react-18-DvWsdwa_.js} +1 -1
- package/dist/assets/webworkerAll-C9GR-KrR.js +1 -0
- package/dist/assets/{world-documents-BSaA5YIF.js → world-documents-DM_aIMAW.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/packaged.mjs +27125 -1075
- package/package.json +2 -2
- package/server/asset-history-snapshots.ts +189 -0
- package/server/asset-library-routes.ts +404 -53
- package/server/catalog-import-recipe.ts +97 -0
- package/server/catalog-reimport.ts +141 -0
- package/server/cloud-asset-catalog.ts +71 -5
- package/server/dev.ts +1 -1
- package/server/editor-server.ts +106 -51
- package/server/editor-sse.ts +5 -0
- package/server/generative-execution-context.ts +45 -0
- package/server/model-import-conversion.ts +345 -0
- package/server/packaged.ts +1 -1
- package/server/project-hmr-files.ts +3 -5
- package/server/project-output-writer.ts +440 -36
- package/server/project-tools.ts +529 -0
- package/server/server-utils.ts +14 -11
- package/src/EditorContext.tsx +4 -4
- package/src/action-registry.ts +12 -0
- package/src/asset-editor-context.tsx +9 -0
- package/src/asset-events.ts +3 -15
- package/src/asset-selection.ts +3 -0
- package/src/asset-workflow/asset-capabilities.ts +40 -0
- package/src/asset-workflow/asset-import-jobs.ts +108 -0
- package/src/asset-workflow/asset-library-query.ts +8 -1
- package/src/asset-workflow/asset-workflow-quality.ts +2 -0
- package/src/asset-workflow/import-contract.ts +2 -0
- package/src/asset-workflow/model-inspection.ts +92 -1
- package/src/asset-workflow/preview-resource-lifetime.ts +44 -0
- package/src/asset-workflow/project-asset-commands.ts +1 -0
- package/src/asset-workflow/project-asset-health.ts +3 -6
- package/src/asset-workflow/project-asset-operations.ts +119 -4
- package/src/command-listener.ts +37 -9
- package/src/components/ApplicationMenus.tsx +13 -15
- package/src/components/AssetBrowser.tsx +301 -26
- package/src/components/AssetImportDetails.tsx +207 -92
- package/src/components/AssetImportJobStatus.tsx +55 -0
- package/src/components/CenterDocuments.tsx +28 -1
- package/src/components/CommandPalette.tsx +2 -2
- package/src/components/DefaultEditorLayout.tsx +4 -0
- package/src/components/InspectorToolSection.tsx +12 -10
- package/src/components/OnlineAssetBrowser.tsx +197 -11
- package/src/components/ToolHost.tsx +8 -8
- package/src/components/ViewportOverlay.tsx +13 -26
- package/src/components/ViewportPanel.tsx +19 -14
- package/src/components/ViewportShadingMenu.tsx +103 -0
- package/src/components/WorkspaceDock.tsx +103 -17
- package/src/components/asset-documents.tsx +38 -11
- package/src/components/asset-editor-persistence.ts +300 -5
- package/src/components/asset-selection-section.tsx +83 -9
- package/src/components/asset-viewers/AudioViewer.tsx +4 -7
- package/src/components/asset-viewers/ModelViewer.tsx +168 -5
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +52 -12
- package/src/components/asset-viewers/SourceAssetViewer.tsx +7 -26
- package/src/components/asset-workflow.css +30 -3
- package/src/components/primitives/editor-icons.ts +1 -0
- package/src/components/project-tool-documents.tsx +298 -0
- package/src/components/status-contributions.tsx +38 -0
- package/src/components/tool-documents.tsx +20 -17
- package/src/components/tool-schema-form.tsx +258 -0
- package/src/components/workspace-dock.css +16 -0
- package/src/editor-api.ts +159 -22
- package/src/editor-store.ts +6 -5
- package/src/editor-viewport.ts +12 -7
- package/src/game-viewport-state.ts +28 -0
- package/src/model-thumbnail.ts +97 -9
- package/src/play-mode.ts +30 -3
- package/src/project-tool-discovery.ts +5 -5
- package/src/{project-operations.ts → project-tools.ts} +39 -26
- package/src/scene-sync.ts +0 -67
- package/src/storage/handle-storage.ts +5 -1
- package/src/storage/http-storage.ts +7 -2
- package/src/storage/mem-storage.ts +7 -1
- package/src/storage/types.ts +2 -0
- package/src/theme-css.ts +17 -7
- package/src/tool-loader.ts +152 -241
- package/src/viewport-shading-boundary.ts +15 -0
- package/src/widgets/index.ts +2 -1
- package/src/workspace-dock-controller.ts +8 -0
- package/src/workspace-document-registry.ts +4 -4
- package/src/workspace-state-persistence.ts +36 -10
- package/template/.claude/skills/editor/SKILL.md +13 -3
- package/template/AGENTS.md +100 -83
- package/template/package.json +16 -0
- package/template/public/scenes/default.vscn.json +1 -2
- package/template/scripts/playtest.ts +7 -37
- package/template/server/rooms/game-room.ts +2 -2
- package/template/src/data/README.md +3 -2
- package/template/src/data/tuning.data.json +1 -3
- package/template/src/data/tuning.schema.json +0 -12
- package/template/src/data/tuning.schema.ts +2 -9
- package/template/src/data/tuning.ts +1 -1
- package/template/src/scripts/registry.ts +0 -3
- package/template/src/tools/README.md +151 -16
- package/template/src/tools/autoplay.tool.ts +31 -0
- package/template/src/tools/autoplay.ts +15 -0
- package/template/src/tools/tuning.document.tsx +107 -0
- package/template/src/tools/tuning.tool.ts +24 -0
- package/template/src/tools/tuning.ts +9 -0
- package/template/src/ui/Button.stories.tsx +1 -1
- package/template/tests/acceptance/example.spec.ts +4 -59
- package/template/vgai.game.json +1 -1
- package/dist/assets/index-bAGhEnnO.css +0 -1
- package/dist/assets/webworkerAll-BA4aq7xq.js +0 -1
- package/dist/project-scripts.bundle.js +0 -3917
- package/server/project-operations.ts +0 -343
- package/src/components/project-operation-documents.tsx +0 -226
- package/template/src/operations/README.md +0 -88
- package/template/src/scripts/components/data-spinner.ts +0 -86
- package/template/src/scripts/components/spin-lap-math.ts +0 -27
- package/template/src/tools/example.tool.tsx +0 -97
- package/template/src/tools/spin.tool.tsx +0 -101
- package/template/tests/logic/example.test.ts +0 -31
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { GameComponent } from '@engine/ecs/game-component';
|
|
2
|
-
import type { GameContext } from '@engine/runtime/types';
|
|
3
|
-
import type * as THREE from 'three';
|
|
4
|
-
import { z } from 'zod';
|
|
5
|
-
import { tuning } from '../../data/tuning';
|
|
6
|
-
import { advanceLap } from './spin-lap-math';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* The template's worked data-asset consumer (docs/DATA-TOOLS-DESIGN.md §6.2
|
|
10
|
-
* — the pattern ships working at birth): spins the default scene's Box at
|
|
11
|
-
* `boxSpinSpeed` and tints it `boxColor`, both read from
|
|
12
|
-
* `src/data/tuning.data.json` through the `tuning` handle.
|
|
13
|
-
*
|
|
14
|
-
* Try it: press ▶, then edit `src/data/tuning.data.json` and save — the box
|
|
15
|
-
* changes speed/color LIVE (Vite HMR, no reload; see src/data/tuning.ts).
|
|
16
|
-
*
|
|
17
|
-
* This component demonstrates BOTH halves of the data-vs-config split
|
|
18
|
-
* (§3.3/§6.2): `boxSpinSpeed`/`boxColor` are PROJECT-WIDE feel, so they live
|
|
19
|
-
* in the data asset and read through `tuning.get()` (every frame — cache a
|
|
20
|
-
* field across frames and live tuning silently stops reaching it). `spinScale`
|
|
21
|
-
* is THIS entity's own knob — per-entity config authored on the component
|
|
22
|
-
* itself via `static schema` (the same shape SceneCamera uses), read live by
|
|
23
|
-
* an inspector tool through `useSelection()` (see `spin.tool.tsx`).
|
|
24
|
-
*
|
|
25
|
-
* It's also the template's worked debug-seam consumer (`ctx.debug`,
|
|
26
|
-
* `docs/SYNTHETIC-PLAYER-SPEC.md`/`docs/ACCEPTANCE-DRIVER-SPEC.md`, Task
|
|
27
|
-
* 4.2): holding the `attack` action (see `public/inputmaps/default.inputmap.json`)
|
|
28
|
-
* boosts the spin, and each completed lap emits a `spin-lap` event —
|
|
29
|
-
* `tests/acceptance/example.spec.ts` drives `attack` through the HONEST input
|
|
30
|
-
* path and asserts on the resulting `spinner` state/events; `spinner.reset`
|
|
31
|
-
* is a declared fixture, never a substitute for that proof (D16).
|
|
32
|
-
*/
|
|
33
|
-
export class DataSpinner extends GameComponent {
|
|
34
|
-
static schema = z.object({
|
|
35
|
-
spinScale: z
|
|
36
|
-
.number()
|
|
37
|
-
.default(1)
|
|
38
|
-
.describe(
|
|
39
|
-
'Per-entity multiplier on the shared boxSpinSpeed — the project-wide feel stays in ' +
|
|
40
|
-
"src/data/tuning.data.json; this is the one knob that is THIS entity's own.",
|
|
41
|
-
),
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
spinScale = 1;
|
|
45
|
-
private rotationSinceLap = 0;
|
|
46
|
-
private lapsCompleted = 0;
|
|
47
|
-
|
|
48
|
-
init(ctx: GameContext) {
|
|
49
|
-
ctx.debug?.registerStateProvider(
|
|
50
|
-
'spinner',
|
|
51
|
-
() => ({ lapsCompleted: this.lapsCompleted, spinScale: this.spinScale }),
|
|
52
|
-
{ tier: 'observable' },
|
|
53
|
-
);
|
|
54
|
-
// Fixture, never a proof (D16): lets a test jump straight to a known
|
|
55
|
-
// lapsCompleted instead of waiting out real spin time. The acceptance
|
|
56
|
-
// spec's actual PROOF is driving 'attack' through game.input.hold and
|
|
57
|
-
// asserting on the observable 'spinner' state/'spin-lap' events below.
|
|
58
|
-
ctx.debug?.registerCommand(
|
|
59
|
-
'spinner.reset',
|
|
60
|
-
{ description: 'Reset lapsCompleted to 0.', locus: 'client' },
|
|
61
|
-
() => {
|
|
62
|
-
this.lapsCompleted = 0;
|
|
63
|
-
this.rotationSinceLap = 0;
|
|
64
|
-
},
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
update(dt: number, ctx: GameContext) {
|
|
69
|
-
const t = tuning.get();
|
|
70
|
-
const boost = ctx.input.isPressed('attack') ? 4 : 1;
|
|
71
|
-
const delta = t.boxSpinSpeed * this.spinScale * boost * dt;
|
|
72
|
-
this.object3D.rotation.y += delta;
|
|
73
|
-
|
|
74
|
-
const progress = advanceLap(this.rotationSinceLap, delta);
|
|
75
|
-
this.rotationSinceLap = progress.rotationSinceLap;
|
|
76
|
-
if (progress.lapsCompleted > 0) {
|
|
77
|
-
this.lapsCompleted += progress.lapsCompleted;
|
|
78
|
-
ctx.debug?.emit('spin-lap', { lapsCompleted: this.lapsCompleted });
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const material = (this.object3D as THREE.Mesh).material as
|
|
82
|
-
| THREE.MeshStandardMaterial
|
|
83
|
-
| undefined;
|
|
84
|
-
if (material?.color) material.color.set(t.boxColor);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure lap-counting math for `DataSpinner` (`data-spinner.ts`) — factored out
|
|
3
|
-
* into its own zero-dependency module so `tests/logic/example.test.ts` can
|
|
4
|
-
* exercise the actual rule the component ships with, with no `@engine`/DOM/
|
|
5
|
-
* Playwright dependency: the L1 layer (`docs/ACCEPTANCE-DRIVER-SPEC.md` §0 —
|
|
6
|
-
* "never debug a pure-model bug through the browser").
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const FULL_ROTATION = Math.PI * 2;
|
|
10
|
-
|
|
11
|
-
export interface LapProgress {
|
|
12
|
-
/** Radians accumulated since the last completed lap, always in `[0, 2π)`. */
|
|
13
|
-
rotationSinceLap: number;
|
|
14
|
-
/** How many full 2π rotations `delta` pushed past on this call (0, 1, 2, ...). */
|
|
15
|
-
lapsCompleted: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** Advance `rotationSinceLap` by `delta` radians, rolling over into completed laps. */
|
|
19
|
-
export function advanceLap(rotationSinceLap: number, delta: number): LapProgress {
|
|
20
|
-
let next = rotationSinceLap + delta;
|
|
21
|
-
let lapsCompleted = 0;
|
|
22
|
-
while (next >= FULL_ROTATION) {
|
|
23
|
-
next -= FULL_ROTATION;
|
|
24
|
-
lapsCompleted++;
|
|
25
|
-
}
|
|
26
|
-
return { rotationSinceLap: next, lapsCompleted };
|
|
27
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Example project tool (a center-document tool) — the worked pattern
|
|
3
|
-
* (docs/DATA-TOOLS-DESIGN.md §3, in the engine checkout). Slightly
|
|
4
|
-
* over-commented on purpose: this file is read far more than it runs.
|
|
5
|
-
*
|
|
6
|
-
* A tool is ONE file, `src/tools/<name>.tool.tsx`, exporting exactly two
|
|
7
|
-
* things:
|
|
8
|
-
* 1. `tool` — metadata via `defineTool` (id, tab title, placement),
|
|
9
|
-
* 2. a default-exported plain React component.
|
|
10
|
-
* The editor discovers it automatically (folder scan — no registration
|
|
11
|
-
* anywhere; delete the file and it's gone) and opens it as a CENTER
|
|
12
|
-
* workspace document via the command palette's `Open Tool: Tuning` action
|
|
13
|
-
* (`placement: 'document'`; the older `placement: 'dock'` spelling preserves
|
|
14
|
-
* its historical bottom-panel placement; a `placement: 'utility'` tool also
|
|
15
|
-
* renders as a bottom utility). It stays interactive during play mode, so
|
|
16
|
-
* you tune while the game runs.
|
|
17
|
-
*
|
|
18
|
-
* Tools are EDITOR-ONLY dev surfaces: nothing in game code imports
|
|
19
|
-
* `src/tools/`, so `npm run build` never bundles this file — cheats and
|
|
20
|
-
* tuning panels can't leak into a shipped game. Keep game logic out of here.
|
|
21
|
-
*
|
|
22
|
-
* This example closes the full live-tuning loop on the `tuning` data asset
|
|
23
|
-
* (`src/data/` — the trio this project ships working):
|
|
24
|
-
*
|
|
25
|
-
* slider → writeData() → src/data/tuning.data.json written on disk
|
|
26
|
-
* → Vite HMR → tuning.hotSwap() → useData re-renders this panel
|
|
27
|
-
* → and the RUNNING game (DataSpinner reads tuning.get() per frame)
|
|
28
|
-
* picks the value up the same instant, no reload.
|
|
29
|
-
*
|
|
30
|
-
* That's the whole architecture: the file is the document; the editor, this
|
|
31
|
-
* panel, VS Code, and the game are all just views of it.
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
import { useData, writeData } from '@engine/react/use-data';
|
|
35
|
-
import { defineTool } from '@engine/tools/define-tool';
|
|
36
|
-
// A tool imports the project's OWN data handles — the same ones the game
|
|
37
|
-
// reads. One handle, every surface agrees on the values.
|
|
38
|
-
import { tuning } from '../data/tuning';
|
|
39
|
-
|
|
40
|
-
/** The editor reads this export to place the tool (docs/DATA-TOOLS-DESIGN.md
|
|
41
|
-
* §3.1). `id` keys the document; `title` is the tab/action label;
|
|
42
|
-
* `'document'` is the project-global placement — it opens as a center
|
|
43
|
-
* document (for the selection-scoped `'inspector'` placement, see
|
|
44
|
-
* `spin.tool.tsx`; transient output panels declare `'utility'` and render
|
|
45
|
-
* as a bottom tab). `'dock'` is a deprecated compatibility spelling for a
|
|
46
|
-
* bottom utility — supported so existing tools keep their behavior. */
|
|
47
|
-
export const tool = defineTool({
|
|
48
|
-
id: 'tuning',
|
|
49
|
-
title: 'Tuning',
|
|
50
|
-
placement: 'document',
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
/** The panel itself: plain React, inline styles (editor chrome around it is
|
|
54
|
-
* editor-styled; what's inside is yours). No providers needed. */
|
|
55
|
-
export default function TuningTool() {
|
|
56
|
-
// useData = live values: re-renders once per accepted edit of the data
|
|
57
|
-
// file, whoever made it (this slider, the editor's Data tab, VS Code, an
|
|
58
|
-
// agent). Read it every render — never copy fields into component state.
|
|
59
|
-
const values = useData(tuning);
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<div style={{ padding: 12, fontSize: 12, fontFamily: 'ui-monospace, monospace' }}>
|
|
63
|
-
{/* Live readout — proves the round trip: drag the slider (or edit the
|
|
64
|
-
JSON anywhere else) and watch these values follow the file. */}
|
|
65
|
-
<div style={{ marginBottom: 10, color: '#8c8c8c' }}>
|
|
66
|
-
src/data/tuning.data.json (live):
|
|
67
|
-
<pre style={{ margin: '4px 0 0', color: 'inherit' }}>{JSON.stringify(values, null, 2)}</pre>
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
|
-
<label style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
71
|
-
boxSpinSpeed
|
|
72
|
-
<input
|
|
73
|
-
type="range"
|
|
74
|
-
min={-5}
|
|
75
|
-
max={5}
|
|
76
|
-
step={0.1}
|
|
77
|
-
data-testid="tool-slider-boxSpinSpeed"
|
|
78
|
-
// Uncontrolled (defaultValue) on purpose: the authoritative value
|
|
79
|
-
// round-trips through the FILE (write → HMR → useData), which can
|
|
80
|
-
// lag a beat behind the pointer — controlling the thumb from that
|
|
81
|
-
// would make it fight the drag. The readout above is the live view.
|
|
82
|
-
defaultValue={values.boxSpinSpeed}
|
|
83
|
-
// writeData writes the WHOLE file through the editor dev server
|
|
84
|
-
// (only src/data/**/*.data.json is accepted): spread the current
|
|
85
|
-
// values, replace one field. It's async — fire and forget here.
|
|
86
|
-
onChange={(e) =>
|
|
87
|
-
void writeData('src/data/tuning.data.json', {
|
|
88
|
-
...values,
|
|
89
|
-
boxSpinSpeed: Number(e.target.value),
|
|
90
|
-
})
|
|
91
|
-
}
|
|
92
|
-
/>
|
|
93
|
-
<span>{values.boxSpinSpeed}</span>
|
|
94
|
-
</label>
|
|
95
|
-
</div>
|
|
96
|
-
);
|
|
97
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Example INSPECTOR tool — the selection-scoped project-tool pattern
|
|
3
|
-
* (docs/DATA-TOOLS-DESIGN.md §3, W3/W3.1, in the engine checkout). Slightly
|
|
4
|
-
* over-commented on purpose: this file is read far more than it runs.
|
|
5
|
-
*
|
|
6
|
-
* Same one-file physics as the dock example (`example.tool.tsx`), different
|
|
7
|
-
* placement: `placement: 'inspector'` + a `match` predicate. Instead of a
|
|
8
|
-
* bottom-dock tab, the editor renders this as a ⚙-marked SECTION in the
|
|
9
|
-
* Inspector rail whenever the current selection satisfies `match` — select
|
|
10
|
-
* the Box (it has a `DataSpinner`) and it appears under the built-in
|
|
11
|
-
* sections; select the Ground (a mesh, but no `DataSpinner`) or a light and
|
|
12
|
-
* it's gone. It stays live during play mode, so you tune while the game runs.
|
|
13
|
-
*
|
|
14
|
-
* Three things this file demonstrates beyond the dock example:
|
|
15
|
-
*
|
|
16
|
-
* 1. `match(node, adapter)` — the selection predicate, now COMPONENT-based
|
|
17
|
-
* rather than shape-based: it asks `adapter.components?.list(node.id)`
|
|
18
|
-
* for the attached component types instead of guessing from `node.kind`.
|
|
19
|
-
* Keep it cheap and pure, it runs on every inspector render.
|
|
20
|
-
* 2. `useSelection()` (`@engine/react/use-selection`, W3.1) — inside the
|
|
21
|
-
* component, this returns the selected node PLUS its attached components
|
|
22
|
-
* and each one's own authored config (`ComponentsProvider.config`). Here
|
|
23
|
-
* it reads `DataSpinner`'s per-entity `spinScale` — read-only in v1;
|
|
24
|
-
* per-entity WRITE-from-tool is future `useTunable`/inspector-set
|
|
25
|
-
* territory. `useSelection` only works inside an inspector tool (it
|
|
26
|
-
* throws a teaching error from a dock tool — there is no selection there).
|
|
27
|
-
* 3. `@editor/widgets` — the editor's OWN widget kit + design tokens,
|
|
28
|
-
* public for project tools (§3.4 "tools are editor citizens"): panels
|
|
29
|
-
* built from it sit flush with the built-in inspector sections and
|
|
30
|
-
* re-skin with the editor. `NumberInput` here is the exact
|
|
31
|
-
* scrub-or-type field the built-in Transform section uses.
|
|
32
|
-
*
|
|
33
|
-
* The component receives `{ node, nodeId }` as props (see
|
|
34
|
-
* `InspectorToolProps` in `@engine/tools/define-tool`) — unchanged by W3.1;
|
|
35
|
-
* richer data arrives through `useSelection()`, not more props. Like every
|
|
36
|
-
* tool, it's editor-only: `npm run build` never bundles this file.
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
import { NumberInput, THEME } from '@editor/widgets';
|
|
40
|
-
import { useData, writeData } from '@engine/react/use-data';
|
|
41
|
-
import { useSelection } from '@engine/react/use-selection';
|
|
42
|
-
import { defineTool, type InspectorToolProps } from '@engine/tools/define-tool';
|
|
43
|
-
// The same project data handle the game (DataSpinner) and the dock example
|
|
44
|
-
// read — one handle, every surface agrees on the values.
|
|
45
|
-
import { tuning } from '../data/tuning';
|
|
46
|
-
|
|
47
|
-
export const tool = defineTool({
|
|
48
|
-
id: 'spin',
|
|
49
|
-
title: 'Spin tuning',
|
|
50
|
-
placement: 'inspector',
|
|
51
|
-
// Show this section only on an entity with a DataSpinner attached — the
|
|
52
|
-
// component-based idiom (rather than guessing from `node.kind`). `node` is
|
|
53
|
-
// null in the no-selection state — return false to stay out of the panel.
|
|
54
|
-
match: (node, adapter) =>
|
|
55
|
-
node !== null &&
|
|
56
|
-
(adapter.components?.list(node.id) ?? []).some((c) => c.type === 'DataSpinner'),
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
export default function SpinInspector({ node }: InspectorToolProps) {
|
|
60
|
-
// Live values: re-renders on every accepted edit of the data file,
|
|
61
|
-
// whoever made it (this field, the dock tool, the DATA tab, VS Code).
|
|
62
|
-
const values = useData(tuning);
|
|
63
|
-
|
|
64
|
-
// The selected entity's own components + their authored config, live
|
|
65
|
-
// (W3.1) — distinct from `values` above: `tuning` is project-wide feel,
|
|
66
|
-
// `spinScale` here is THIS entity's own per-entity knob.
|
|
67
|
-
const { components } = useSelection();
|
|
68
|
-
const spinner = components.find((c) => c.type === 'DataSpinner');
|
|
69
|
-
const spinScale =
|
|
70
|
-
typeof spinner?.config?.['spinScale'] === 'number'
|
|
71
|
-
? (spinner.config['spinScale'] as number)
|
|
72
|
-
: 1;
|
|
73
|
-
|
|
74
|
-
return (
|
|
75
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 11 }}>
|
|
76
|
-
<div style={{ color: THEME.textMuted }}>
|
|
77
|
-
{node ? `Selected: ${node.label}` : 'Nothing selected'} — boxSpinSpeed drives the Box's
|
|
78
|
-
DataSpinner (src/data/tuning.data.json, live)
|
|
79
|
-
</div>
|
|
80
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
|
81
|
-
<span style={{ color: THEME.textMuted, width: 90 }}>boxSpinSpeed</span>
|
|
82
|
-
{/* The editor's own scrub-or-type number field (@editor/widgets):
|
|
83
|
-
drag horizontally to scrub, click to type. writeData writes the
|
|
84
|
-
whole data file through the dev server; HMR flows it back into
|
|
85
|
-
useData above AND the running game. */}
|
|
86
|
-
<NumberInput
|
|
87
|
-
value={values.boxSpinSpeed}
|
|
88
|
-
step={0.1}
|
|
89
|
-
testId="spin-tool-speed"
|
|
90
|
-
style={{ flex: 1 }}
|
|
91
|
-
onChange={(v) =>
|
|
92
|
-
void writeData('src/data/tuning.data.json', { ...values, boxSpinSpeed: v })
|
|
93
|
-
}
|
|
94
|
-
/>
|
|
95
|
-
</div>
|
|
96
|
-
<div style={{ color: THEME.textMuted }} data-testid="spin-tool-entity-scale">
|
|
97
|
-
this entity's spinScale: {spinScale} ×
|
|
98
|
-
</div>
|
|
99
|
-
</div>
|
|
100
|
-
);
|
|
101
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* L1 — logic layer (`docs/ACCEPTANCE-DRIVER-SPEC.md` §0): pure rules/math in
|
|
3
|
-
* plain Node tests, seconds per run, never through a browser. This exercises
|
|
4
|
-
* the ACTUAL lap-counting rule `DataSpinner` (`src/scripts/components/
|
|
5
|
-
* data-spinner.ts`) ships with — imported from its real module, not
|
|
6
|
-
* reimplemented here — so a regression in the rule fails here first, not in
|
|
7
|
-
* the slower L2 acceptance spec (`tests/acceptance/example.spec.ts`).
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { describe, expect, it } from 'vitest';
|
|
11
|
-
import { advanceLap } from '../../src/scripts/components/spin-lap-math';
|
|
12
|
-
|
|
13
|
-
describe('advanceLap', () => {
|
|
14
|
-
it('accumulates rotation without completing a lap below 2π', () => {
|
|
15
|
-
const result = advanceLap(0, 1);
|
|
16
|
-
expect(result.lapsCompleted).toBe(0);
|
|
17
|
-
expect(result.rotationSinceLap).toBeCloseTo(1, 10);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('completes exactly one lap and carries the remainder', () => {
|
|
21
|
-
const result = advanceLap(0, Math.PI * 2 + 1);
|
|
22
|
-
expect(result.lapsCompleted).toBe(1);
|
|
23
|
-
expect(result.rotationSinceLap).toBeCloseTo(1, 10);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('completes multiple laps in a single large step', () => {
|
|
27
|
-
const result = advanceLap(0, Math.PI * 2 * 3.5);
|
|
28
|
-
expect(result.lapsCompleted).toBe(3);
|
|
29
|
-
expect(result.rotationSinceLap).toBeCloseTo(Math.PI, 10);
|
|
30
|
-
});
|
|
31
|
-
});
|