@combos-fun/engine 0.0.5 → 0.0.7
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/README.md +25 -1
- package/agent-skill.md +451 -0
- package/combos-plugin.json +23 -0
- package/dist/engine.cjs.js +7 -0
- package/dist/engine.cjs.js.map +1 -1
- package/dist/engine.cjs.prod.js +1 -1
- package/dist/engine.d.ts +19 -3
- package/dist/engine.esm.js +6 -1
- package/dist/engine.esm.js.map +1 -1
- package/package.json +17 -3
package/README.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
1
|
# @combos-fun/engine
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
@combos-fun/engine — part of the Combos Fun engine monorepo.
|
|
4
|
+
|
|
5
|
+
Keywords: `engine`, `ecs`, `kernel`, `runtime`, `game`, `system`, `component`.
|
|
6
|
+
|
|
7
|
+
## Documentation
|
|
8
|
+
|
|
9
|
+
- Per-package agent / developer notes: [`agent-skill.md`](./agent-skill.md)
|
|
10
|
+
- Machine-readable manifest: [`combos-plugin.json`](./combos-plugin.json)
|
|
11
|
+
(validated against `schemas/combos-plugin.schema.json` in the repo
|
|
12
|
+
root)
|
|
13
|
+
- Entry skill (single source of truth for AI agents working with this
|
|
14
|
+
monorepo): `skills/combos-engine-development/SKILL.md`
|
|
15
|
+
|
|
16
|
+
When consumed from npm, the manifest and agent notes are exposed as
|
|
17
|
+
stable subpaths:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import manifest from '@combos-fun/engine/plugin-manifest';
|
|
21
|
+
// or fetch the markdown directly:
|
|
22
|
+
// require.resolve('@combos-fun/engine/agent-skill')
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## License
|
|
26
|
+
|
|
27
|
+
Internal workspace package, part of the Combos Fun engine monorepo.
|
package/agent-skill.md
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
# `@combos-fun/engine` — Agent notes
|
|
2
|
+
|
|
3
|
+
ECS microkernel for Combos Fun. This file is the canonical knowledge source for
|
|
4
|
+
the engine and the standard for authoring any new `@combos-fun/plugin-*`.
|
|
5
|
+
|
|
6
|
+
## When to read
|
|
7
|
+
|
|
8
|
+
Read this every time before working on a Combos Fun project. The entry skill
|
|
9
|
+
loads this file as part of "Core" packages, regardless of 2D / 3D mode.
|
|
10
|
+
|
|
11
|
+
## Microkernel model
|
|
12
|
+
|
|
13
|
+
- **Kernel** `@combos-fun/engine`: `Game`, `Scene`, `GameObject`, `Component`,
|
|
14
|
+
`System`, `Transform`, `resource`, `decorators`. **No** built-in
|
|
15
|
+
rendering / physics / audio.
|
|
16
|
+
- **Extension**: `game.addSystem(new XxxSystem(...))` then
|
|
17
|
+
`gameObject.addComponent(new Xxx(...))`.
|
|
18
|
+
- **Observer**: `@decorators.componentObserver({ Name: ['prop'] })` on a
|
|
19
|
+
System; drain `this.componentObserver.clear()` in `update()` and switch on
|
|
20
|
+
`OBSERVER_TYPE.ADD | CHANGE | REMOVE`.
|
|
21
|
+
- **Frame order**:
|
|
22
|
+
`Component.update` → `Component.lateUpdate` → `System.update` →
|
|
23
|
+
`System.lateUpdate`. `start()` fires inline on the first `update` tick.
|
|
24
|
+
|
|
25
|
+
## Public API
|
|
26
|
+
|
|
27
|
+
### Values
|
|
28
|
+
|
|
29
|
+
`Game`, `Scene`, `GameObject`, `Component`, `System`, `Transform`,
|
|
30
|
+
`resource`, `resourceLoader`, `decorators`, `IDEProp`,
|
|
31
|
+
`componentObserver`, `LOAD_EVENT`, `RESOURCE_TYPE`, `OBSERVER_TYPE`,
|
|
32
|
+
`LOAD_SCENE_MODE`, `RESOURCE_TYPE_STRATEGY`, `version`,
|
|
33
|
+
`COMBOS_GAME_PLUGIN_INIT_SUCCESS`, `postParentPluginInitSuccess`.
|
|
34
|
+
|
|
35
|
+
### Types
|
|
36
|
+
|
|
37
|
+
`GameParams`, `PluginStruct`, `TransformParams`, `ComponentChanged`,
|
|
38
|
+
`UpdateParams`, `ComponentParams`, `ObserverInfo`, `PureObserverInfo`,
|
|
39
|
+
`ResourceBase`, `SystemConstructor`,
|
|
40
|
+
`CombosGamePluginInitSuccessMessage`.
|
|
41
|
+
|
|
42
|
+
### `GameParams`
|
|
43
|
+
|
|
44
|
+
| Field | Type | Default | Notes |
|
|
45
|
+
|-------|------|---------|-------|
|
|
46
|
+
| `systems` | `System[]` | `[]` | Bootstrapped async in registration order |
|
|
47
|
+
| `frameRate` | `number` | `60` | |
|
|
48
|
+
| `autoStart` | `boolean` | `true` | |
|
|
49
|
+
| `needScene` | `boolean` | `true` | Auto-creates `Scene('scene')` |
|
|
50
|
+
| `onSystemsBootstrapComplete` | `(game, error?) => void` | — | After all systems init |
|
|
51
|
+
| `pluginInitNotifyTargetOrigin` | `string` | `'*'` | postMessage target |
|
|
52
|
+
|
|
53
|
+
### `Game` methods
|
|
54
|
+
|
|
55
|
+
`addSystem(system)`, `removeSystem(system | class | string)` (calls
|
|
56
|
+
`system.destroy()` internally), `getSystem(class | string)`,
|
|
57
|
+
`loadScene({ scene, mode?, params? })`
|
|
58
|
+
(`LOAD_SCENE_MODE.SINGLE | MULTI_CANVAS`), `start()`, `pause()`,
|
|
59
|
+
`resume()`, `destroy()`.
|
|
60
|
+
|
|
61
|
+
### `resource` singleton
|
|
62
|
+
|
|
63
|
+
| Method | Notes |
|
|
64
|
+
|--------|-------|
|
|
65
|
+
| `addResource(resources[])` | Register (no load) |
|
|
66
|
+
| `preload()` | Load all `preload: true` |
|
|
67
|
+
| `loadConfig(resources[])` | `addResource` + `preload` |
|
|
68
|
+
| `loadSingle(resource): Promise` | Add + load one |
|
|
69
|
+
| `getResource(name): Promise` | Get loaded |
|
|
70
|
+
| `destroy(name): Promise` | Destroy one |
|
|
71
|
+
| `registerResourceType(type, value?)` | Custom type |
|
|
72
|
+
| `registerInstance(type, cb)` / `registerDestroy(type, cb)` | Factory / destructor |
|
|
73
|
+
|
|
74
|
+
Fields: `timeout` (6000ms), `resourcesMap`, `progress`.
|
|
75
|
+
|
|
76
|
+
## ECS hard rules (mandatory)
|
|
77
|
+
|
|
78
|
+
These rules apply to every code change. Violations must be fixed before the
|
|
79
|
+
change is considered complete.
|
|
80
|
+
|
|
81
|
+
### Rule 1: All entity state lives in Components
|
|
82
|
+
|
|
83
|
+
Any data belonging to a game entity must be a field on a `Component`
|
|
84
|
+
attached to a `GameObject`. No module-scope variables, closure captures,
|
|
85
|
+
or shadow stores holding entity state. **Test:** if you `destroy()` a
|
|
86
|
+
`GameObject`, does all its state disappear? If anything survives in a
|
|
87
|
+
closure or module variable, violation.
|
|
88
|
+
|
|
89
|
+
### Rule 2: All behaviour logic lives in Component / System hooks
|
|
90
|
+
|
|
91
|
+
Code that runs per-frame, reacts to events, or mutates entity state must
|
|
92
|
+
live in `Component` hooks (`init` / `awake` / `start` / `update` /
|
|
93
|
+
`lateUpdate` / `onPause` / `onResume` / `onDestroy`) or `System` hooks
|
|
94
|
+
(same set + `componentObserver` in `update`). No event callbacks in
|
|
95
|
+
bootstrap, no `setInterval` outside Components, no `game.ticker.add` for
|
|
96
|
+
game logic.
|
|
97
|
+
|
|
98
|
+
### Rule 3: Bootstrap code only wires, never implements
|
|
99
|
+
|
|
100
|
+
The entry file may: register resources, create `Game` with `systems`,
|
|
101
|
+
create `GameObject`s + attach `Component`s, add to scene, call
|
|
102
|
+
`game.start()`. The entry file must **not**: define event handlers,
|
|
103
|
+
call component methods (`spriteAnim.play()`), hold component references
|
|
104
|
+
for later use, or contain `if` / `switch` / loop game logic. When you
|
|
105
|
+
need interaction logic, create a custom Component whose `start()` wires
|
|
106
|
+
it and whose `onDestroy()` cleans it up.
|
|
107
|
+
|
|
108
|
+
### Rule 4: Single concern per Component, cross-entity logic in Systems
|
|
109
|
+
|
|
110
|
+
A Component holds data + self-contained behaviour for one concern on one
|
|
111
|
+
entity. A System holds cross-entity logic reacting to component changes.
|
|
112
|
+
Do not put multi-entity coordination inside a Component.
|
|
113
|
+
|
|
114
|
+
### Rule 5: No direct Pixi / Three.js / DOM manipulation outside Renderer pattern
|
|
115
|
+
|
|
116
|
+
2D display objects must come through `plugin-renderer-*` systems or a
|
|
117
|
+
custom `Renderer` subclass registered via `rendererManager.register(this)`.
|
|
118
|
+
3D objects must come through `plugin-renderer-3d-*` systems or a custom
|
|
119
|
+
`Renderer3D` subclass. No raw `new PIXI.Sprite(...)` / `new THREE.Mesh(...)`
|
|
120
|
+
in bootstrap or plain Components.
|
|
121
|
+
|
|
122
|
+
### Rule 6: Component configuration via params, not imperative calls
|
|
123
|
+
|
|
124
|
+
Configure Components through constructor params (e.g. `autoPlay: true`).
|
|
125
|
+
If behaviour must be triggered at runtime, do it from another Component's
|
|
126
|
+
lifecycle hook, not from bootstrap code.
|
|
127
|
+
|
|
128
|
+
## Decision flowchart
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
Runs only once at startup to wire things?
|
|
132
|
+
├── YES: Only creates GameObjects/Components/Systems/resources?
|
|
133
|
+
│ ├── YES → bootstrap / entry file
|
|
134
|
+
│ └── NO (contains logic) → Component or System
|
|
135
|
+
└── NO: Runs per-frame or reacts to events?
|
|
136
|
+
├── Single-entity concern → Component
|
|
137
|
+
├── Cross-entity concern → System with @componentObserver
|
|
138
|
+
└── Rendering integration → Renderer (2D) / Renderer3D (3D) subclass
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## ECS compliance checklist
|
|
142
|
+
|
|
143
|
+
Run after every change:
|
|
144
|
+
|
|
145
|
+
- [ ] No game logic in entry file — only resource registration, `Game` /
|
|
146
|
+
`System` creation, `GameObject` + `Component` wiring, scene setup
|
|
147
|
+
- [ ] No retained component references — entry file does not store
|
|
148
|
+
`addComponent()` returns for later use
|
|
149
|
+
- [ ] No anonymous event handlers — all `.on(...)` /
|
|
150
|
+
`addEventListener(...)` inside Component lifecycle hooks with cleanup
|
|
151
|
+
in `onDestroy`
|
|
152
|
+
- [ ] No module-scope mutable state holding entity data
|
|
153
|
+
- [ ] All entity state accessible via `gameObject.getComponent(...)`
|
|
154
|
+
- [ ] All per-frame logic in Component / System `update` / `lateUpdate`
|
|
155
|
+
- [ ] Custom behaviour = named Component class with `static componentName`
|
|
156
|
+
- [ ] Every event subscription has cleanup in `onDestroy`
|
|
157
|
+
- [ ] Systems use `@decorators.componentObserver` and drain in `update()`
|
|
158
|
+
- [ ] No direct Pixi / Three.js manipulation outside `Renderer` /
|
|
159
|
+
`Renderer3D` subclasses
|
|
160
|
+
|
|
161
|
+
## Inventory (mandatory after every task)
|
|
162
|
+
|
|
163
|
+
After every development task, write or update three inventory tables in the
|
|
164
|
+
**project memory**:
|
|
165
|
+
|
|
166
|
+
**Component inventory** — for each custom Component:
|
|
167
|
+
|
|
168
|
+
| Field | Description |
|
|
169
|
+
|-------|-------------|
|
|
170
|
+
| `componentName` | Static name string |
|
|
171
|
+
| Purpose | One-sentence description |
|
|
172
|
+
| Key params | Constructor params that affect behaviour |
|
|
173
|
+
| Lifecycle hooks | Which of `init` / `awake` / `start` / `update` / `lateUpdate` / `onDestroy` it implements |
|
|
174
|
+
|
|
175
|
+
**GameObject inventory** — for each GameObject:
|
|
176
|
+
|
|
177
|
+
| Field | Description |
|
|
178
|
+
|-------|-------------|
|
|
179
|
+
| Name | Identifier or variable name |
|
|
180
|
+
| Parent | Parent GameObject or scene root |
|
|
181
|
+
| Components | Attached Components |
|
|
182
|
+
| Purpose | Role in the game |
|
|
183
|
+
|
|
184
|
+
**System inventory** — for each System:
|
|
185
|
+
|
|
186
|
+
| Field | Description |
|
|
187
|
+
|-------|-------------|
|
|
188
|
+
| Class name | System class |
|
|
189
|
+
| Order | Registration order in `systems` array |
|
|
190
|
+
| What it owns | Components observed / coordinated |
|
|
191
|
+
| Purpose | One-sentence description |
|
|
192
|
+
|
|
193
|
+
Skipping inventory is treated with the same weight as the ECS hard rules.
|
|
194
|
+
|
|
195
|
+
## Plugin authoring (the standard for `@combos-fun/plugin-*`)
|
|
196
|
+
|
|
197
|
+
A plugin is `Component(s)` + optional `System(s)` + optional `Renderer` /
|
|
198
|
+
`Renderer3D` subclass, shipped as an npm package or local package under the
|
|
199
|
+
`@combos-fun/` namespace.
|
|
200
|
+
|
|
201
|
+
### Required package layout
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
packages/<plugin-name>/
|
|
205
|
+
package.json
|
|
206
|
+
combos-plugin.json # machine-readable manifest (see schemas/combos-plugin.schema.json)
|
|
207
|
+
agent-skill.md # per-package agent notes
|
|
208
|
+
README.md # human-facing docs
|
|
209
|
+
index.js # CJS entry
|
|
210
|
+
lib/ # TypeScript sources
|
|
211
|
+
dist/ # built CJS / ESM / .d.ts (created by build-package.mjs)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Required `package.json` fields
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"name": "@combos-fun/plugin-xxx",
|
|
219
|
+
"files": ["dist", "index.js", "agent-skill.md", "combos-plugin.json"],
|
|
220
|
+
"exports": {
|
|
221
|
+
".": "./dist/plugin-xxx.esm.js",
|
|
222
|
+
"./plugin-manifest": "./combos-plugin.json",
|
|
223
|
+
"./agent-skill": "./agent-skill.md"
|
|
224
|
+
},
|
|
225
|
+
"combos": {
|
|
226
|
+
"pluginManifest": "./combos-plugin.json"
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
The `./plugin-manifest` and `./agent-skill` subpaths are part of the public
|
|
232
|
+
contract. External Agents resolve documentation via these stable subpaths.
|
|
233
|
+
Renaming the underlying files without updating `exports` is a breaking
|
|
234
|
+
change.
|
|
235
|
+
|
|
236
|
+
### Component subclass
|
|
237
|
+
|
|
238
|
+
Subclass `Component` from `@combos-fun/engine`. Set
|
|
239
|
+
`static componentName = 'MyFeature'`.
|
|
240
|
+
|
|
241
|
+
Lifecycle (all optional):
|
|
242
|
+
|
|
243
|
+
| Hook | When |
|
|
244
|
+
|------|------|
|
|
245
|
+
| `init(params?)` | During construction |
|
|
246
|
+
| `awake()` | When added to a GameObject |
|
|
247
|
+
| `start()` | Inline on first `update` tick (same frame as first `update`) |
|
|
248
|
+
| `update(frame)` | Every frame (`frame.deltaTime` in ms) |
|
|
249
|
+
| `lateUpdate(frame)` | After all components' `update`, before any `System.update` |
|
|
250
|
+
| `onPause()` / `onResume()` | Game pause / resume |
|
|
251
|
+
| `onDestroy()` | Component or GameObject destroyed |
|
|
252
|
+
|
|
253
|
+
`UpdateParams`: `deltaTime`, `frameCount`, `time`, `currentTime`, `fps`.
|
|
254
|
+
|
|
255
|
+
### System subclass
|
|
256
|
+
|
|
257
|
+
Subclass `System`. Set `static systemName = 'MyFeatureSystem'`.
|
|
258
|
+
|
|
259
|
+
Decorate with `@decorators.componentObserver({ MyFeature: ['power'] })`:
|
|
260
|
+
|
|
261
|
+
- `[]` (empty array) → ADD / REMOVE only
|
|
262
|
+
- `['prop']` → CHANGE on set
|
|
263
|
+
- `{ prop: ['a','b'], deep: true }` → deep change watching
|
|
264
|
+
|
|
265
|
+
In `update()`:
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
const changes = this.componentObserver.clear();
|
|
269
|
+
for (const c of changes) {
|
|
270
|
+
switch (c.type) {
|
|
271
|
+
case OBSERVER_TYPE.ADD: /* c.component, c.gameObject */ break;
|
|
272
|
+
case OBSERVER_TYPE.CHANGE: /* c.prop?: { deep, prop: string[] } */ break;
|
|
273
|
+
case OBSERVER_TYPE.REMOVE: break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
System lifecycle is the same as Component (`init` can be async). `init`
|
|
279
|
+
receives constructor params; `this.game` is available.
|
|
280
|
+
|
|
281
|
+
`System.destroy()` nulls internals and calls `onDestroy()` but **does not**
|
|
282
|
+
remove the system from Game. Always call `game.removeSystem(system)`, which
|
|
283
|
+
calls `destroy()` internally.
|
|
284
|
+
|
|
285
|
+
### `ComponentChanged` shape
|
|
286
|
+
|
|
287
|
+
`type: OBSERVER_TYPE`, `component`, `componentName`, `gameObject`,
|
|
288
|
+
`prop?: { deep, prop: string[] }`.
|
|
289
|
+
|
|
290
|
+
### 2D Pixi `Renderer` subclass (`@combos-fun/plugin-renderer`)
|
|
291
|
+
|
|
292
|
+
Extend `Renderer` from `plugin-renderer` with `@decorators.componentObserver`.
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
class MyRenderer extends Renderer {
|
|
296
|
+
init() {
|
|
297
|
+
this.rendererSystem = this.game.getSystem(RendererSystem);
|
|
298
|
+
this.rendererSystem.rendererManager.register(this);
|
|
299
|
+
}
|
|
300
|
+
componentChanged(changed: ComponentChanged) {
|
|
301
|
+
const container = this.rendererSystem.containerManager.getContainer(
|
|
302
|
+
changed.gameObject.id,
|
|
303
|
+
);
|
|
304
|
+
switch (changed.type) {
|
|
305
|
+
case OBSERVER_TYPE.ADD: /* create pixi object, container.addChild(obj) */ break;
|
|
306
|
+
case OBSERVER_TYPE.CHANGE: /* mutate */ break;
|
|
307
|
+
case OBSERVER_TYPE.REMOVE: /* destroy + container.removeChild */ break;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
rendererUpdate(gameObject) {
|
|
311
|
+
/* per-frame sync */
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
`RendererSystem` must be added before any system calling
|
|
317
|
+
`getSystem(RendererSystem)` in `init`.
|
|
318
|
+
|
|
319
|
+
### 3D Three.js `Renderer3D` subclass (`@combos-fun/plugin-renderer-3d`)
|
|
320
|
+
|
|
321
|
+
Extend `Renderer3D` with `@decorators.componentObserver`.
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
class MyRenderer3D extends Renderer3D {
|
|
325
|
+
init() {
|
|
326
|
+
this.rendererSystem = this.game.getSystem(Renderer3DSystem);
|
|
327
|
+
this.rendererSystem.rendererManager.register(this);
|
|
328
|
+
}
|
|
329
|
+
componentChanged(changed) {
|
|
330
|
+
const scene = this.threeContext.scene;
|
|
331
|
+
/* create / mutate / dispose THREE.Object3D */
|
|
332
|
+
}
|
|
333
|
+
rendererUpdate(gameObject) { /* per-frame */ }
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
`Renderer3DSystem` must be added before any system calling
|
|
338
|
+
`getSystem(Renderer3DSystem)` in `init`.
|
|
339
|
+
|
|
340
|
+
**Async loading pattern** (3D only): use the inherited `increaseAsyncId(id)`
|
|
341
|
+
before async work and `validateAsyncId(id, asyncId)` after each `await` to
|
|
342
|
+
cancel stale operations when the component is removed mid-load.
|
|
343
|
+
|
|
344
|
+
### `combos-plugin.json` (manifest)
|
|
345
|
+
|
|
346
|
+
Validate against `schemas/combos-plugin.schema.json`. Required fields:
|
|
347
|
+
`name`, `pluginId`, `category`, `dimension`, `agentSkill`. For non-core
|
|
348
|
+
packages, `category` ∈ {`rendering`, `physics`, `audio`, `input`, `ui`,
|
|
349
|
+
`a11y`, `animation`, `devtool`, `other`} and `dimension` ∈ {`2d`, `3d`,
|
|
350
|
+
`shared`}.
|
|
351
|
+
|
|
352
|
+
### `agent-skill.md` (per-plugin notes)
|
|
353
|
+
|
|
354
|
+
Use this template:
|
|
355
|
+
|
|
356
|
+
```md
|
|
357
|
+
# <plugin-name> — Agent notes
|
|
358
|
+
|
|
359
|
+
## When to read
|
|
360
|
+
<!-- which tasks should load this -->
|
|
361
|
+
|
|
362
|
+
## Public API
|
|
363
|
+
<!-- exported components / systems / params -->
|
|
364
|
+
|
|
365
|
+
## Required setup
|
|
366
|
+
<!-- system registration order, dependencies -->
|
|
367
|
+
|
|
368
|
+
## Runtime behaviour
|
|
369
|
+
<!-- lifecycle interactions, frame-order specifics -->
|
|
370
|
+
|
|
371
|
+
## Common pitfalls
|
|
372
|
+
<!-- blank canvas, missing systems, async stale, etc -->
|
|
373
|
+
|
|
374
|
+
## Minimal example
|
|
375
|
+
<!-- shortest copy-pasteable snippet -->
|
|
376
|
+
|
|
377
|
+
## Verification
|
|
378
|
+
<!-- how to run / test the plugin after changes -->
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Build & publish requirements
|
|
382
|
+
|
|
383
|
+
- Build: CJS / ESM + `.d.ts` to `dist/` via
|
|
384
|
+
`node ../../scripts/build-package.mjs`.
|
|
385
|
+
- Peer / runtime deps: `@combos-fun/engine`, optionally `plugin-renderer` +
|
|
386
|
+
`pixi.js` (for 2D renderer plugins) or `plugin-renderer-3d` + `three` (for
|
|
387
|
+
3D renderer plugins).
|
|
388
|
+
- `package.json`: include `agent-skill.md` and `combos-plugin.json` in
|
|
389
|
+
`files`; expose `./plugin-manifest` and `./agent-skill` in `exports`.
|
|
390
|
+
- Run `pnpm validate-plugin-manifests --strict` and
|
|
391
|
+
`pnpm plugin-index:check` before publishing. The publish script does
|
|
392
|
+
this automatically.
|
|
393
|
+
- Duplicate registration of the same System class is warned and skipped.
|
|
394
|
+
|
|
395
|
+
## Common pitfalls
|
|
396
|
+
|
|
397
|
+
| Symptom | Fix |
|
|
398
|
+
|---------|-----|
|
|
399
|
+
| Blank canvas | Add the right base renderer system (`RendererSystem` for 2D, `Renderer3DSystem` for 3D); ensure `autoStart: true` or call `game.start()` |
|
|
400
|
+
| Nothing draws | Add the matching sub-system (e.g. `ImgSystem`, `Graphics3DSystem`) before adding components |
|
|
401
|
+
| `getSystem` returns undefined | Use class reference, not string: `game.getSystem(RendererSystem)` |
|
|
402
|
+
| "Component already added" error | Use a new `Component` instance per `GameObject` |
|
|
403
|
+
| Destroyed `GameObject` causes errors | Retained reference in a closure — move logic into a Component |
|
|
404
|
+
| Cannot pause / resume behaviour | Logic in `setInterval` / `ticker.add` — move into `Component.update()` |
|
|
405
|
+
| Resource name not found | Match `resource.addResource` `name` to component `resource` field |
|
|
406
|
+
| TS: `onError` missing on `SoundSystem` | `SoundSystem` requires `onError` callback |
|
|
407
|
+
| Stale render after async load | 3D: use `increaseAsyncId` / `validateAsyncId` to drop stale work |
|
|
408
|
+
|
|
409
|
+
## Minimal 2D bootstrap
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
import { Game, resource } from '@combos-fun/engine';
|
|
413
|
+
import { RendererSystem } from '@combos-fun/plugin-renderer';
|
|
414
|
+
import { RenderSystem } from '@combos-fun/plugin-renderer-render';
|
|
415
|
+
import { ImgSystem } from '@combos-fun/plugin-renderer-img';
|
|
416
|
+
|
|
417
|
+
resource.addResource([{ name: 'logo', src: 'logo.png', preload: true }]);
|
|
418
|
+
|
|
419
|
+
new Game({
|
|
420
|
+
systems: [
|
|
421
|
+
new RendererSystem({ canvas: document.querySelector('#canvas')!, width: 750, height: 1334 }),
|
|
422
|
+
new RenderSystem(),
|
|
423
|
+
new ImgSystem(),
|
|
424
|
+
],
|
|
425
|
+
});
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
## Minimal 3D bootstrap
|
|
429
|
+
|
|
430
|
+
```ts
|
|
431
|
+
import { Game } from '@combos-fun/engine';
|
|
432
|
+
import { Renderer3DSystem } from '@combos-fun/plugin-renderer-3d';
|
|
433
|
+
import { Graphics3DSystem } from '@combos-fun/plugin-renderer-3d-graphics';
|
|
434
|
+
|
|
435
|
+
new Game({
|
|
436
|
+
systems: [
|
|
437
|
+
new Renderer3DSystem({ canvas: document.querySelector('#canvas')!, width: 750, height: 1000 }),
|
|
438
|
+
new Graphics3DSystem(),
|
|
439
|
+
],
|
|
440
|
+
});
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
## Verification
|
|
444
|
+
|
|
445
|
+
After modifying engine code or plugin authoring rules:
|
|
446
|
+
|
|
447
|
+
1. `pnpm typecheck`
|
|
448
|
+
2. `pnpm run build` (rebuild all packages in dependency order)
|
|
449
|
+
3. `pnpm validate-plugin-manifests` (warn during migration, strict at GA)
|
|
450
|
+
4. `pnpm plugin-index:check`
|
|
451
|
+
5. Run an example app from `examples/` and verify no console errors
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@combos-fun/engine",
|
|
3
|
+
"pluginId": "engine",
|
|
4
|
+
"category": "core",
|
|
5
|
+
"dimension": "shared",
|
|
6
|
+
"isCore": true,
|
|
7
|
+
"keywords": ["engine", "ecs", "kernel", "runtime", "game", "system", "component"],
|
|
8
|
+
"agentSkill": "./agent-skill.md",
|
|
9
|
+
"exports": [
|
|
10
|
+
"Game",
|
|
11
|
+
"Scene",
|
|
12
|
+
"GameObject",
|
|
13
|
+
"Component",
|
|
14
|
+
"System",
|
|
15
|
+
"Transform",
|
|
16
|
+
"resource",
|
|
17
|
+
"decorators",
|
|
18
|
+
"OBSERVER_TYPE",
|
|
19
|
+
"RESOURCE_TYPE",
|
|
20
|
+
"LOAD_EVENT",
|
|
21
|
+
"LOAD_SCENE_MODE"
|
|
22
|
+
]
|
|
23
|
+
}
|
package/dist/engine.cjs.js
CHANGED
|
@@ -1433,6 +1433,7 @@ exports.RESOURCE_TYPE = void 0;
|
|
|
1433
1433
|
RESOURCE_TYPE["SPRITE_ANIMATION"] = "SPRITE_ANIMATION";
|
|
1434
1434
|
RESOURCE_TYPE["AUDIO"] = "AUDIO";
|
|
1435
1435
|
RESOURCE_TYPE["VIDEO"] = "VIDEO";
|
|
1436
|
+
RESOURCE_TYPE["GLB"] = "GLB";
|
|
1436
1437
|
})(exports.RESOURCE_TYPE || (exports.RESOURCE_TYPE = {}));
|
|
1437
1438
|
resourceLoader$1.XhrLoadStrategy.setExtensionXhrType('json', resourceLoader$1.XhrResponseType.Json);
|
|
1438
1439
|
resourceLoader$1.XhrLoadStrategy.setExtensionXhrType('tex', resourceLoader$1.XhrResponseType.Json);
|
|
@@ -1441,6 +1442,8 @@ resourceLoader$1.XhrLoadStrategy.setExtensionXhrType('mp3', resourceLoader$1.Xhr
|
|
|
1441
1442
|
resourceLoader$1.XhrLoadStrategy.setExtensionXhrType('wav', resourceLoader$1.XhrResponseType.Buffer);
|
|
1442
1443
|
resourceLoader$1.XhrLoadStrategy.setExtensionXhrType('aac', resourceLoader$1.XhrResponseType.Buffer);
|
|
1443
1444
|
resourceLoader$1.XhrLoadStrategy.setExtensionXhrType('ogg', resourceLoader$1.XhrResponseType.Buffer);
|
|
1445
|
+
resourceLoader$1.XhrLoadStrategy.setExtensionXhrType('glb', resourceLoader$1.XhrResponseType.Buffer);
|
|
1446
|
+
resourceLoader$1.XhrLoadStrategy.setExtensionXhrType('gltf', resourceLoader$1.XhrResponseType.Json);
|
|
1444
1447
|
const RESOURCE_TYPE_STRATEGY = {
|
|
1445
1448
|
png: resourceLoader$1.ImageLoadStrategy,
|
|
1446
1449
|
jpg: resourceLoader$1.ImageLoadStrategy,
|
|
@@ -1451,6 +1454,8 @@ const RESOURCE_TYPE_STRATEGY = {
|
|
|
1451
1454
|
ske: resourceLoader$1.XhrLoadStrategy,
|
|
1452
1455
|
audio: resourceLoader$1.XhrLoadStrategy,
|
|
1453
1456
|
video: resourceLoader$1.VideoLoadStrategy,
|
|
1457
|
+
glb: resourceLoader$1.XhrLoadStrategy,
|
|
1458
|
+
gltf: resourceLoader$1.XhrLoadStrategy,
|
|
1454
1459
|
};
|
|
1455
1460
|
/**
|
|
1456
1461
|
* Resource manager
|
|
@@ -1697,6 +1702,7 @@ const decorators = {
|
|
|
1697
1702
|
const version = '__VERSION__';
|
|
1698
1703
|
console.log(`@combos-fun/engine version: ${version}`);
|
|
1699
1704
|
|
|
1705
|
+
exports.COMBOS_GAME_PLUGIN_INIT_SUCCESS = COMBOS_GAME_PLUGIN_INIT_SUCCESS;
|
|
1700
1706
|
exports.Component = Component;
|
|
1701
1707
|
exports.Game = Game;
|
|
1702
1708
|
exports.GameObject = GameObject;
|
|
@@ -1707,6 +1713,7 @@ exports.System = System;
|
|
|
1707
1713
|
exports.Transform = Transform;
|
|
1708
1714
|
exports.componentObserver = componentObserver;
|
|
1709
1715
|
exports.decorators = decorators;
|
|
1716
|
+
exports.postParentPluginInitSuccess = postParentPluginInitSuccess;
|
|
1710
1717
|
exports.resource = resource;
|
|
1711
1718
|
exports.resourceLoader = resourceLoader;
|
|
1712
1719
|
exports.version = version;
|