@vulfram/engine 0.5.6-alpha

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.
Files changed (40) hide show
  1. package/package.json +17 -0
  2. package/src/engine/api.ts +282 -0
  3. package/src/engine/bridge/dispatch.ts +115 -0
  4. package/src/engine/bridge/guards.ts +28 -0
  5. package/src/engine/bridge/protocol.ts +130 -0
  6. package/src/engine/ecs/index.ts +516 -0
  7. package/src/engine/errors.ts +10 -0
  8. package/src/engine/state.ts +135 -0
  9. package/src/engine/systems/command-intent.ts +74 -0
  10. package/src/engine/systems/core-command-builder.ts +265 -0
  11. package/src/engine/systems/diagnostics.ts +42 -0
  12. package/src/engine/systems/index.ts +7 -0
  13. package/src/engine/systems/input-mirror.ts +152 -0
  14. package/src/engine/systems/resource-upload.ts +143 -0
  15. package/src/engine/systems/response-decode.ts +28 -0
  16. package/src/engine/systems/utils.ts +147 -0
  17. package/src/engine/systems/world-lifecycle.ts +164 -0
  18. package/src/engine/world/entities.ts +516 -0
  19. package/src/index.ts +9 -0
  20. package/src/types/cmds/camera.ts +76 -0
  21. package/src/types/cmds/environment.ts +45 -0
  22. package/src/types/cmds/geometry.ts +144 -0
  23. package/src/types/cmds/gizmo.ts +18 -0
  24. package/src/types/cmds/index.ts +231 -0
  25. package/src/types/cmds/light.ts +69 -0
  26. package/src/types/cmds/material.ts +98 -0
  27. package/src/types/cmds/model.ts +59 -0
  28. package/src/types/cmds/shadow.ts +22 -0
  29. package/src/types/cmds/system.ts +15 -0
  30. package/src/types/cmds/texture.ts +63 -0
  31. package/src/types/cmds/window.ts +263 -0
  32. package/src/types/events/gamepad.ts +34 -0
  33. package/src/types/events/index.ts +19 -0
  34. package/src/types/events/keyboard.ts +225 -0
  35. package/src/types/events/pointer.ts +105 -0
  36. package/src/types/events/system.ts +13 -0
  37. package/src/types/events/window.ts +96 -0
  38. package/src/types/index.ts +3 -0
  39. package/src/types/kinds.ts +111 -0
  40. package/tsconfig.json +29 -0
@@ -0,0 +1,516 @@
1
+ import type { ViewPosition } from '../../types/cmds/camera';
2
+ import type { GeometryPrimitiveEntry } from '../../types/cmds/geometry';
3
+ import type { MaterialOptions } from '../../types/cmds/material';
4
+ import type { ShadowConfig } from '../../types/cmds/shadow';
5
+ import type { ForwardAtlasOptions } from '../../types/cmds/texture';
6
+ import type { EnvironmentConfig } from '../../types/cmds/environment';
7
+ import type {
8
+ CameraKind,
9
+ LightKind,
10
+ MaterialKind,
11
+ TextureCreateMode,
12
+ NotificationLevel,
13
+ WindowState,
14
+ CursorGrabMode,
15
+ CursorIcon,
16
+ UserAttentionType,
17
+ } from '../../types/kinds';
18
+ import type { WorldState } from '../state';
19
+
20
+ /**
21
+ * Standard ECS components for Vulfram.
22
+ *
23
+ * Rules:
24
+ * 1. Components are plain data.
25
+ * 2. They are NEVER modified directly by the user during a frame.
26
+ * 3. Changes are requested via Intents.
27
+ */
28
+
29
+ /**
30
+ * Transform component data used to position entities.
31
+ */
32
+ export interface TransformProps {
33
+ position?: [number, number, number];
34
+ rotation?: [number, number, number, number]; // Quaternion
35
+ scale?: [number, number, number];
36
+ layerMask?: number;
37
+ visible?: boolean;
38
+ }
39
+
40
+ /**
41
+ * Fully-resolved transform component stored in the ECS.
42
+ */
43
+ export interface TransformComponent extends Required<
44
+ Omit<TransformProps, never>
45
+ > {
46
+ type: 'Transform';
47
+ }
48
+
49
+ /**
50
+ * Parent link data used for hierarchical transforms.
51
+ */
52
+ export interface ParentProps {
53
+ parentId: number;
54
+ }
55
+
56
+ /**
57
+ * Parent component stored in the ECS.
58
+ */
59
+ export interface ParentComponent extends ParentProps {
60
+ type: 'Parent';
61
+ }
62
+
63
+ /**
64
+ * Camera component configuration.
65
+ */
66
+ export interface CameraProps {
67
+ kind?: CameraKind;
68
+ near?: number;
69
+ far?: number;
70
+ order?: number;
71
+ viewPosition?: ViewPosition;
72
+ orthoScale?: number;
73
+ }
74
+
75
+ /**
76
+ * Camera component stored in the ECS.
77
+ */
78
+ export interface CameraComponent extends Required<
79
+ Omit<CameraProps, 'viewPosition'>
80
+ > {
81
+ type: 'Camera';
82
+ id: number; // Core Camera ID
83
+ viewPosition?: ViewPosition;
84
+ skipUpdate?: boolean; // Internal flag to skip next transform update
85
+ }
86
+
87
+ /**
88
+ * Light component configuration.
89
+ */
90
+ export interface LightProps {
91
+ kind?: LightKind;
92
+ color?: [number, number, number];
93
+ intensity?: number;
94
+ range?: number;
95
+ castShadow?: boolean;
96
+ direction?: [number, number, number];
97
+ spotInnerOuter?: [number, number];
98
+ }
99
+
100
+ /**
101
+ * Light component stored in the ECS.
102
+ */
103
+ export interface LightComponent extends Required<Omit<LightProps, never>> {
104
+ type: 'Light';
105
+ id: number; // Core Light ID
106
+ skipUpdate?: boolean; // Internal flag to skip next transform update
107
+ }
108
+
109
+ /**
110
+ * Model component configuration.
111
+ */
112
+ export interface ModelProps {
113
+ geometryId: number;
114
+ materialId?: number;
115
+ castShadow?: boolean;
116
+ receiveShadow?: boolean;
117
+ }
118
+
119
+ /**
120
+ * Model component stored in the ECS.
121
+ */
122
+ export interface ModelComponent extends Required<
123
+ Omit<ModelProps, 'materialId'>
124
+ > {
125
+ type: 'Model';
126
+ id: number; // Core Model ID
127
+ materialId?: number;
128
+ skipUpdate?: boolean; // Internal flag to skip next transform update
129
+ }
130
+
131
+ /**
132
+ * Tag component configuration.
133
+ */
134
+ export interface TagProps {
135
+ name?: string;
136
+ labels?: string[];
137
+ }
138
+
139
+ /**
140
+ * Tag component stored in the ECS.
141
+ */
142
+ export interface TagComponent {
143
+ type: 'Tag';
144
+ name: string;
145
+ labels: Set<string>;
146
+ }
147
+
148
+ /**
149
+ * InputState: Stores the current input state for a world.
150
+ * This is updated each frame based on incoming events.
151
+ */
152
+ export interface InputStateComponent {
153
+ type: 'InputState';
154
+ keysPressed: Set<number>; // KeyCodes currently held down
155
+ keysJustPressed: Set<number>; // KeyCodes pressed this frame
156
+ keysJustReleased: Set<number>; // KeyCodes released this frame
157
+ mouseButtons: Set<number>; // Mouse buttons currently held
158
+ mousePosition: [number, number]; // Current mouse position
159
+ mouseJustPressed: Set<number>; // Mouse buttons pressed this frame
160
+ mouseJustReleased: Set<number>; // Mouse buttons released this frame
161
+ mouseDelta: [number, number]; // Mouse movement delta this frame
162
+ scrollDelta: [number, number]; // Scroll delta this frame
163
+ }
164
+
165
+ /**
166
+ * WindowState: Stores window events and state.
167
+ */
168
+ export interface WindowStateComponent {
169
+ type: 'WindowState';
170
+ focused: boolean;
171
+ size: [number, number];
172
+ position: [number, number];
173
+ scaleFactor: number;
174
+ closeRequested: boolean;
175
+ resizedThisFrame: boolean;
176
+ movedThisFrame: boolean;
177
+ focusChangedThisFrame: boolean;
178
+ }
179
+
180
+ /**
181
+ * Resource Properties
182
+ */
183
+
184
+ /**
185
+ * Base properties shared by resources.
186
+ */
187
+ export interface BaseResourceProps {
188
+ label?: string;
189
+ }
190
+
191
+ /**
192
+ * Material resource configuration.
193
+ */
194
+ export interface MaterialProps extends BaseResourceProps {
195
+ kind: MaterialKind;
196
+ options: MaterialOptions;
197
+ }
198
+
199
+ /**
200
+ * Options for cube primitive geometry.
201
+ */
202
+ export interface CubeOptions {
203
+ size?: [number, number, number];
204
+ }
205
+
206
+ /**
207
+ * Options for plane primitive geometry.
208
+ */
209
+ export interface PlaneOptions {
210
+ size?: [number, number, number];
211
+ subdivisions?: number;
212
+ }
213
+
214
+ /**
215
+ * Options for sphere primitive geometry.
216
+ */
217
+ export interface SphereOptions {
218
+ radius?: number;
219
+ sectors?: number;
220
+ stacks?: number;
221
+ }
222
+
223
+ /**
224
+ * Options for cylinder primitive geometry.
225
+ */
226
+ export interface CylinderOptions {
227
+ radius?: number;
228
+ height?: number;
229
+ segments?: number;
230
+ }
231
+
232
+ /**
233
+ * Options for torus primitive geometry.
234
+ */
235
+ export interface TorusOptions {
236
+ majorRadius?: number;
237
+ minorRadius?: number;
238
+ radialSegments?: number;
239
+ tubularSegments?: number;
240
+ }
241
+
242
+ /**
243
+ * Options for pyramid primitive geometry.
244
+ */
245
+ export interface PyramidOptions {
246
+ size?: [number, number, number];
247
+ subdivisions?: number;
248
+ }
249
+
250
+ /**
251
+ * Geometry resource configuration (primitive or custom).
252
+ */
253
+ export type GeometryProps = BaseResourceProps &
254
+ (
255
+ | {
256
+ type: 'custom';
257
+ entries: GeometryPrimitiveEntry[];
258
+ }
259
+ | ({ type: 'primitive' } & (
260
+ | { shape: 'cube'; options?: CubeOptions }
261
+ | { shape: 'plane'; options?: PlaneOptions }
262
+ | { shape: 'sphere'; options?: SphereOptions }
263
+ | { shape: 'cylinder'; options?: CylinderOptions }
264
+ | { shape: 'torus'; options?: TorusOptions }
265
+ | { shape: 'pyramid'; options?: PyramidOptions }
266
+ ))
267
+ );
268
+
269
+ /**
270
+ * Texture resource configuration.
271
+ */
272
+ export interface TextureProps extends BaseResourceProps {
273
+ srgb?: boolean;
274
+ mode?: TextureCreateMode;
275
+ atlasOptions?: ForwardAtlasOptions;
276
+ source:
277
+ | { type: 'buffer'; bufferId: number }
278
+ | { type: 'color'; color: [number, number, number, number] };
279
+ }
280
+
281
+ /**
282
+ * Union of all built-in component types.
283
+ */
284
+ export type Component =
285
+ | TransformComponent
286
+ | ParentComponent
287
+ | CameraComponent
288
+ | LightComponent
289
+ | ModelComponent
290
+ | TagComponent
291
+ | InputStateComponent
292
+ | WindowStateComponent
293
+ | CustomComponent;
294
+
295
+ /**
296
+ * Custom components are dynamic and registered via schema.
297
+ */
298
+ /**
299
+ * Custom component payload stored in the ECS.
300
+ */
301
+ export interface CustomComponent {
302
+ type: string;
303
+ data: Record<string, unknown>;
304
+ }
305
+
306
+ /**
307
+ * String literal type for component identifiers.
308
+ */
309
+ export type ComponentType = Component['type'];
310
+
311
+ /**
312
+ * Extension Schemas
313
+ */
314
+
315
+ /**
316
+ * Supported property types for custom schemas.
317
+ */
318
+ export type PropertyType =
319
+ | 'number'
320
+ | 'string'
321
+ | 'boolean'
322
+ | 'vec2'
323
+ | 'vec3'
324
+ | 'vec4'
325
+ | 'quat'
326
+ | 'entity'
327
+ | 'resource'
328
+ | 'array'
329
+ | 'object';
330
+
331
+ /**
332
+ * Schema entry describing a custom component field.
333
+ */
334
+ export interface SchemaProperty {
335
+ type: PropertyType;
336
+ default?: unknown;
337
+ optional?: boolean;
338
+ }
339
+
340
+ /**
341
+ * Schema definition for a custom component.
342
+ */
343
+ export interface ComponentSchema {
344
+ [key: string]: SchemaProperty;
345
+ }
346
+
347
+ /**
348
+ * Window update properties.
349
+ */
350
+ export interface WindowProps {
351
+ title?: string;
352
+ position?: [number, number];
353
+ size?: [number, number];
354
+ state?: WindowState;
355
+ resizable?: boolean;
356
+ decorations?: boolean;
357
+ cursorVisible?: boolean;
358
+ cursorGrab?: CursorGrabMode;
359
+ icon?: string;
360
+ cursorIcon?: CursorIcon;
361
+ }
362
+
363
+ /**
364
+ * Window creation properties.
365
+ */
366
+ export interface CreateWindowProps {
367
+ title: string;
368
+ size: [number, number];
369
+ position: [number, number];
370
+ canvasId?: string;
371
+ borderless?: boolean;
372
+ resizable?: boolean;
373
+ transparent?: boolean;
374
+ initialState?: WindowState;
375
+ }
376
+
377
+ /**
378
+ * Intents:
379
+ * Requests to change the state of the World.
380
+ * Systems process Intents and generate Core Commands.
381
+ */
382
+
383
+ export type Intent =
384
+ | { type: 'create-window'; props: CreateWindowProps }
385
+ | { type: 'close-window' }
386
+ | { type: 'update-window'; props: WindowProps }
387
+ | { type: 'request-attention'; attentionType?: UserAttentionType }
388
+ | { type: 'focus-window' }
389
+ | { type: 'configure-environment'; config: EnvironmentConfig }
390
+ | {
391
+ type: 'request-resource-list';
392
+ resourceType:
393
+ | 'model'
394
+ | 'material'
395
+ | 'texture'
396
+ | 'geometry'
397
+ | 'light'
398
+ | 'camera';
399
+ }
400
+ | { type: 'create-entity'; worldId: number; entityId: number }
401
+ | { type: 'remove-entity'; entityId: number }
402
+ | {
403
+ type: 'update-transform';
404
+ entityId: number;
405
+ props: TransformProps;
406
+ }
407
+ | {
408
+ type: 'attach-camera';
409
+ entityId: number;
410
+ props: CameraProps;
411
+ }
412
+ | {
413
+ type: 'attach-model';
414
+ entityId: number;
415
+ props: ModelProps;
416
+ }
417
+ | {
418
+ type: 'attach-light';
419
+ entityId: number;
420
+ props: LightProps;
421
+ }
422
+ | {
423
+ type: 'attach-tag';
424
+ entityId: number;
425
+ props: TagProps;
426
+ }
427
+ | { type: 'create-material'; resourceId: number; props: MaterialProps }
428
+ | { type: 'create-geometry'; resourceId: number; props: GeometryProps }
429
+ | { type: 'create-texture'; resourceId: number; props: TextureProps }
430
+ | { type: 'dispose-material'; resourceId: number }
431
+ | { type: 'dispose-texture'; resourceId: number }
432
+ | { type: 'dispose-geometry'; resourceId: number }
433
+ | { type: 'detach-component'; entityId: number; componentType: ComponentType }
434
+ | { type: 'set-parent'; entityId: number; parentId: number | null }
435
+ | {
436
+ type: 'send-notification';
437
+ level: NotificationLevel;
438
+ title: string;
439
+ message: string;
440
+ }
441
+ | { type: 'configure-shadows'; config: ShadowConfig }
442
+ | {
443
+ type: 'gizmo-draw-line';
444
+ start: [number, number, number];
445
+ end: [number, number, number];
446
+ color: [number, number, number, number];
447
+ }
448
+ | {
449
+ type: 'gizmo-draw-aabb';
450
+ min: [number, number, number];
451
+ max: [number, number, number];
452
+ color: [number, number, number, number];
453
+ }
454
+ | {
455
+ type: 'custom';
456
+ name: string;
457
+ data: Record<string, unknown>;
458
+ };
459
+
460
+ /**
461
+ * Internal World Events:
462
+ * Events emitted by systems or the engine to be consumed by other systems.
463
+ */
464
+ export type WorldEvent =
465
+ | { type: 'entity-created'; entityId: number }
466
+ | { type: 'entity-destroyed'; entityId: number }
467
+ | { type: 'component-added'; entityId: number; componentType: ComponentType }
468
+ | {
469
+ type: 'component-removed';
470
+ entityId: number;
471
+ componentType: ComponentType;
472
+ };
473
+
474
+ /**
475
+ * System Types
476
+ */
477
+
478
+ /**
479
+ * Pipeline stage at which a system runs.
480
+ */
481
+ export type SystemStep = 'input' | 'update' | 'preRender' | 'postRender';
482
+
483
+ /**
484
+ * System execution context provided each frame.
485
+ */
486
+ export interface SystemContext {
487
+ dt: number; // Delta time in seconds
488
+ time: number; // Total time in seconds
489
+ worldId: number;
490
+ }
491
+
492
+ /**
493
+ * A System is a function that processes the WorldState.
494
+ * It can emit new Intents, generate Core Commands, or interpret Events.
495
+ */
496
+ /**
497
+ * System function signature.
498
+ */
499
+ export type System = (state: WorldState, context: SystemContext) => void;
500
+
501
+ /**
502
+ * Registration Hooks
503
+ */
504
+
505
+ /**
506
+ * Registry of components and systems.
507
+ */
508
+ export interface EngineRegistry {
509
+ components: Map<string, ComponentSchema>;
510
+ systems: {
511
+ input: System[];
512
+ update: System[];
513
+ preRender: System[];
514
+ postRender: System[];
515
+ };
516
+ }
@@ -0,0 +1,10 @@
1
+ /** Error type thrown by the engine runtime. */
2
+ export class EngineError extends Error {
3
+ code: string;
4
+
5
+ constructor(code: string, message: string) {
6
+ super(message);
7
+ this.code = code;
8
+ this.name = 'EngineError';
9
+ }
10
+ }
@@ -0,0 +1,135 @@
1
+ import type { EngineTransport } from '@vulfram/transport-types';
2
+ import type { CommandResponseEnvelope, EngineCmdEnvelope } from '../types/cmds';
3
+ import type { EngineEvent } from '../types/events';
4
+ import type {
5
+ Component,
6
+ ComponentType,
7
+ EngineRegistry,
8
+ Intent,
9
+ WorldEvent,
10
+ } from './ecs';
11
+
12
+ type EngineStatus = 'uninitialized' | 'initialized' | 'disposed';
13
+
14
+ /**
15
+ * Component storage indexed by Entity ID.
16
+ */
17
+ export type ComponentStore = Map<ComponentType, Component>;
18
+
19
+ type ComponentIdCounters = {
20
+ camera: number;
21
+ light: number;
22
+ model: number;
23
+ };
24
+
25
+ type ResourceIdCounters = {
26
+ material: number;
27
+ geometry: number;
28
+ texture: number;
29
+ };
30
+
31
+ export type WorldState = {
32
+ windowId: number;
33
+ entities: Set<number>;
34
+ /**
35
+ * Components stored by entityId -> componentType -> ComponentData
36
+ */
37
+ components: Map<number, ComponentStore>;
38
+ nextCoreId: number;
39
+ systems: string[];
40
+ /**
41
+ * Pending intents to be processed by systems in the next tick.
42
+ */
43
+ pendingIntents: Intent[];
44
+ /**
45
+ * Internal world events for system-to-system communication.
46
+ */
47
+ internalEvents: WorldEvent[];
48
+ /**
49
+ * Pending commands generated by this world in the current frame.
50
+ * Will be moved to the engine's global batch during the tick.
51
+ */
52
+ pendingCommands: EngineCmdEnvelope[];
53
+ /**
54
+ * Events routed to this world in the current frame.
55
+ */
56
+ inboundEvents: EngineEvent[];
57
+ /**
58
+ * Responses routed to this world in the current frame.
59
+ */
60
+ inboundResponses: CommandResponseEnvelope[];
61
+ };
62
+
63
+ export type EngineState = {
64
+ status: EngineStatus;
65
+ transport: EngineTransport | null;
66
+ worlds: Map<number, WorldState>;
67
+ nextEntityId: number;
68
+ nextCommandId: number;
69
+ /**
70
+ * Global command batch for the current frame.
71
+ */
72
+ commandBatch: EngineCmdEnvelope[];
73
+ /**
74
+ * Maps command IDs to the world ID that created them.
75
+ * This is used to route responses back to the correct world.
76
+ */
77
+ commandTracker: Map<number, number>;
78
+ /**
79
+ * Registry for custom components and systems.
80
+ */
81
+ registry: EngineRegistry;
82
+ /**
83
+ * Engine clock information.
84
+ */
85
+ clock: {
86
+ lastTime: number;
87
+ lastDelta: number;
88
+ frameCount: number;
89
+ };
90
+ /**
91
+ * Internal flags for protection and debugging.
92
+ */
93
+ flags: {
94
+ isExecutingSystems: boolean;
95
+ debugEnabled: boolean;
96
+ };
97
+ };
98
+
99
+ export const REQUIRED_SYSTEMS = [
100
+ 'InputMirrorSystem',
101
+ 'CommandIntentSystem',
102
+ 'ResourceUploadSystem',
103
+ 'CoreCommandBuilderSystem',
104
+ 'ResponseDecodeSystem',
105
+ 'WorldLifecycleSystem',
106
+ 'DiagnosticsSystem',
107
+ ];
108
+
109
+ export const engineState: EngineState = {
110
+ status: 'uninitialized',
111
+ transport: null,
112
+ worlds: new Map(),
113
+ nextEntityId: 1,
114
+ nextCommandId: 1,
115
+ commandBatch: [],
116
+ commandTracker: new Map(),
117
+ registry: {
118
+ components: new Map(),
119
+ systems: {
120
+ input: [],
121
+ update: [],
122
+ preRender: [],
123
+ postRender: [],
124
+ },
125
+ },
126
+ clock: {
127
+ lastTime: 0,
128
+ lastDelta: 0,
129
+ frameCount: 0,
130
+ },
131
+ flags: {
132
+ isExecutingSystems: false,
133
+ debugEnabled: false,
134
+ },
135
+ };