@cyberart-io/engine 0.0.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/LICENSE +186 -0
- package/README.md +291 -0
- package/dist/index.d.ts +377 -0
- package/dist/index.js +8 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
CyberArt Engine License
|
|
2
|
+
Copyright (c) 2026 Aaron Boyarsky
|
|
3
|
+
|
|
4
|
+
This is a source-available license, not an OSI open-source license.
|
|
5
|
+
It is intended to let people make and sell art, games, simulations,
|
|
6
|
+
and similar standalone works, and to keep the Engine from being
|
|
7
|
+
redistributed or used as a competing product or platform.
|
|
8
|
+
|
|
9
|
+
By installing, copying, modifying, or using the Engine, you agree to
|
|
10
|
+
this license. If you do not agree, you may not use the Engine.
|
|
11
|
+
|
|
12
|
+
This license applies only to this version of the Engine. The Licensor
|
|
13
|
+
may license other versions under different terms.
|
|
14
|
+
|
|
15
|
+
1. Definitions
|
|
16
|
+
|
|
17
|
+
"Licensor" means Aaron Boyarsky, and any successor or assignee of
|
|
18
|
+
his copyright in the Engine. A written assignment may substitute a
|
|
19
|
+
company or other person as Licensor for this license and for later
|
|
20
|
+
versions.
|
|
21
|
+
|
|
22
|
+
"You" (or "your") means the individual or legal entity exercising
|
|
23
|
+
rights under this license.
|
|
24
|
+
|
|
25
|
+
"Affiliate" means any entity that directly or indirectly controls,
|
|
26
|
+
is controlled by, or is under common control with you. "Control"
|
|
27
|
+
means the power to direct management, whether through ownership of
|
|
28
|
+
more than fifty percent (50%) of voting securities, by contract, or
|
|
29
|
+
otherwise.
|
|
30
|
+
|
|
31
|
+
"Engine" means the @cyberart-io/engine software, including its
|
|
32
|
+
source, object, and bundled forms, and any modification, fork, or
|
|
33
|
+
derivative of that software (a "Derivative Engine").
|
|
34
|
+
|
|
35
|
+
"Creative Work" means an original standalone work that you create
|
|
36
|
+
using the Engine, whose primary purpose is to be experienced,
|
|
37
|
+
played, or observed as art, a game, a simulation, or another work
|
|
38
|
+
of the same kind (for example a cart, sketch, animation,
|
|
39
|
+
installation, toy, visualizer, exhibit, or playable piece). The
|
|
40
|
+
Engine may be bundled only as the runtime for that work.
|
|
41
|
+
|
|
42
|
+
A Creative Work is not: a general-purpose host, runtime, SDK,
|
|
43
|
+
template, marketplace, catalog, CMS, widget, multi-artist gallery,
|
|
44
|
+
or other platform that exists mainly to run, sell, or host works by
|
|
45
|
+
others (including a collection or site whose purpose is to mount
|
|
46
|
+
third-party carts or similar works); a competing product to the
|
|
47
|
+
Engine; or general application or infrastructure software that is
|
|
48
|
+
not itself art, a game, a simulation, or a similar standalone work.
|
|
49
|
+
A site or exhibit of only your own Creative Works is not, by itself,
|
|
50
|
+
excluded.
|
|
51
|
+
|
|
52
|
+
"Competing Use" means you or an Affiliate using the Engine or a
|
|
53
|
+
Derivative Engine to offer, operate, or sell a product or service
|
|
54
|
+
that (a) provides the Engine (or equivalent host/runtime APIs) to
|
|
55
|
+
third parties, (b) hosts, mounts, or distributes third-party carts
|
|
56
|
+
or similar works as a platform, or (c) substitutes for the Engine
|
|
57
|
+
as a framework others would otherwise obtain from the Licensor.
|
|
58
|
+
|
|
59
|
+
2. Grant
|
|
60
|
+
|
|
61
|
+
Subject to this license, the Licensor grants you a non-exclusive,
|
|
62
|
+
non-sublicensable, royalty-free license to:
|
|
63
|
+
|
|
64
|
+
(a) use, copy, and modify the Engine, solely to create, develop,
|
|
65
|
+
perform, exhibit, play, and archive Creative Works;
|
|
66
|
+
(b) distribute and sell your Creative Works, including a copy of
|
|
67
|
+
the Engine bundled with a Creative Work, but only as reasonably
|
|
68
|
+
necessary for that work to run for its audience (for example a
|
|
69
|
+
webpage, file, token, or build that runs the piece); and
|
|
70
|
+
(c) keep private copies and modifications of the Engine for the
|
|
71
|
+
purposes in (a) and (b).
|
|
72
|
+
|
|
73
|
+
This license is personal to you and is not transferable, except
|
|
74
|
+
that you may assign it to a successor of your business solely
|
|
75
|
+
together with your Creative Work(s). The successor is bound by this
|
|
76
|
+
license. You may not assign or transfer rights in the Engine apart
|
|
77
|
+
from a Creative Work.
|
|
78
|
+
|
|
79
|
+
You may charge for the Creative Work. You may not charge for the
|
|
80
|
+
Engine as a separate product, library, or service.
|
|
81
|
+
|
|
82
|
+
Each distribution of a Creative Work under Section 2(b) grants the
|
|
83
|
+
recipient, directly from the Licensor (not as a sublicense from
|
|
84
|
+
you), a license solely to run that copy to experience that Creative
|
|
85
|
+
Work, and not to extract or reuse the Engine for any other purpose.
|
|
86
|
+
|
|
87
|
+
Any distribution under Section 2(b) must keep copyright and license
|
|
88
|
+
notices on the Engine, or include a prominent pointer to this
|
|
89
|
+
license, with the bundled copy.
|
|
90
|
+
|
|
91
|
+
3. Restrictions
|
|
92
|
+
|
|
93
|
+
You may not, except under a separate written license from the
|
|
94
|
+
Licensor:
|
|
95
|
+
|
|
96
|
+
(a) publish, distribute, license, sublicense, or sell the Engine or
|
|
97
|
+
a Derivative Engine on its own, as an SDK, npm package,
|
|
98
|
+
template, or competing framework;
|
|
99
|
+
(b) make a Competing Use of the Engine or a Derivative Engine, or
|
|
100
|
+
permit an Affiliate to do so;
|
|
101
|
+
(c) use the Engine for a primary purpose other than creating
|
|
102
|
+
Creative Works (including general application software or
|
|
103
|
+
infrastructure that is not itself a Creative Work);
|
|
104
|
+
(d) remove or obscure copyright or license notices on the Engine;
|
|
105
|
+
(e) represent that the Licensor endorses you or your Creative Work;
|
|
106
|
+
or
|
|
107
|
+
(f) use the Engine (including its source, APIs, or runtime
|
|
108
|
+
behavior) to train a machine-learning model, or otherwise to
|
|
109
|
+
generate a substitute engine or a Competing Use.
|
|
110
|
+
|
|
111
|
+
Creating a Creative Work that itself uses machine learning is not,
|
|
112
|
+
by itself, a violation of Section 3(f). Using output of your own
|
|
113
|
+
Creative Work to train a model for your own Creative Works is not,
|
|
114
|
+
by itself, a violation of Section 3(f).
|
|
115
|
+
|
|
116
|
+
Bundling under Section 2(b) does not allow you to offer the Engine
|
|
117
|
+
as a reusable library, document it as a third-party SDK, or invite
|
|
118
|
+
others to build unrelated works on your copy.
|
|
119
|
+
|
|
120
|
+
4. Patents
|
|
121
|
+
|
|
122
|
+
The Licensor grants you a non-exclusive, royalty-free patent
|
|
123
|
+
license under the Licensor's patents that are necessarily infringed
|
|
124
|
+
by the Engine as provided, or by modifications you make under
|
|
125
|
+
Section 2, solely to the extent required to exercise the rights in
|
|
126
|
+
Section 2. All other patent rights are reserved.
|
|
127
|
+
|
|
128
|
+
This patent license ends if you or an Affiliate commence a claim
|
|
129
|
+
that the Engine or the Licensor infringes a patent.
|
|
130
|
+
|
|
131
|
+
5. Ownership
|
|
132
|
+
|
|
133
|
+
The Licensor retains all right, title, and interest in the Engine.
|
|
134
|
+
You retain all right, title, and interest in your Creative Works,
|
|
135
|
+
excluding the Engine itself. Assignment of copyright to a successor
|
|
136
|
+
Licensor does not expand this grant. This license does not grant
|
|
137
|
+
trademark rights in "CyberArt", "@cyberart-io", or related marks.
|
|
138
|
+
|
|
139
|
+
6. Contributions
|
|
140
|
+
|
|
141
|
+
If you submit a contribution to the Engine by pull request, patch,
|
|
142
|
+
or another submission you designate as a contribution, you assign
|
|
143
|
+
to the Licensor all right, title, and interest in that
|
|
144
|
+
contribution, and you grant the Licensor a matching patent license
|
|
145
|
+
in the contribution. Informal comments are not contributions unless
|
|
146
|
+
you later submit them as above. If that assignment is ineffective,
|
|
147
|
+
you grant the Licensor an irrevocable, perpetual, worldwide,
|
|
148
|
+
royalty-free, sublicensable license to use, modify, and relicense
|
|
149
|
+
the contribution as part of the Engine, including under other
|
|
150
|
+
terms.
|
|
151
|
+
|
|
152
|
+
7. No warranty; limitation of liability
|
|
153
|
+
|
|
154
|
+
THE ENGINE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
155
|
+
EXPRESS OR IMPLIED, INCLUDING MERCHANTABILITY, FITNESS FOR A
|
|
156
|
+
PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|
|
157
|
+
|
|
158
|
+
TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE LICENSOR IS NOT LIABLE
|
|
159
|
+
FOR ANY DAMAGES ARISING FROM THE ENGINE OR THIS LICENSE, WHETHER IN
|
|
160
|
+
CONTRACT, TORT, OR OTHERWISE, INCLUDING LOST PROFITS, LOST DATA, OR
|
|
161
|
+
CONSEQUENTIAL, INCIDENTAL, INDIRECT, OR SPECIAL DAMAGES.
|
|
162
|
+
|
|
163
|
+
IF A COURT HOLDS THE EXCLUSION OF DAMAGES UNENFORCEABLE, THE
|
|
164
|
+
LICENSOR'S TOTAL LIABILITY IS LIMITED TO THE AMOUNTS YOU PAID THE
|
|
165
|
+
LICENSOR FOR THE ENGINE (IF ANY), AND IS US$0 IF YOU PAID NOTHING.
|
|
166
|
+
|
|
167
|
+
8. Termination
|
|
168
|
+
|
|
169
|
+
This license ends automatically if you breach it. On termination
|
|
170
|
+
you must stop using and distributing the Engine, except that
|
|
171
|
+
recipients who already received a Creative Work bundled under
|
|
172
|
+
Section 2(b) may keep that copy solely to run that work. Sections
|
|
173
|
+
1, 3, 4, 5, 6, 7, 9, and 10 survive.
|
|
174
|
+
|
|
175
|
+
9. Governing law
|
|
176
|
+
|
|
177
|
+
Except where prohibited by applicable law, this license is governed
|
|
178
|
+
by the laws of the Commonwealth of Virginia, without regard to
|
|
179
|
+
conflict-of-law rules, and the exclusive venue for disputes is the
|
|
180
|
+
state or federal courts located in Virginia.
|
|
181
|
+
|
|
182
|
+
10. Entire agreement
|
|
183
|
+
|
|
184
|
+
This is the entire license for this version of the Engine. Rights
|
|
185
|
+
not expressly granted are reserved. If a court finds a term
|
|
186
|
+
unenforceable, the rest remains in effect.
|
package/README.md
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# `@cyberart-io/engine`
|
|
2
|
+
|
|
3
|
+
An engine for building animated, interactive, generative programs for the web. You write a **cart** — a small object with `getDefaultState` (initial state), `update` (advance that state each tick), and `render` (draw it). That is a state machine in a loop that paints to the screen. The engine mounts it into a DOM element you own, runs the loop, and gives you pause, snapshot, events, and save/load.
|
|
4
|
+
|
|
5
|
+
## License
|
|
6
|
+
|
|
7
|
+
You may use this to make and sell art, games, simulations, or similar standalone works — including bundling the runtime so the piece can run. You may not redistribute or sell the engine itself, or use it to build a competing platform. This is not an OSI open-source license. Full terms: [LICENSE](LICENSE).
|
|
8
|
+
|
|
9
|
+
The published `@cyberart-io/engine` package, and the source files that comprise it, are under that license. The rest of this repository (site, carts, API, infrastructure) is proprietary and has no public license grant.
|
|
10
|
+
|
|
11
|
+
A bundled copy must keep the copyright and license notice, or a pointer to it. The minified `dist/index.js` does not embed the text, so put a pointer next to the build, for example in HTML:
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<!-- @cyberart-io/engine — CyberArt Engine License: see ./LICENSE -->
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
or ship a copy of `LICENSE` beside the files you distribute.
|
|
18
|
+
|
|
19
|
+
Tone.js is an optional peer under *its* license. Bundling Tone with a piece does not relicense Tone.
|
|
20
|
+
|
|
21
|
+
This guide is the path from `npm i` to a piece on screen, then a short map of the API. Copy the examples as-is; swap in your own drawing.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm i @cyberart-io/engine
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The package is minified JS plus a rolled-up `.d.ts`. It does not include animation carts. Tone.js is an optional peer — skip it until a cart declares `audio: 'tone'`.
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
A silent cart can `start()` immediately (no click). This `art-project` picks a hue from the token seed and drifts it each frame.
|
|
32
|
+
|
|
33
|
+
`art-project.ts`:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import type { AnimationCart } from '@cyberart-io/engine';
|
|
37
|
+
|
|
38
|
+
type State = { hue: number; t: number };
|
|
39
|
+
|
|
40
|
+
export const artProject: AnimationCart<State> = {
|
|
41
|
+
metadata: {
|
|
42
|
+
id: 'art-project',
|
|
43
|
+
name: 'art-project',
|
|
44
|
+
frameRate: 30,
|
|
45
|
+
},
|
|
46
|
+
getDefaultState: (R) => ({
|
|
47
|
+
hue: R.dec(0, 360),
|
|
48
|
+
t: 0,
|
|
49
|
+
}),
|
|
50
|
+
update: (_R, _frames, _params, _dim, state) => ({
|
|
51
|
+
...state,
|
|
52
|
+
t: state.t + 1,
|
|
53
|
+
}),
|
|
54
|
+
render: (_R, _frames, _params, dim, state, ctx) => {
|
|
55
|
+
const hue = (state.hue + state.t * 0.4) % 360;
|
|
56
|
+
ctx.fillStyle = `hsl(${hue} 70% 45%)`;
|
|
57
|
+
ctx.fillRect(0, 0, dim.width, dim.height);
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`main.ts`:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { createRuntime } from '@cyberart-io/engine';
|
|
66
|
+
import { artProject } from './art-project';
|
|
67
|
+
|
|
68
|
+
const container = document.getElementById('stage')!;
|
|
69
|
+
const runtime = createRuntime({ container });
|
|
70
|
+
const cart = runtime.mount(artProject);
|
|
71
|
+
|
|
72
|
+
await cart.start();
|
|
73
|
+
|
|
74
|
+
window.addEventListener('pagehide', () => {
|
|
75
|
+
cart.destroy();
|
|
76
|
+
runtime.destroy();
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```html
|
|
81
|
+
<div id="stage"></div>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`createRuntime` and `mount` are synchronous. `mount` **prepares** the cart (`getDefaultState`) but does not start the loop — that is `start()`. If prepare fails, `mount` throws.
|
|
85
|
+
|
|
86
|
+
## Write a cart
|
|
87
|
+
|
|
88
|
+
A cart is an `AnimationCart`. Required:
|
|
89
|
+
|
|
90
|
+
| Piece | Role |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `getDefaultState` | Build the initial `state`. Use `R` (`Random`) for anything that should follow the seed. |
|
|
93
|
+
| `update` | Advance `state` each tick. Return the next state. |
|
|
94
|
+
| `render` | Draw into the 2D context (and optional `ImageData`). |
|
|
95
|
+
| `metadata.id` / `name` / `frameRate` | Identity and loop rate. |
|
|
96
|
+
|
|
97
|
+
Useful optionals:
|
|
98
|
+
|
|
99
|
+
| Piece | Role |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `teardown` | Dispose long-lived resources (Tone nodes, listeners) when the cart unloads. |
|
|
102
|
+
| `metadata.audio` | `'tone'` if the piece needs Web Audio. Omit for a silent cart. |
|
|
103
|
+
| `metadata.generative` | `true` when output is a function of the token hash. Saves then only load on the same seed. |
|
|
104
|
+
|
|
105
|
+
Callbacks also receive `DimensionContext` (`width` / `height` and derived size fields), `KeyboardManager`, `PointerManager`, and an optional `HostChannel`. You do not have to use them. Types live on the package: `AnimationCart`, `Random`, `DimensionContext`, `KeyboardManager`, `PointerManager`, `HostChannel`.
|
|
106
|
+
|
|
107
|
+
`Random` is seed-stable: `R.dec(min, max)`, `R.int(min, max)`, `R.bool()`, `R.choose(list)`.
|
|
108
|
+
|
|
109
|
+
## Host API
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
const runtime = createRuntime({
|
|
113
|
+
container, // required mount point
|
|
114
|
+
seed, // optional `0x` + 64 hex; drives Random
|
|
115
|
+
captureKeyboard, // default false — embeds should leave this off
|
|
116
|
+
audio, // hint to unlock Tone if a click beats mount
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const cart = runtime.mount(artProject, {
|
|
120
|
+
initialState, // boot overrides into getDefaultState (not a save replay)
|
|
121
|
+
onEvent, // outbound events from the cart
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`createRuntime` options:
|
|
126
|
+
|
|
127
|
+
| Option | Default | Meaning |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| `container` | required | Element that will hold the canvas. |
|
|
130
|
+
| `seed` | generated | Token hash for `Random`. Instance-local; does not clobber a global cache. |
|
|
131
|
+
| `captureKeyboard` | `false` | When true, the cart listens for `window` keydown. Full-page players pass `true`. |
|
|
132
|
+
| `audio` | none | Libraries to unlock if `unlockAudio()` runs before `mount`. After mount, the cart’s `metadata.audio` wins. |
|
|
133
|
+
|
|
134
|
+
`CartHandle` (what `mount` returns):
|
|
135
|
+
|
|
136
|
+
| Member | Meaning |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `start()` | Unlock declared audio (if any) and begin the loop. |
|
|
139
|
+
| `pause()` / `resume()` | Freeze / continue the loop. |
|
|
140
|
+
| `snapshot()` | PNG data URL + seed + metadata. Not live `state`. |
|
|
141
|
+
| `dispatch(event)` | Queue an inbound host event for the cart. |
|
|
142
|
+
| `exportState()` / `exportStateJSON()` | Pause-safe serializable bundle. |
|
|
143
|
+
| `importState(bundle)` | Restore a bundle (or JSON string). |
|
|
144
|
+
| `destroy()` | Unload this cart. Idempotent. |
|
|
145
|
+
| `needsAudio` | True when the cart declared audio libraries. Use this to show a click overlay. |
|
|
146
|
+
| `paused` / `isPrepared` / `isLoopRunning` | Loop flags. |
|
|
147
|
+
| `canvas` | The canvas in use, if any. |
|
|
148
|
+
| `tokenData` | Live hash and token id. |
|
|
149
|
+
| `getCartState()` | Live object for debug UI. Not JSON-safe — use export/import for saves. |
|
|
150
|
+
|
|
151
|
+
`runtime.destroy()` tears down the runtime. `runtime.unlockAudio()` is for an early click before `mount`. `runtime.onError` receives frame errors (`phase`, `consecutive`, `stopped`).
|
|
152
|
+
|
|
153
|
+
One cart per runtime. A second `mount` unloads the first. Several pieces on a page means several `createRuntime()` calls.
|
|
154
|
+
|
|
155
|
+
## Audio
|
|
156
|
+
|
|
157
|
+
Silent carts (no `metadata.audio`) never import Tone. Call `start()` whenever you want.
|
|
158
|
+
|
|
159
|
+
Carts that need Web Audio declare it:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
metadata: {
|
|
163
|
+
id: 'art-project',
|
|
164
|
+
name: 'art-project',
|
|
165
|
+
frameRate: 30,
|
|
166
|
+
audio: 'tone',
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Install the peer: `npm i tone@^14.8.15`. Call `cart.start()` **inside a click or tap**. Browsers often leave `Tone.start()` pending without a gesture, so the loop never begins. `needsAudio` is true so the host can show a click overlay.
|
|
171
|
+
|
|
172
|
+
`start()` only unlocks libraries the cart (or `createRuntime({ audio })`) declared.
|
|
173
|
+
|
|
174
|
+
If a click can happen before `mount` finishes, pass the same `audio` into `createRuntime` and/or call `runtime.unlockAudio()` from that click. After mount, the cart’s metadata wins.
|
|
175
|
+
|
|
176
|
+
## Seed
|
|
177
|
+
|
|
178
|
+
Pass `seed` when you want a deterministic output (for example from `?hash=`):
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
const hash = new URLSearchParams(window.location.search).get('hash');
|
|
182
|
+
const runtime = createRuntime({
|
|
183
|
+
container,
|
|
184
|
+
seed: hash ?? undefined,
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`Random` in `getDefaultState` / `update` / `render` follows that hash. Mark `metadata.generative: true` when the piece is a function of the seed — saves then refuse to load onto a different hash.
|
|
189
|
+
|
|
190
|
+
## Events
|
|
191
|
+
|
|
192
|
+
The runtime does not ship an event schema. Types are yours.
|
|
193
|
+
|
|
194
|
+
- Host → cart: `cart.dispatch({ type, payload })`. The cart opts in by calling `hostChannel.consume()` (typically in `update`). Unconsumed events keep the newest 32.
|
|
195
|
+
- Cart → host: `hostChannel.emit({ type, payload })`. Pass `onEvent` at `mount` to hear them.
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
const cart = runtime.mount(artProject, {
|
|
199
|
+
onEvent: (event) => {
|
|
200
|
+
if (event.type === 'art-project.theme') {
|
|
201
|
+
document.body.dataset.theme = String(event.payload);
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
cart.dispatch({ type: 'art-project.theme', payload: 'dusk' });
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Carts that never mention `HostChannel` ignore both directions.
|
|
210
|
+
|
|
211
|
+
## Save and load
|
|
212
|
+
|
|
213
|
+
Live `state` is not JSON-safe (audio nodes, managers, typed arrays, possible cycles). `snapshot()` is PNG + seed. Use export/import when you want the simulation itself:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
const bundle = await cart.exportState();
|
|
217
|
+
const json = await cart.exportStateJSON();
|
|
218
|
+
|
|
219
|
+
await cart.importState(bundle);
|
|
220
|
+
await cart.importState(json);
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Both pause, wait until any in-flight `update` finishes, then restore the previous pause flag. Import tears down, runs `getDefaultState` again (fresh managers, empty audio graph), overlays the save onto that scaffold unless `getDefaultState` already returned the revived `customState`, and restores `framesElapsed`. Works on a prepared cart before `start()`. Throws if nothing is prepared, or if the bundle is incompatible (`IncompatibleCartStateError`).
|
|
224
|
+
|
|
225
|
+
Compatibility is against the **serializable subset** of live state:
|
|
226
|
+
|
|
227
|
+
- If both sides have a `cartId` (`metadata.id`), they must match.
|
|
228
|
+
- If either the live cart or the bundle is generative, `bundle.seed` must match the live hash (case-insensitive, optional `0x`).
|
|
229
|
+
- The snapshot must share at least one key with the live serializable shape.
|
|
230
|
+
- Extra snapshot keys are ignored. Keys missing from the snapshot keep scaffold values.
|
|
231
|
+
- A type clash on an overlapping key throws. An empty `state` object is rejected.
|
|
232
|
+
- The bundle records canvas buffer size. Load pins the simulation to that size and letterboxes if the window is smaller. A later user resize to a different size clears the pin and restarts from the hash.
|
|
233
|
+
|
|
234
|
+
Not restored: Tone / AudioContext graph (re-init audio on the next `update`), the `Random` sequence, canvas pixels, or hash-derived `featureState`. Typed arrays round-trip; class instances and functions are omitted.
|
|
235
|
+
|
|
236
|
+
### Optional W/E helpers
|
|
237
|
+
|
|
238
|
+
`exportState` / `importState` do not touch `localStorage`. Two helpers wire **W** / **E** if you want that:
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
// in the cart, once you have keyboardManager + hostChannel
|
|
242
|
+
import { registerCartStateHotkeys } from '@cyberart-io/engine';
|
|
243
|
+
|
|
244
|
+
registerCartStateHotkeys(keyboardManager, hostChannel, {
|
|
245
|
+
persist: 'localStorage',
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
// in the host
|
|
251
|
+
import { attachCartStatePersistence } from '@cyberart-io/engine';
|
|
252
|
+
|
|
253
|
+
const cart = runtime.mount(artProject, {
|
|
254
|
+
onEvent: attachCartStatePersistence(
|
|
255
|
+
() => cart,
|
|
256
|
+
() => artProject.metadata?.id,
|
|
257
|
+
),
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Pass `captureKeyboard: true` on `createRuntime` or the keys never reach the cart. Without `persist: 'localStorage'`, the host adapter ignores the events.
|
|
262
|
+
|
|
263
|
+
Slots are `localStorage['cyberart.state.' + cartId]`, or per-hash when the cart is generative. JSON can persist even if a framebuffer write fails.
|
|
264
|
+
|
|
265
|
+
## Canvas
|
|
266
|
+
|
|
267
|
+
The engine prefers, in order:
|
|
268
|
+
|
|
269
|
+
1. A canvas in the container marked `data-cyberart-canvas` (`CYBERART_CANVAS_ATTR`)
|
|
270
|
+
2. The container’s first `<canvas>`
|
|
271
|
+
3. A canvas it creates (no `id="canvas"`)
|
|
272
|
+
|
|
273
|
+
A canvas the host adopted is left in place on destroy; a canvas the engine created is removed. Both `cart.destroy()` and `runtime.destroy()` are idempotent.
|
|
274
|
+
|
|
275
|
+
## Limits
|
|
276
|
+
|
|
277
|
+
- **One audio graph per page.** Tone / master effects are process-global. Destroying a cart disposes cart-owned nodes, not the shared context.
|
|
278
|
+
- **No catalog.** There is no `mount({ id, version })` network fetch. You pass the cart object.
|
|
279
|
+
- **`getCartState()` is live**, not a snapshot. Use export/import for pause-safe state. `snapshot()` is still PNG + seed.
|
|
280
|
+
- **Keyboard defaults off** in embeds (`captureKeyboard: false`). Full-page players pass `true`.
|
|
281
|
+
|
|
282
|
+
## Publishing this package (maintainers)
|
|
283
|
+
|
|
284
|
+
Not part of writing a cart. The npm tarball is built from `packages/engine/src/index.ts` and contains minified `dist/index.js`, rolled-up `dist/index.d.ts`, `LICENSE`, `README.md`, and `package.json`.
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
pnpm run pack:engine
|
|
288
|
+
cd packages/engine && npm publish --access public
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
`pack:engine` builds, then runs the same `npm pack` membership check `npm publish` uses. `prepublishOnly` rebuilds immediately before publish. `publishConfig.access` is `public`. You must be `npm login`’d to the `@cyberart-io` org. Do not `npm i github:cyberart-io/cyberart.io` — that downloads this repo’s TypeScript. Version independently of the site. Do not publish from the repo root (`@cyberart.io/source` is private).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* @cyberart-io/engine
|
|
3
|
+
* Copyright (c) 2026 Aaron Boyarsky
|
|
4
|
+
* Licensed under the CyberArt Engine License. See LICENSE.
|
|
5
|
+
* Not an OSI open-source license.
|
|
6
|
+
*/
|
|
7
|
+
/** Marker on canvases this runtime created or that a host offered for adoption. */
|
|
8
|
+
declare const CYBERART_CANVAS_ATTR = "data-cyberart-canvas";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Copyright (c) 2026 Aaron Boyarsky
|
|
12
|
+
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
13
|
+
* See packages/engine/LICENSE
|
|
14
|
+
*
|
|
15
|
+
* Audio libraries the animation manager knows how to load and unlock.
|
|
16
|
+
* Carts declare which they need via `metadata.audio`; the manager looks them
|
|
17
|
+
* up here. Add a new adapter when a second library is supported.
|
|
18
|
+
*/
|
|
19
|
+
type AudioLibraryId = 'tone';
|
|
20
|
+
type AudioLibrarySpec = AudioLibraryId | AudioLibraryId[];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Copyright (c) 2026 Aaron Boyarsky
|
|
24
|
+
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
25
|
+
* See packages/engine/LICENSE
|
|
26
|
+
*/
|
|
27
|
+
type ExternalAssetDependency = {
|
|
28
|
+
cid: string;
|
|
29
|
+
[k: string]: unknown;
|
|
30
|
+
};
|
|
31
|
+
type TokenData = {
|
|
32
|
+
hash: string;
|
|
33
|
+
tokenId: string;
|
|
34
|
+
externalAssetDependencies?: ExternalAssetDependency[];
|
|
35
|
+
/**
|
|
36
|
+
* Per AB Engine Flex generator spec: gateway URL the script is supposed to
|
|
37
|
+
* combine with each `externalAssetDependencies[i].cid` of type `"IPFS"`.
|
|
38
|
+
* AB defaults this to `https://ipfs.artblocks.io/ipfs/` and lets partners
|
|
39
|
+
* override per-contract via support. Optional because standard (non-Flex)
|
|
40
|
+
* projects don't get it.
|
|
41
|
+
*/
|
|
42
|
+
preferredIPFSGateway?: string;
|
|
43
|
+
/** Same idea for Arweave-typed external assets. We don't currently use it. */
|
|
44
|
+
preferredArweaveGateway?: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Copyright (c) 2026 Aaron Boyarsky
|
|
49
|
+
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
50
|
+
* See packages/engine/LICENSE
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
declare class Random {
|
|
54
|
+
private useA;
|
|
55
|
+
private prngA;
|
|
56
|
+
private prngB;
|
|
57
|
+
constructor(tokenData: TokenData);
|
|
58
|
+
r_zero_one(): number;
|
|
59
|
+
dec(min?: number, max?: number): number;
|
|
60
|
+
int(min: number, max?: number): number;
|
|
61
|
+
bool(p?: number): boolean;
|
|
62
|
+
sign(): 1 | -1;
|
|
63
|
+
choose<T = unknown>(list: T[]): T;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type DimensionContext = {
|
|
67
|
+
width: number;
|
|
68
|
+
height: number;
|
|
69
|
+
iWidth: number;
|
|
70
|
+
iHeight: number;
|
|
71
|
+
smallDim: number;
|
|
72
|
+
largeDim: number;
|
|
73
|
+
area: number;
|
|
74
|
+
aspectRatio: number;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Copyright (c) 2026 Aaron Boyarsky
|
|
79
|
+
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
80
|
+
* See packages/engine/LICENSE
|
|
81
|
+
*/
|
|
82
|
+
type KeypressHandler = () => void;
|
|
83
|
+
/**
|
|
84
|
+
* Handler for keyboard inputs.
|
|
85
|
+
*/
|
|
86
|
+
declare class KeyboardManager {
|
|
87
|
+
private actionMap;
|
|
88
|
+
private debugMode;
|
|
89
|
+
constructor(debugMode?: boolean, captureKeyboard?: boolean);
|
|
90
|
+
/**
|
|
91
|
+
* When a key is pressed, check if there's a corresponding action, and execute it.
|
|
92
|
+
* @param e
|
|
93
|
+
*/
|
|
94
|
+
checkKeypress({ key }: KeyboardEvent): void;
|
|
95
|
+
/**
|
|
96
|
+
* Register a certain action to be performed when a given key is pressed.
|
|
97
|
+
* @param key
|
|
98
|
+
* @param action
|
|
99
|
+
* @param overwrite
|
|
100
|
+
*/
|
|
101
|
+
registerAction(key: string, action: KeypressHandler, overwrite?: boolean): void;
|
|
102
|
+
/**
|
|
103
|
+
* Remove the global keydown listener and clear registered actions. Call this
|
|
104
|
+
* when the owning animation is torn down so listeners don't accumulate across
|
|
105
|
+
* cart reloads (e.g. on window resize).
|
|
106
|
+
*/
|
|
107
|
+
destroy(): void;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Copyright (c) 2026 Aaron Boyarsky
|
|
112
|
+
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
113
|
+
* See packages/engine/LICENSE
|
|
114
|
+
*/
|
|
115
|
+
type PointerClick = {
|
|
116
|
+
x: number;
|
|
117
|
+
y: number;
|
|
118
|
+
};
|
|
119
|
+
declare class PointerManager {
|
|
120
|
+
x: number;
|
|
121
|
+
y: number;
|
|
122
|
+
isDown: boolean;
|
|
123
|
+
private clicks;
|
|
124
|
+
private canvas;
|
|
125
|
+
constructor(canvas: HTMLCanvasElement);
|
|
126
|
+
private toCanvasCoords;
|
|
127
|
+
private onPointerDown;
|
|
128
|
+
private onPointerMove;
|
|
129
|
+
private onPointerUp;
|
|
130
|
+
hasClick(): boolean;
|
|
131
|
+
consumeClick(): PointerClick | null;
|
|
132
|
+
destroy(): void;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Copyright (c) 2026 Aaron Boyarsky
|
|
137
|
+
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
138
|
+
* See packages/engine/LICENSE
|
|
139
|
+
*
|
|
140
|
+
* Host ↔ cart mailbox. Inbound events are queued by the host via `dispatch`
|
|
141
|
+
* and drained by cart code that opts in via `consume()`. Outbound events are
|
|
142
|
+
* `emit`ted by the cart and forwarded to host `onEvent` listeners.
|
|
143
|
+
*
|
|
144
|
+
* Carts that never mention this object behave as they do today.
|
|
145
|
+
*/
|
|
146
|
+
type HostEvent = {
|
|
147
|
+
type: string;
|
|
148
|
+
payload?: unknown;
|
|
149
|
+
};
|
|
150
|
+
type HostEventListener = (event: HostEvent) => void;
|
|
151
|
+
declare class HostChannel {
|
|
152
|
+
private inbound;
|
|
153
|
+
private listeners;
|
|
154
|
+
dispatch(event: HostEvent): void;
|
|
155
|
+
consume(): HostEvent[];
|
|
156
|
+
emit(event: HostEvent): void;
|
|
157
|
+
onEvent(listener: HostEventListener): () => void;
|
|
158
|
+
clear(): void;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Copyright (c) 2026 Aaron Boyarsky
|
|
163
|
+
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
164
|
+
* See packages/engine/LICENSE
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
type AnimationTiming = {
|
|
168
|
+
now: number;
|
|
169
|
+
startTime: number;
|
|
170
|
+
elapsedSinceStart: number;
|
|
171
|
+
deltaSinceLastUpdate: number;
|
|
172
|
+
deltaSinceLastRender: number;
|
|
173
|
+
};
|
|
174
|
+
type AnimationCart<T = unknown, TFeatureState = undefined> = {
|
|
175
|
+
getDefaultFeatureState?: (R: Random, dimensionContext: DimensionContext, rawParams: number[], keyboardManager: KeyboardManager, customState?: Partial<T>, pointerManager?: PointerManager, gameManager?: unknown, hostChannel?: HostChannel) => TFeatureState;
|
|
176
|
+
getDefaultState: (R: Random, dimensionContext: DimensionContext, rawParams: number[], keyboardManager: KeyboardManager, customState?: Partial<T>, pointerManager?: PointerManager, gameManager?: unknown, featureState?: Readonly<TFeatureState>, hostChannel?: HostChannel) => T;
|
|
177
|
+
update: (R: Random, framesElapsed: number, rawParams: number[], dimensionContext: DimensionContext, state: T, keyboardManager: KeyboardManager, pointerManager?: PointerManager, gameManager?: unknown, timing?: AnimationTiming, featureState?: Readonly<TFeatureState>, hostChannel?: HostChannel) => T;
|
|
178
|
+
render: (R: Random, framesElapsed: number, rawParams: number[], dimensionContext: DimensionContext, state: T, drawingContext: CanvasRenderingContext2D, imageData: ImageData, pointerManager?: PointerManager, gameManager?: unknown, timing?: AnimationTiming, featureState?: Readonly<TFeatureState>, hostChannel?: HostChannel) => void;
|
|
179
|
+
adjust?: Record<string, {
|
|
180
|
+
type: 'switch';
|
|
181
|
+
immediate?: boolean;
|
|
182
|
+
label: string;
|
|
183
|
+
description: string;
|
|
184
|
+
} | {
|
|
185
|
+
type: 'slider';
|
|
186
|
+
immediate?: boolean;
|
|
187
|
+
description: string;
|
|
188
|
+
min: number;
|
|
189
|
+
max: number;
|
|
190
|
+
step: number;
|
|
191
|
+
label: string;
|
|
192
|
+
mapToOutput: (value: number) => number;
|
|
193
|
+
mapToInput: (value: number) => number;
|
|
194
|
+
}>;
|
|
195
|
+
/**
|
|
196
|
+
* Optional cleanup hook invoked when the cart is unloaded (e.g. on window
|
|
197
|
+
* resize, which fully rebuilds the animation). Carts that allocate long-lived
|
|
198
|
+
* resources such as Tone.js audio nodes should dispose them here to avoid
|
|
199
|
+
* leaks accumulating across reloads.
|
|
200
|
+
*/
|
|
201
|
+
teardown?: (state: T, featureState?: Readonly<TFeatureState>) => void;
|
|
202
|
+
metadata?: {
|
|
203
|
+
id: string;
|
|
204
|
+
name: string;
|
|
205
|
+
description?: string;
|
|
206
|
+
frameRate: number;
|
|
207
|
+
/**
|
|
208
|
+
* Optional per-visualMode display FPS overrides (e.g. mycelium at 60
|
|
209
|
+
* while the cart baseline stays lower). Keys match cart `visualMode`.
|
|
210
|
+
*/
|
|
211
|
+
frameRateByVisualMode?: Partial<Record<string, number>>;
|
|
212
|
+
/**
|
|
213
|
+
* Audio libraries this cart needs (currently `'tone'`). The animation
|
|
214
|
+
* manager loads and unlocks them; omit or leave unset for a silent cart.
|
|
215
|
+
*/
|
|
216
|
+
audio?: AudioLibrarySpec;
|
|
217
|
+
/**
|
|
218
|
+
* When true, outputs are a function of the token hash. Saved state may
|
|
219
|
+
* only be loaded back onto the same hash.
|
|
220
|
+
*/
|
|
221
|
+
generative?: boolean;
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
type CartStateDimensions = {
|
|
226
|
+
/** Canvas buffer pixels (device pixels). */
|
|
227
|
+
width: number;
|
|
228
|
+
height: number;
|
|
229
|
+
/** CSS layout size of the window/container at save. */
|
|
230
|
+
cssWidth?: number;
|
|
231
|
+
cssHeight?: number;
|
|
232
|
+
dpr?: number;
|
|
233
|
+
};
|
|
234
|
+
type CartStateBundle = {
|
|
235
|
+
version: number;
|
|
236
|
+
cartId?: string;
|
|
237
|
+
/** True when the cart is hash-seeded; load requires the same seed. */
|
|
238
|
+
generative?: boolean;
|
|
239
|
+
seed: string;
|
|
240
|
+
framesElapsed: number;
|
|
241
|
+
/** Pause-aware animation clock; restored so time-based visuals match the save. */
|
|
242
|
+
elapsedSinceStart?: number;
|
|
243
|
+
/** Output size the simulation was running at. Load pins the canvas to this. */
|
|
244
|
+
dimensions?: CartStateDimensions;
|
|
245
|
+
state: unknown;
|
|
246
|
+
};
|
|
247
|
+
declare class IncompatibleCartStateError extends Error {
|
|
248
|
+
constructor(message: string);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Copyright (c) 2026 Aaron Boyarsky
|
|
253
|
+
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
254
|
+
* See packages/engine/LICENSE
|
|
255
|
+
*/
|
|
256
|
+
|
|
257
|
+
type FrameErrorInfo = {
|
|
258
|
+
phase: 'update' | 'render' | 'draw';
|
|
259
|
+
consecutive: number;
|
|
260
|
+
stopped: boolean;
|
|
261
|
+
};
|
|
262
|
+
type CreateRuntimeOptions = {
|
|
263
|
+
/** Required mount point. The runtime creates or adopts a canvas inside this element. */
|
|
264
|
+
container: HTMLElement;
|
|
265
|
+
/** Optional token hash (`0x` + 64 hex). Instance-local; does not clobber an existing global cache. */
|
|
266
|
+
seed?: string;
|
|
267
|
+
/**
|
|
268
|
+
* When true, the cart listens for window keydown (full-page players).
|
|
269
|
+
* Defaults to false so an embed does not steal keys from the host.
|
|
270
|
+
*/
|
|
271
|
+
captureKeyboard?: boolean;
|
|
272
|
+
/**
|
|
273
|
+
* Audio libraries to unlock if the host calls `unlockAudio()` before `mount`
|
|
274
|
+
* (click can beat idle prebuild). After mount, `cart.metadata.audio` wins.
|
|
275
|
+
*/
|
|
276
|
+
audio?: AudioLibrarySpec;
|
|
277
|
+
};
|
|
278
|
+
type MountOptions<T = unknown> = {
|
|
279
|
+
/** Boot overrides passed as `customState` into `getDefaultState`. Not a live-state replay. */
|
|
280
|
+
initialState?: Partial<T>;
|
|
281
|
+
/** Outbound events emitted by cart code via `hostChannel.emit`. */
|
|
282
|
+
onEvent?: HostEventListener;
|
|
283
|
+
/** Site-only. Opaque to the published engine; pass a GameManager from the app. */
|
|
284
|
+
gameManager?: unknown;
|
|
285
|
+
};
|
|
286
|
+
type CartSnapshot = {
|
|
287
|
+
seed: string;
|
|
288
|
+
metadata?: AnimationCart['metadata'];
|
|
289
|
+
pngDataUrl: string;
|
|
290
|
+
};
|
|
291
|
+
type CartHandle = {
|
|
292
|
+
start(): Promise<void>;
|
|
293
|
+
pause(): void;
|
|
294
|
+
resume(): void;
|
|
295
|
+
dispatch(event: HostEvent): void;
|
|
296
|
+
snapshot(): CartSnapshot;
|
|
297
|
+
destroy(): void;
|
|
298
|
+
reload(): void;
|
|
299
|
+
reinit(event?: Event): void;
|
|
300
|
+
/**
|
|
301
|
+
* Live cart state for site chrome (e.g. `/art` debug sliders). Not a public
|
|
302
|
+
* snapshot API — grids and audio nodes are not structured-cloneable.
|
|
303
|
+
*/
|
|
304
|
+
getCartState(): unknown;
|
|
305
|
+
exportState(): Promise<CartStateBundle>;
|
|
306
|
+
exportStateJSON(): Promise<string>;
|
|
307
|
+
importState(bundle: CartStateBundle | string, extras?: {
|
|
308
|
+
framebuffer?: ImageData | null;
|
|
309
|
+
}): Promise<void>;
|
|
310
|
+
peekExportedFramebuffer(): ImageData | null;
|
|
311
|
+
peekSeed(): string | undefined;
|
|
312
|
+
isGenerative(): boolean;
|
|
313
|
+
readonly canvas: HTMLCanvasElement | undefined;
|
|
314
|
+
paused: boolean;
|
|
315
|
+
readonly tokenData: TokenData;
|
|
316
|
+
readonly isPrepared: boolean;
|
|
317
|
+
readonly isLoopRunning: boolean;
|
|
318
|
+
readonly needsAudio: boolean;
|
|
319
|
+
readonly audioLibraries: AudioLibraryId[];
|
|
320
|
+
};
|
|
321
|
+
type CyberArtRuntime = {
|
|
322
|
+
mount<T>(cart: AnimationCart<T, any>, options?: MountOptions<T>): CartHandle;
|
|
323
|
+
unlockAudio(): Promise<void>;
|
|
324
|
+
destroy(): void;
|
|
325
|
+
readonly tokenData: TokenData;
|
|
326
|
+
onError?: (error: unknown, info: FrameErrorInfo) => void;
|
|
327
|
+
};
|
|
328
|
+
declare function createRuntime(options: CreateRuntimeOptions): CyberArtRuntime;
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Copyright (c) 2026 Aaron Boyarsky
|
|
332
|
+
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
333
|
+
* See packages/engine/LICENSE
|
|
334
|
+
*/
|
|
335
|
+
|
|
336
|
+
type CartStateMessageHandler = (message: string, mode: 'persistent' | 'transient') => void;
|
|
337
|
+
type ImportCartStateExtras = {
|
|
338
|
+
/** CPU pixels captured at save. Not part of cartState. */
|
|
339
|
+
framebuffer?: ImageData | null;
|
|
340
|
+
};
|
|
341
|
+
type CartStatePersister = {
|
|
342
|
+
exportState: () => Promise<CartStateBundle>;
|
|
343
|
+
importState: (bundle: CartStateBundle | string, extras?: ImportCartStateExtras) => Promise<void>;
|
|
344
|
+
peekExportedFramebuffer?: () => ImageData | null;
|
|
345
|
+
/** Live token hash; used to look up a generative cart's per-hash save. */
|
|
346
|
+
peekSeed?: () => string | undefined;
|
|
347
|
+
/** Live cart `metadata.generative`. */
|
|
348
|
+
isGenerative?: () => boolean;
|
|
349
|
+
};
|
|
350
|
+
/**
|
|
351
|
+
* Chain `exportState` / `importState` onto localStorage. No-ops unless the
|
|
352
|
+
* cart's W/E event asks for `persist: 'localStorage'`.
|
|
353
|
+
*/
|
|
354
|
+
declare function attachCartStatePersistence(getTarget: () => CartStatePersister | null | undefined, getCartId?: () => string | undefined, onMessage?: CartStateMessageHandler): HostEventListener;
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Copyright (c) 2026 Aaron Boyarsky
|
|
358
|
+
* SPDX-License-Identifier: LicenseRef-CyberArt-Engine
|
|
359
|
+
* See packages/engine/LICENSE
|
|
360
|
+
*/
|
|
361
|
+
|
|
362
|
+
type CartStatePersistBackend = 'localStorage';
|
|
363
|
+
type CartStateHotkeyOptions = {
|
|
364
|
+
/**
|
|
365
|
+
* Host persistence to chain onto W/E. `exportState` / `importState` stay
|
|
366
|
+
* on the runtime; the host adapter writes and reads this backend.
|
|
367
|
+
*/
|
|
368
|
+
persist?: CartStatePersistBackend;
|
|
369
|
+
};
|
|
370
|
+
/**
|
|
371
|
+
* Cart-facing W/E bindings. Emits save/load events; does not touch
|
|
372
|
+
* localStorage. Pass `persist: 'localStorage'` so a host adapter chained
|
|
373
|
+
* with `attachCartStatePersistence` will store and restore the bundle.
|
|
374
|
+
*/
|
|
375
|
+
declare function registerCartStateHotkeys(keyboardManager: KeyboardManager, hostChannel: HostChannel, options?: CartStateHotkeyOptions): void;
|
|
376
|
+
|
|
377
|
+
export { type AnimationCart, type AnimationTiming, type AudioLibraryId, type AudioLibrarySpec, CYBERART_CANVAS_ATTR, type CartHandle, type CartSnapshot, type CartStateBundle, type CartStateHotkeyOptions, type CartStateMessageHandler, type CartStatePersister, type CreateRuntimeOptions, type CyberArtRuntime, type DimensionContext, type FrameErrorInfo, HostChannel, type HostEvent, type HostEventListener, type ImportCartStateExtras, IncompatibleCartStateError, KeyboardManager, type MountOptions, type PointerClick, PointerManager, Random, type TokenData, attachCartStatePersistence, createRuntime, registerCartStateHotkeys };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* @cyberart-io/engine
|
|
3
|
+
* Copyright (c) 2026 Aaron Boyarsky
|
|
4
|
+
* Licensed under the CyberArt Engine License. See LICENSE.
|
|
5
|
+
* Not an OSI open-source license.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
var Ft=(e,t)=>()=>(e&&(t=e(e=0)),t);var jt={};function Kt(e,t){let n=e.indexOf(yt);if(n===-1)return null;let r=n+yt.length,i=`const __cyberSkip=new Set(${JSON.stringify(t)});const __cyberOrig=registerProcessor;registerProcessor=(n,p)=>__cyberSkip.has(n)?undefined:__cyberOrig(n,p);`;return e.slice(0,r)+i+e.slice(r)}var Y,U,Ut,_,yt,wt=Ft(()=>{"use strict";Y=/AudioWorkletProcessor with name:\s*["'`][^"'`]+["'`]\s+is already registered/i,U="__cyberartWorkletPatchApplied";typeof window<"u"&&!window[U]&&(window.addEventListener("error",e=>{let t=e?.message??"";Y.test(t)&&(e.preventDefault(),e.stopImmediatePropagation())},!0),window.addEventListener("unhandledrejection",e=>{let t=e?.reason,n=typeof t=="string"?t:t?.message??"";Y.test(n)&&e.preventDefault()}),window[U]=!0);Ut=!1,_=(...e)=>{Ut&&console.log("[workletPatch]",...e)},yt="((AudioWorkletProcessor,registerProcessor)=>{";if(typeof AudioWorklet<"u"&&!AudioWorklet.prototype[U]){let e=new WeakMap,t=/registerProcessor\s*\(\s*['"`]([^'"`]+)['"`]/g,n=AudioWorklet.prototype.addModule,r=0;AudioWorklet.prototype.addModule=async function(i,s){let o=++r,u=e.get(this);u||(u=new Set,e.set(this,u)),_(`#${o} addModule`,{url:i.slice(0,64),isBlob:i.startsWith("blob:"),knownBefore:[...u]});let l=i,c=null;if(i.startsWith("blob:")){let h=null;try{h=await(await fetch(i)).text()}catch(d){_(`#${o} blob fetch failed`,d)}if(h!==null){let d=new Set;for(let m of h.matchAll(t))d.add(m[1]);if(d.size>0){let m=[...d].filter(C=>u.has(C)),S=[...d].filter(C=>!u.has(C));if(m.length>0&&S.length===0){_(`#${o} short-circuit \u2014 all processors already registered`,m);return}if(m.length>0&&S.length>0){let C=Kt(h,m);if(C!==null){let f=new Blob([C],{type:"application/javascript"});c=URL.createObjectURL(f),l=c,_(`#${o} rewrote blob; skipping`,m,"allowing",S)}else _(`#${o} blob rewrite failed; passing original through`)}S.forEach(C=>u.add(C))}}}return n.call(this,l,s).then(()=>{c&&URL.revokeObjectURL(c)},h=>{c&&URL.revokeObjectURL(c);let d=(h&&h.message)??"";if(!(Y.test(d)||/already registered/i.test(d)))throw h})},AudioWorklet.prototype[U]=!0}});var dt=()=>{let e=["ms","moz","webkit","o"];for(let t=0;t<e.length&&!window.requestAnimationFrame;++t)window.requestAnimationFrame=window[e[t]+"RequestAnimationFrame"],window.cancelAnimationFrame=window[e[t]+"CancelAnimationFrame"]||window[e[t]+"CancelRequestAnimationFrame"]},b=window.devicePixelRatio||1,k=/Headless/i.test(navigator.userAgent),Ae=/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)||/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.platform);function ut(){return Math.min(window.innerWidth,window.innerHeight)}var O="data-cyberart-canvas",T=class{constructor(t,n,r=1,a={}){this.width=t,this.height=n;let i=a.container,s=a.adoptFromDocument??!i,o=i?i.querySelector(`canvas[${O}]`)??i.querySelector("canvas"):null;if(o)this.canvas=o,this.adopted=!0;else if(s){let u=document.getElementById("canvas")??document.querySelector("canvas");u?(this.canvas=u,this.adopted=!0):(this.canvas=document.createElement("canvas"),this.canvas.setAttribute("id","canvas"),this.adopted=!1)}else this.canvas=document.createElement("canvas"),this.adopted=!1;this.canvas.hasAttribute(O)||this.canvas.setAttribute(O,""),this.canvas.style.display="block",this.canvas.style.margin="auto",this.ctx=this.canvas.getContext("2d",{colorSpace:"display-p3",alpha:!1}),this.ctx.globalAlpha=r,this.ctx.imageSmoothingEnabled=!1,this.setSize(t,n)}setSize(t,n){this.width=t,this.height=n,this.canvas.width=t,this.canvas.height=n,this.canvas.style.width=`${t/b}px`,this.canvas.style.height=`${n/b}px`,this.imageData=this.ctx.createImageData(t,n),this.ctx.putImageData(this.imageData,0,0)}_blank(t=1){let n=t?Math.max(t,.03):1;this.ctx.fillStyle=`rgba(0,0,0,${n})`,this.ctx.fillRect(0,0,this.width,this.height)}clearAll(){this.ctx.save(),this.ctx.globalAlpha=1,this._blank(),this.ctx.restore()}};function ct(e,t){let n=e/t,r,a;return n>1?(r=e,a=~~(e/n)):(a=t,r=~~(t*n)),{width:r,height:a,iWidth:1/r,iHeight:1/a,area:r*a,largeDim:Math.max(r,a),smallDim:Math.min(r,a),aspectRatio:n}}function ht(e,t=window.innerWidth,n=window.innerHeight){let r=ut(),a,i;return n<t?e>1?t>=r*e?(a=r*e,i=r):(a=t,i=a/e):(a=r*e,i=r):e>1?(a=r,i=r/e):n<r/e?(i=n,a=i*e):(a=r,i=r/e),[a,i]}var H=class{constructor(t=!1,n=!0){this.debugMode=t,this.actionMap={},this.checkKeypress=this.checkKeypress.bind(this),this.registerAction=this.registerAction.bind(this),n&&window.addEventListener("keydown",this.checkKeypress,!1)}checkKeypress({key:t}){this.debugMode&&console.log(`Pressed ${t}`),t in this.actionMap&&this.actionMap[t]()}registerAction(t,n,r=!1){if(t.length!==1)throw new Error(`Only single-character actions may be registered. Cannot register: ${t}`);if(t in this.actionMap&&!r)throw new Error(`Action already exists for code: ${t}`);this.actionMap[t]=n}destroy(){window.removeEventListener("keydown",this.checkKeypress,!1),this.actionMap={}}},lt=H;var B=class{constructor(t){this.x=-1;this.y=-1;this.isDown=!1;this.clicks=[];this.canvas=t,this.onPointerDown=this.onPointerDown.bind(this),this.onPointerMove=this.onPointerMove.bind(this),this.onPointerUp=this.onPointerUp.bind(this),t.addEventListener("pointerdown",this.onPointerDown),t.addEventListener("pointermove",this.onPointerMove),t.addEventListener("pointerup",this.onPointerUp)}toCanvasCoords(t){let n=this.canvas.getBoundingClientRect();return{x:(t.clientX-n.left)*(this.canvas.width/n.width),y:(t.clientY-n.top)*(this.canvas.height/n.height)}}onPointerDown(t){let{x:n,y:r}=this.toCanvasCoords(t);this.x=n,this.y=r,this.isDown=!0,this.clicks.push({x:n,y:r})}onPointerMove(t){let{x:n,y:r}=this.toCanvasCoords(t);this.x=n,this.y=r}onPointerUp(t){let{x:n,y:r}=this.toCanvasCoords(t);this.x=n,this.y=r,this.isDown=!1}hasClick(){return this.clicks.length>0}consumeClick(){return this.clicks.shift()??null}destroy(){this.canvas.removeEventListener("pointerdown",this.onPointerDown),this.canvas.removeEventListener("pointermove",this.onPointerMove),this.canvas.removeEventListener("pointerup",this.onPointerUp)}},pt=B;var mt=(e,t,n,r)=>({now:e,startTime:t,elapsedSinceStart:e-t,deltaSinceLastUpdate:n===null?0:e-n,deltaSinceLastRender:r===null?0:e-r}),N=class{constructor(t,n,r,a,i,s={}){this.startTime=0;this.lastUpdateAt=null;this.lastRenderAt=null;this.clockOffsetMs=0;this.pauseStartedAt=null;this.renderAnimation=this.renderAnimation.bind(this),this.dimensionContext=ct(t,n),this.R=r,this.gameManager=s.gameManager,this.hostChannel=s.hostChannel,this.canvas=new T(this.dimensionContext.width,this.dimensionContext.height,1,s.canvasMount),this.drawingContext=this.canvas.ctx,this.keyboardManager=new lt(!1,s.captureKeyboard??!0),this.pointerManager=new pt(this.canvas.canvas),this.animationCart=i;let o=s.customState;this.featureState=i.getDefaultFeatureState?.(this.R,this.dimensionContext,a,this.keyboardManager,o,this.pointerManager,this.gameManager,this.hostChannel),this.cartState=i.getDefaultState(this.R,this.dimensionContext,a,this.keyboardManager,o,this.pointerManager,this.gameManager,this.featureState,this.hostChannel),this.startTime=performance.now(),this.canvas.clearAll()}captureFramebuffer(){let t=this.canvas.imageData,n=new ImageData(t.width,t.height);return n.data.set(t.data),n}restoreFramebuffer(t){let n=this.canvas.imageData;return n.width!==t.width||n.height!==t.height||n.data.length!==t.data.length?!1:(n.data.set(t.data),this.drawingContext.putImageData(n,0,0),!0)}setPaused(t){let n=performance.now();if(t){this.pauseStartedAt===null&&(this.pauseStartedAt=n);return}this.pauseStartedAt!==null&&(this.clockOffsetMs+=n-this.pauseStartedAt,this.pauseStartedAt=null)}resolveNow(t){let n=this.pauseStartedAt!==null?t-this.pauseStartedAt:0;return t-this.clockOffsetMs-n}getElapsedSinceStart(t=performance.now()){return Math.max(0,this.resolveNow(t)-this.startTime)}restoreClock(t,n){this.pauseStartedAt=null,this.lastUpdateAt=null,this.lastRenderAt=null;let r=Math.max(0,t);if(typeof n=="number"&&Number.isFinite(n)){this.startTime=n,this.clockOffsetMs=performance.now()-n-r;return}this.clockOffsetMs=0,this.startTime=performance.now()-r}async updateAnimationState(t,n,r=performance.now()){let a=this.resolveNow(r),i=mt(a,this.startTime,this.lastUpdateAt,this.lastRenderAt);this.cartState=this.animationCart.update(this.R,t,n,this.dimensionContext,this.cartState,this.keyboardManager,this.pointerManager,this.gameManager,i,this.featureState,this.hostChannel),this.lastUpdateAt=a}async renderAnimation(t,n,r=performance.now()){let a=this.resolveNow(r),i=mt(a,this.startTime,this.lastUpdateAt,this.lastRenderAt);this.animationCart.render(this.R,t,n,this.dimensionContext,this.cartState,this.drawingContext,this.canvas.imageData,this.pointerManager,this.gameManager,i,this.featureState,this.hostChannel),this.lastRenderAt=a}getCanvas(){return this.canvas.canvas}getDrawDetails(){return[this.canvas.canvas,0,0,this.dimensionContext.width,this.dimensionContext.height]}teardown(){try{this.animationCart.teardown?.(this.cartState,this.featureState)}catch(t){console.error("Error during cart teardown",t)}this.keyboardManager.destroy(),this.pointerManager.destroy()}};var W=class extends T{constructor(t,n,r=1,a=!0,i={}){if(super(t,n,r,i),this.adopted)return;let s=i.container;if(s){this.canvas.parentNode!==s&&s.appendChild(this.canvas);return}if(a){let o=document.getElementById("canvas");o&&o!==this.canvas&&o.parentNode===document.body&&document.body.removeChild(o),document.body.appendChild(this.canvas)}}};var I=class{constructor(t){this.useA=!1;let n=function(r){let a=parseInt(r.substring(0,8),16),i=parseInt(r.substring(8,8),16),s=parseInt(r.substring(16,8),16),o=parseInt(r.substring(24,8),16);return function(){a|=0,i|=0,s|=0,o|=0;let u=(a+i|0)+o|0;return o=o+1|0,a=i^i>>>9,i=s+(s<<3)|0,s=s<<21|s>>>11,s=s+u|0,(u>>>0)/4294967296}};this.prngA=n(t.hash.substring(2,32)),this.prngB=n(t.hash.substring(34,32));for(let r=0;r<1e6;r+=2)this.prngA(),this.prngB()}r_zero_one(){return this.useA=!this.useA,this.useA?this.prngA():this.prngB()}dec(t,n){return t===void 0?this.r_zero_one():n===void 0?t*this.r_zero_one():t+(n-t)*this.r_zero_one()}int(t,n){return n===void 0&&(n=t,t=0),Math.floor(this.dec(t,n))}bool(t=.5){return this.r_zero_one()<t}sign(){return this.bool()?1:-1}choose(t){return t[this.int(t.length)]}};var gt="bafybeicjwcq5lxxtfnyj4p2ugcr7ctyb5wl62cbcechj6tsabdq5whcs7u",ft="https://ipfs.io/ipfs/";function Ot(e){return e.endsWith("/")?e:`${e}/`}var L=null;function Ht(e){return[...Array(e)].map(()=>Math.floor(Math.random()*16).toString(16)).join("")}function Bt(e){return e&&e.length>0?e:[{cid:gt}]}function Nt(e){return e.startsWith("0x")?e:`0x${e}`}function Wt(e){if(!e&&typeof tokenData<"u"&&tokenData?.hash)return{hash:tokenData.hash,tokenId:String(tokenData.tokenId),externalAssetDependencies:Bt(tokenData.externalAssetDependencies),preferredIPFSGateway:Ot(tokenData.preferredIPFSGateway||ft),preferredArweaveGateway:tokenData.preferredArweaveGateway};let t=typeof window<"u"?new URLSearchParams(window.location.search):new URLSearchParams;return{hash:e?Nt(e):t.get("hash")||`0x${Ht(64)}`,tokenId:"18009999",externalAssetDependencies:[{cid:gt}],preferredIPFSGateway:ft}}function q(e){let t=e?.useCache!==!1;if(t&&L&&!e?.hash)return L;let n=Wt(e?.hash);return t?L=n:L||(L=n),n}function bt(e){return new Array(32).fill(null).map((t,n)=>parseInt(e.hash.slice(2+n*2,4+n*2),16))}async function J(){let e=await import("tone");if(typeof e.start=="function")return e;let t=e.default;return t&&typeof t.start=="function"?t:e}var St={id:"tone",async load(){await Promise.resolve().then(()=>(wt(),jt))},async unlock(){await St.load(),await(await J()).start()},async resumeIfSuspended(){let e=await J();e.context.state==="suspended"&&await e.context.resume()},async suspendIfRunning(){let t=(await J()).context.rawContext;"suspend"in t&&t.state==="running"&&await t.suspend()}},Ct={tone:St};function X(e){if(!e)return[];let t=Array.isArray(e)?e:[e],n=[];for(let r of t){if(!(r in Ct)){console.warn(`Unknown audio library "${r}"`);continue}let a=r;n.includes(a)||n.push(a)}return n}function Q(e){return e.map(t=>Ct[t])}var K="$cyberartTa",g=class extends Error{constructor(t){super(t),this.name="IncompatibleCartStateError"}},Z={Float32Array,Float64Array,Int8Array,Int16Array,Int32Array,Uint8Array,Uint8ClampedArray,Uint16Array,Uint32Array};function Vt(e){for(let t of Object.keys(Z))if(e instanceof Z[t])return t}function At(e){return ArrayBuffer.isView(e)&&!(e instanceof DataView)}function y(e){if(e===null||typeof e!="object"||Array.isArray(e))return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function tt(e){return y(e)?typeof e[K]=="string"&&Array.isArray(e.d):!1}function $t(e){return e==null?!1:typeof e=="function"?!0:!(typeof e!="object"||Array.isArray(e)||At(e)||y(e))}var Gt=new Set(["audioContextStarted","midiSynths","keepRatioPrimed","audioInitStarted","musicSeekApplied"]);function vt(e){return e===null?"null":tt(e)?String(e[K]):Array.isArray(e)?"array":y(e)?"object":typeof e}function zt(e,t){return e===t||e==="null"||t==="null"}function A(e){return e.trim().toLowerCase().replace(/^0x/,"")}function Et(e,t,n,r){if(!(!n&&!r)&&A(e)!==A(t))throw new g(`Generative cart state is for seed "${t}", but the live token is "${e}"`)}function P(e){return typeof e=="number"&&Number.isFinite(e)&&e>0}function xt(e,t){let n=e.dimensions;if(n&&P(n.width)&&P(n.height))return n;if(t&&P(t.width)&&P(t.height))return{width:t.width,height:t.height};if(y(e.state)){let r=e.state.containerWidth,a=e.state.containerHeight;if(P(r)&&P(a))return{width:r,height:a}}return null}function D(e,t=new WeakSet){if(e===null||typeof e=="string"||typeof e=="boolean")return e;if(typeof e=="number")return Number.isFinite(e)?e:null;if(!(typeof e=="function"||typeof e>"u"||typeof e=="bigint"||typeof e=="symbol")){if(At(e)){let n=Vt(e);return n?{[K]:n,d:Array.from(e)}:void 0}if(typeof e=="object"&&!t.has(e)){t.add(e);try{if(Array.isArray(e)){let a=[],i=0;for(let s of e){let o=D(s,t);o!==void 0&&(a.push(o),i++)}return e.length>0&&i===0?void 0:a}if(!y(e)||$t(e))return;let n=Object.keys(e),r={};for(let a of n){if(Gt.has(a))continue;let i=D(e[a],t);i!==void 0&&(r[a]=i)}return n.length>0&&Object.keys(r).length===0?void 0:r}finally{t.delete(e)}}}}function F(e){if(tt(e)){let t=Z[e[K]];return t?new t(e.d.map(n=>Number(n))):e}if(Array.isArray(e))return e.map(t=>F(t));if(y(e)){let t={};for(let n of Object.keys(e))t[n]=F(e[n]);return t}return e}function et(e,t,n,r,a=!0){if(a&&n&&r&&n!==r)throw new g(`Cart state is for "${r}", but the live cart is "${n}"`);if(!y(t)){if(a)throw new g("Cart state snapshot is empty");return}let i=Object.keys(t);if(i.length===0){if(a)throw new g("Cart state snapshot is empty");return}let s=D(e);if(!y(s)||Object.keys(s).length===0){if(a)throw new g("Live cart has no serializable state");return}let o=i.filter(u=>u in s);if(o.length===0){if(a)throw new g("Cart state snapshot does not overlap the live cart shape");return}for(let u of o){let l=vt(s[u]),c=vt(t[u]);if(!zt(l,c))throw new g(`Cart state field "${u}" has type ${c}, live cart expects ${l}`);if(l==="object"){let h=t[u],d=e[u];y(h)&&!tt(h)&&et(d,h,void 0,void 0,!1)}}}function kt(e,t){if(!y(e)||!y(t))return;let n=D(e);if(y(n))for(let r of Object.keys(t))r in n&&(e[r]=F(t[r]))}function Tt(e){let t=typeof e=="string"?JSON.parse(e):e;if(!t||typeof t!="object")throw new g("Cart state bundle is not an object");if(t.version!==1)throw new g(`Unsupported cart state version ${String(t.version)}`);if(typeof t.seed!="string"||typeof t.framesElapsed!="number")throw new g("Cart state bundle is missing seed or framesElapsed");return t}function Yt(e){if(!e||typeof e!="object")return 0;let t=e;return typeof t.startTime=="number"&&typeof t.lastSimulationUpdateAt=="number"?Math.max(0,t.lastSimulationUpdateAt-t.startTime):0}function Jt(e,t){if(typeof window.resizeTo!="function")return!1;try{if(window.top!==window)return!1}catch{return!1}let n=Math.max(1,Math.round(e)),r=Math.max(1,Math.round(t)),a=Math.max(0,window.outerWidth-window.innerWidth),i=Math.max(0,window.outerHeight-window.innerHeight);try{window.resizeTo(n+a,r+i)}catch{return!1}return Math.abs(window.innerWidth-n)<=8&&Math.abs(window.innerHeight-r)<=8}var Xt=[[0,0],[16,9],[9,16],[4,3],[3,4],[2,1],[1,2],[3,1],[1,3],[1,1]],rt=Xt[0],Qt=rt[0]/rt[1],nt=30,Zt=120,te=3840/2160,ee=2160/3840,j=class{constructor(t=nt,n=!1,r=window.innerWidth,a=window.innerHeight,i=!0,s,o={}){this.framesElapsed=0;this.frameRate=nt;this._paused=!1;this.savedToken=!1;this.prepared=!1;this.loopRunning=!1;this.fallbackAudio=[];this.audioLibrariesInternal=[];this.consecutiveErrorCount=0;this.lastErrorLogAt=0;this.reinitStayPaused=!1;this.reinitPending=!1;this.lastExportedFramebuffer=null;this.pinnedOutput=null;this.pinViewportAtImport=null;this.importing=!1;this.drawLoop=this.drawLoop.bind(this),this.loadCart=this.loadCart.bind(this),this.prepareCart=this.prepareCart.bind(this),this.beginPlayback=this.beginPlayback.bind(this),this.reinit=this.reinit.bind(this),this.tuneFramerate=this.tuneFramerate.bind(this),this.container=o.container,this.seed=o.seed,this.captureKeyboard=o.captureKeyboard??!0,this.customState=o.customState,this.hostChannel=o.hostChannel,this.fallbackAudio=X(o.audio),this.audioLibrariesInternal=this.fallbackAudio,this.isBodyCanvas=this.container?!1:i,this.autoMode=n,this.aspectRatio=Qt,this.selectedAsp=rt,this.isFullscreen=!!document.fullscreenElement,this.updateFrameRate(t),this.tempUnpause=!1,this.synchronous=k,this.containerWidth=r,this.containerHeight=a,this.gameManager=s,this.init(!0)}setGameManager(t){this.gameManager=t}reinit(t){if(!this.importing){if(this.shouldKeepPinnedOutput()){this.fitPinnedCanvas();return}this.reinitPending||(this.reinitStayPaused=this._paused,this.reinitPending=!0),this.paused=!0,clearTimeout(this.reinitTimeout),this.reinitTimeout=setTimeout(()=>{if(this.importing){this.paused=this.reinitStayPaused,this.reinitPending=!1;return}this.updateInProgress?this.reinit(t):(this.requestedAnimationFrame&&cancelAnimationFrame(this.requestedAnimationFrame),this.clearPinnedOutput(),this.init(),this.reloadCart(),this.paused=this.reinitStayPaused,this.reinitPending=!1)},150)}}initSeed(){this.tokenData=this.seed?q({hash:this.seed,useCache:!1}):q(),this.rawParams=bt(this.tokenData),this.mintNo=parseInt(this.tokenData.tokenId)%1e6,console.log("token",this.mintNo,this.tokenData.hash)}init(t=!1){t&&this.initSeed(),this.pureAspectUpdate(this.selectedAsp);let n=this.aspectRatio,[r,a]=ht(n,this.containerWidth,this.containerHeight);r*=b,a*=b,this.width=~~r,this.height=~~a,this.atCenterX=this.width/2,this.atCenterY=this.height/2,this.mainCanvas?this.mainCanvas.setSize(this.width,this.height):this.mainCanvas=new W(this.width,this.height,1,this.isBodyCanvas,this.canvasMountOptions())}loadCart(t){this.prepareCart(t)&&this.beginPlayback()}prepareCart(t){if(this.prepared)return!0;try{let n=~~(this.width/1),r=~~(this.height/this.width*n),a=new I(this.tokenData),i=new N(n,r,a,this.rawParams,t,{gameManager:this.gameManager,canvasMount:this.canvasMountOptions(),customState:this.customState,captureKeyboard:this.captureKeyboard,hostChannel:this.hostChannel});return this.animation=i,this.animationCart=t,this.paused=!1,this.framesElapsed=0,this.R=a,this.applyCartFrameRate(t,i),this.resolveAudioLibraries(t),this.prepared=!0,!0}catch(n){return console.error(n),!1}}applyCartFrameRate(t,n){let r=t.metadata,a=r?.frameRate??nt,i=n.cartState?.visualMode??n.featureState?.visualMode,s=i&&r?.frameRateByVisualMode?r.frameRateByVisualMode[i]:void 0;this.updateFrameRate(typeof s=="number"&&s>0?s:a)}get audioLibraries(){return this.audioLibrariesInternal}get needsAudio(){return this.audioLibrariesInternal.length>0}resolveAudioLibraries(t){this.audioLibrariesInternal=X(t.metadata?.audio)}async unlockAudio(){if(this.audioLibrariesInternal.length===0)return;let t=Q(this.audioLibrariesInternal);if(k){for(let n of t)await n.load();return}for(let n of t)await n.unlock()}async resumeAudioLibraries(){if(!(this.paused||k))for(let t of Q(this.audioLibrariesInternal))try{await t.resumeIfSuspended()}catch(n){console.error(`Failed to resume audio library "${t.id}" after reload`,n)}}beginPlayback(){!this.prepared||this.loopRunning||(this.prepared=!1,this.loopRunning=!0,this.consecutiveErrorCount=0,this.requestedAnimationFrame=requestAnimationFrame(this.drawLoop))}get isPrepared(){return this.prepared}get isLoopRunning(){return this.loopRunning}get paused(){return this._paused}set paused(t){this._paused!==t&&(this._paused=t,this.animation?.setPaused(t))}async waitUntilUpdateIdle(){for(;this.updateInProgress;)await new Promise(t=>{typeof requestAnimationFrame=="function"?requestAnimationFrame(()=>t()):setTimeout(t,0)})}requirePreparedAnimation(){if(!this.animation)throw new Error("No prepared cart");return this.animation}async exportState(){let t=this.requirePreparedAnimation(),n=this._paused;this.paused=!0,await this.waitUntilUpdateIdle();try{this.lastExportedFramebuffer=t.captureFramebuffer();let r=t.getCanvas();return{version:1,cartId:this.animationCart?.metadata?.id,generative:this.animationCart?.metadata?.generative,seed:this.tokenData.hash,framesElapsed:this.framesElapsed,elapsedSinceStart:t.getElapsedSinceStart(),dimensions:{width:r.width,height:r.height,cssWidth:this.containerWidth,cssHeight:this.containerHeight,dpr:b},state:D(t.cartState)}}finally{this.paused=n}}async exportStateJSON(){return JSON.stringify(await this.exportState())}peekExportedFramebuffer(){return this.lastExportedFramebuffer}peekSeed(){return this.tokenData?.hash}isGenerative(){return this.animationCart?.metadata?.generative===!0}async importState(t,n){let r=this.requirePreparedAnimation(),a=Tt(t),i=this._paused,s=this.loopRunning;this.paused=!0,await this.waitUntilUpdateIdle(),et(r.cartState,a.state,this.animationCart?.metadata?.id,a.cartId),Et(this.tokenData.hash,a.seed,this.animationCart?.metadata?.generative,a.generative),clearTimeout(this.reinitTimeout),this.reinitPending=!1,this.importing=!0;let o=xt(a,n?.framebuffer??this.lastExportedFramebuffer);if(o){this.pinOutputSize(o);let c=o.cssWidth??o.width/b,h=o.cssHeight??o.height/b;Jt(c,h),this.syncContainerFromDom(),this.pinViewportAtImport={w:this.containerWidth,h:this.containerHeight}}let u=this.customState,l=!1;try{if(this.customState=F(a.state),this.unloadCart(),!this.prepareCart(this.animationCart))throw new Error("Failed to prepare cart while loading state");this.animation.cartState!==this.customState&&kt(this.animation.cartState,a.state),this.framesElapsed=a.framesElapsed;let c=this.animation,h=c.cartState,d=typeof a.elapsedSinceStart=="number"?a.elapsedSinceStart:Yt(h);c.restoreClock(d,typeof h?.startTime=="number"?h.startTime:void 0),i&&c.setPaused(!0);let m=n?.framebuffer??this.lastExportedFramebuffer;m&&c.restoreFramebuffer(m)?h.keepRatioPrimed=!0:await c.renderAnimation(this.framesElapsed,this.rawParams),this.fitPinnedCanvas(),l=!0}catch(c){throw this.clearPinnedOutput(),this.customState=u,this.paused=i,this.animationCart&&(this.syncContainerFromDom(),this.init(),this.reloadCart()),c}finally{this.importing=!1,l&&(this.customState=u,this.paused=i,s&&this.beginPlayback())}}unloadCart(){this.loopRunning=!1,this.requestedAnimationFrame&&cancelAnimationFrame(this.requestedAnimationFrame),this.drawLoopTimeout&&clearTimeout(this.drawLoopTimeout),this.animation?.teardown(),this.animation=void 0,this.prepared=!1,this.audioLibrariesInternal=this.fallbackAudio,this.mainCanvas?.clearAll()}reloadCart(t=!1){let n=this.animationCart;if(!n)return;this.pinnedOutput&&(this.clearPinnedOutput(),this.syncContainerFromDom(),this.init());let r=this.reinitPending?this.reinitStayPaused:this._paused;this.unloadCart(),t&&this.initSeed(),this.loadCart(n),this.paused=r,!this.paused&&!k&&this.resumeAudioLibraries()}getFrameRate(){return this.frameRate}updateFrameRate(t){if(this.frameRate=t,t>=1)this.targetDrawTime=1e3/t;else{let r=1/(2-t);this.targetDrawTime=1e3/r}this.drawTime=this.targetDrawTime}canvasMountOptions(){return{container:this.container,adoptFromDocument:!this.container}}syncContainerFromDom(){if(this.container){this.containerWidth=this.container.clientWidth||this.containerWidth,this.containerHeight=this.container.clientHeight||this.containerHeight;return}this.containerWidth=window.innerWidth,this.containerHeight=window.innerHeight}pinOutputSize(t){this.pinnedOutput=t,this.width=~~t.width,this.height=~~t.height,this.atCenterX=this.width/2,this.atCenterY=this.height/2,this.aspectRatio=this.width/this.height,this.mainCanvas&&this.mainCanvas.setSize(this.width,this.height)}shouldKeepPinnedOutput(){return this.pinnedOutput?(this.syncContainerFromDom(),this.viewportMatches(this.pinnedCssSize())?!0:!!(this.pinViewportAtImport&&this.viewportMatches(this.pinViewportAtImport))):!1}pinnedCssSize(){let t=this.pinnedOutput;return{w:t.cssWidth??t.width/b,h:t.cssHeight??t.height/b}}viewportMatches(t){return Math.abs(this.containerWidth-t.w)<8&&Math.abs(this.containerHeight-t.h)<8}fitPinnedCanvas(){let t=this.pinnedOutput,n=this.canvas;if(!t||!n)return;this.container&&!this.container.style.position&&(this.container.style.position="relative");let r=this.containerWidth||window.innerWidth,a=this.containerHeight||window.innerHeight,i=t.width/b,s=t.height/b;if(i<1||s<1||r<1||a<1)return;let o=Math.min(r/i,a/s),u=i*o,l=s*o;n.style.position="absolute",n.style.left="50%",n.style.top="50%",n.style.transform="translate(-50%, -50%)",n.style.margin="0",n.style.width=`${u}px`,n.style.height=`${l}px`,n.style.imageRendering=Math.abs(o-1)<.01?"auto":"pixelated"}clearPinnedOutput(){let t=this.canvas;this.pinnedOutput=null,this.pinViewportAtImport=null,t&&(t.style.position="",t.style.left="",t.style.top="",t.style.transform="",t.style.imageRendering="",t.style.width="",t.style.height="",t.style.margin="auto")}get canvas(){return this.animation?.getCanvas()??this.mainCanvas?.canvas}destroy(){this.clearPinnedOutput(),this.unloadCart(),clearTimeout(this.reinitTimeout),this.mainCanvas&&!this.mainCanvas.adopted&&this.mainCanvas.canvas.remove()}drawIt(){}tuneFramerate(t){let n=this.lastCall||t;this.lastCall=t,this.drawTime=Math.max(0,this.drawTime+this.targetDrawTime-this.lastCall+n)}async drawLoop(t){if(!this.loopRunning)return;let n=!1,r,a="update";try{if(!this.paused||this.tempUnpause){if(k||this.tuneFramerate(t),!this.animation)return;if(a="update",this.updateInProgress=!0,await this.animation.updateAnimationState(this.framesElapsed,this.rawParams,t),this.updateInProgress=!1,a="render",await this.animation.renderAnimation(this.framesElapsed,this.rawParams,t),this.autoMode&&this.animation.cartState.saveToken&&!this.savedToken&&!new URLSearchParams(window.location.search).get("hash")){let o=localStorage.getItem("goodones"),u=JSON.stringify(o?[...JSON.parse(o),this.tokenData.hash]:[this.tokenData.hash]);localStorage.setItem("goodones",u),this.savedToken=!0,localStorage.setItem(this.tokenData.hash,this.mainCanvas.canvas.toDataURL("image/png")),window.location.reload(),this.loopRunning=!1;return}this.framesElapsed++,this.tempUnpause&&(this.tempUnpause=!1)}a="draw",this.drawIt()}catch(i){n=!0,r=i,this.handleFrameError(i,a)}finally{this.updateInProgress=!1}n?(this.consecutiveErrorCount++,this.consecutiveErrorCount>=Zt&&(this.loopRunning=!1,this.onError?.(r,{phase:a,consecutive:this.consecutiveErrorCount,stopped:!0}))):this.consecutiveErrorCount=0,this.loopRunning&&(this.drawLoopTimeout=setTimeout(()=>{this.requestedAnimationFrame=requestAnimationFrame(this.drawLoop)},this.drawTime))}handleFrameError(t,n){let r=performance.now();r-this.lastErrorLogAt>=1e3&&(this.lastErrorLogAt=r,console.error(`Animation ${n} error:`,t)),this.consecutiveErrorCount===0&&this.onError?.(t,{phase:n,consecutive:1,stopped:!1})}pureAspectUpdate(t){if(t[0]===0&&t[1]===0){let r=this.containerWidth||window.innerWidth,a=this.containerHeight||window.innerHeight,i=r/a;this.aspectRatio=Math.min(te,Math.max(ee,i))}else this.aspectRatio=t[0]/t[1]}};var M=class{constructor(){this.inbound=[];this.listeners=[]}dispatch(t){this.inbound.push(t),this.inbound.length>32&&this.inbound.splice(0,this.inbound.length-32)}consume(){if(this.inbound.length===0)return[];let t=this.inbound;return this.inbound=[],t}emit(t){for(let n of this.listeners)n(t)}onEvent(t){return this.listeners.push(t),()=>{this.listeners=this.listeners.filter(n=>n!==t)}}clear(){this.inbound=[],this.listeners=[]}};function ne(e){dt();let{container:t,seed:n,captureKeyboard:r=!1,audio:a}=e,i=new M,s=!1,o=null,u,l=!1,c=t.clientWidth||window.innerWidth,h=t.clientHeight||window.innerHeight,d=new j(30,!1,c,h,!1,void 0,{container:t,seed:n,captureKeyboard:r,hostChannel:i,audio:a}),m=()=>{let f=t.clientWidth||window.innerWidth,v=t.clientHeight||window.innerHeight;return f===d.containerWidth&&v===d.containerHeight?!1:(d.containerWidth=f,d.containerHeight=v,!0)},S=null;return typeof ResizeObserver<"u"&&(S=new ResizeObserver(()=>{if(s||!o)return;m()&&l&&d.reinit()}),S.observe(t)),d.onError=(f,v)=>{u?.(f,v)},{get tokenData(){return d.tokenData},get onError(){return u},set onError(f){u=f},mount(f,v={}){if(s)throw new Error("createRuntime: cannot mount on a destroyed runtime");o?.destroy(),l=!1,d.customState=v.initialState,d.setGameManager(v.gameManager);let ot=v.onEvent?i.onEvent(v.onEvent):()=>{};if(!d.prepareCart(f))throw ot(),new Error("createRuntime: failed to prepare cart");let p=!1,z={async start(){if(!p){if(m()&&d.isPrepared&&(d.init(),d.unloadCart(),!d.prepareCart(f)))throw new Error("createRuntime: failed to prepare cart");d.needsAudio&&(await d.unlockAudio(),p)||(l=!0,d.beginPlayback())}},pause(){p||(d.paused=!0)},resume(){p||(d.paused=!1)},dispatch(w){p||i.dispatch(w)},snapshot(){if(p)return{seed:"",pngDataUrl:""};let w=d.canvas;return{seed:d.tokenData.hash,metadata:f.metadata,pngDataUrl:w?w.toDataURL("image/png"):""}},destroy(){p||(p=!0,l=!1,ot(),i.clear(),d.unloadCart(),o===z&&(o=null))},reload(){if(!p){if(!l){if(d.isPrepared&&(d.unloadCart(),!d.prepareCart(f)))throw new Error("createRuntime: failed to prepare cart");return}d.reloadCart()}},reinit(w){if(!p){if(!l){m();return}d.reinit(w)}},getCartState(){return d.animation?.cartState},exportState(){return p?Promise.reject(new Error("Cart handle has been destroyed")):d.exportState()},exportStateJSON(){return p?Promise.reject(new Error("Cart handle has been destroyed")):d.exportStateJSON()},async importState(w,_t){if(p)throw new Error("Cart handle has been destroyed");await d.importState(w,_t)},peekExportedFramebuffer(){return p?null:d.peekExportedFramebuffer()},peekSeed(){if(!p)return d.peekSeed()},isGenerative(){return p?!1:d.isGenerative()},get canvas(){return d.canvas},get paused(){return d.paused},set paused(w){p||(d.paused=w)},get tokenData(){return d.tokenData},get isPrepared(){return d.isPrepared},get isLoopRunning(){return d.isLoopRunning},get needsAudio(){return d.needsAudio},get audioLibraries(){return d.audioLibraries}};return o=z,z},async unlockAudio(){await d.unlockAudio()},destroy(){s||(s=!0,o?.destroy(),o=null,S?.disconnect(),S=null,i.clear(),d.destroy())}}}var V="cyberart.state.save",$="cyberart.state.load";function re(e,t,n={}){let r=n.persist,a=r?{persist:r}:void 0,i=()=>{t.emit({type:V,payload:a})},s=()=>{t.emit({type:$,payload:a})};e.registerAction("w",i,!0),e.registerAction("W",i,!0),e.registerAction("e",s,!0),e.registerAction("E",s,!0)}function Pt(e){if(e.type!==V&&e.type!==$)return;let t=e.payload?.persist;return t==="localStorage"?t:void 0}var ae="cyberart-io",ie=1,E="framebuffers";function at(e,t){return`${e||"unknown"}:${t?A(t):"none"}`}function Mt(e,t){return`${e||"unknown"}:${t||"none"}`}function it(){return new Promise((e,t)=>{let n=indexedDB.open(ae,ie);n.onupgradeneeded=()=>{let r=n.result;r.objectStoreNames.contains(E)||r.createObjectStore(E)},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error??new Error("indexedDB open failed"))})}function se(e){let t=e.data;return t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength)}function Dt(e,t){return new Promise((n,r)=>{let i=e.transaction(E,"readonly").objectStore(E).get(t);i.onsuccess=()=>n(i.result),i.onerror=()=>r(i.error??new Error("indexedDB read failed"))})}async function Rt(e,t,n){if(typeof indexedDB>"u")return;let r=await it(),a={width:n.width,height:n.height,data:se(n)};try{await new Promise((i,s)=>{let o=r.transaction(E,"readwrite");o.oncomplete=()=>i(),o.onerror=()=>s(o.error??new Error("indexedDB write failed")),o.objectStore(E).put(a,at(e,t))})}finally{r.close()}}async function It(e,t){if(typeof indexedDB>"u")return null;let n=await it();try{let r=at(e,t),a=Mt(e,t),i=await Dt(n,r)??(r===a?void 0:await Dt(n,a));if(!i||!i.data||i.width<1||i.height<1)return null;let s=new ImageData(i.width,i.height);return s.data.set(new Uint8ClampedArray(i.data)),s}finally{n.close()}}async function Lt(e,t){if(typeof indexedDB>"u")return;let n=await it();try{await new Promise((r,a)=>{let i=n.transaction(E,"readwrite");i.oncomplete=()=>r(),i.onerror=()=>a(i.error??new Error("indexedDB delete failed"));let s=i.objectStore(E);s.delete(at(e,t)),s.delete(Mt(e,t))})}finally{n.close()}}var st="cyberart.state.",oe="Saved state",de="Loaded state",ue="Failed to save state",ce="Failed to load state",he="No saved state";function x(e,t){let n=e||"unknown";return t?st+n+"."+A(t):st+n}function le(e,t,n){return r=>{if(Pt(r)==="localStorage"){if(r.type===V){pe(e,t,n);return}r.type===$&&me(e,t,n)}}}function R(e){try{let t=JSON.parse(e);if(!t||typeof t!="object")return null;let n=t;if(typeof n.savedAt=="number"&&n.bundle&&typeof n.bundle=="object")return{savedAt:n.savedAt,bundle:n.bundle};let r=t;return typeof r.seed=="string"&&typeof r.version=="number"?{savedAt:0,bundle:r}:null}catch{return null}}async function pe(e,t,n){let r=e();if(r)try{let a=await r.exportState(),i=a.cartId??t?.();a.generative===!0?(Ce(i),G(x(i,a.seed),a)):G(x(i),a);let s=r.peekExportedFramebuffer?.();if(s)try{await Rt(i,a.seed,s)}catch(o){console.warn("Failed to save cart framebuffer",o)}n?.(oe,"transient")}catch(a){console.error("Failed to save cart state",a),n?.(ue,"transient")}}async function me(e,t,n){let r=e();if(!r)return;let a=t?.(),i=r.peekSeed?.(),s=r.isGenerative?.()===!0,o=we(a,s,i);if(!o){console.warn("No saved cart state for",a||"unknown",s&&i?`(hash ${i})`:""),n?.(he,"transient");return}try{let u=await It(a,o.bundle.seed).catch(()=>null);await r.importState(o.bundle,{framebuffer:u}),n?.(de,"transient")}catch(u){console.error("Failed to load cart state",u),n?.(ce,"transient")}}function G(e,t){let n=JSON.stringify({savedAt:Date.now(),bundle:t});fe(e,n)}function fe(e,t){for(;;)try{localStorage.setItem(e,t);return}catch(n){if(!ge(n)||!be(e))throw n}}function ge(e){if(!e||typeof e!="object")return!1;let t=e.name,n=e.code;return t==="QuotaExceededError"||t==="NS_ERROR_DOM_QUOTA_REACHED"||n===22||n===1014}function be(e){let t=null,n=1/0;for(let i of ye()){if(i===e)continue;let s=localStorage.getItem(i);if(!s)continue;let u=R(s)?.savedAt??0;u<n&&(n=u,t=i)}if(!t)return!1;let r=localStorage.getItem(t),a=r?R(r):null;return localStorage.removeItem(t),a?.bundle&&Lt(a.bundle.cartId,a.bundle.seed).catch(()=>{}),!0}function ye(){let e=[];for(let t=0;t<localStorage.length;t++){let n=localStorage.key(t);n&&n.startsWith(st)&&e.push(n)}return e}function we(e,t,n){if(t&&n){let a=localStorage.getItem(x(e,n));if(a)return R(a);let i=localStorage.getItem(x(e));if(!i)return null;let s=R(i);if(!s||!Se(s.bundle,n))return null;try{G(x(e,n),s.bundle)}catch{}return s}let r=localStorage.getItem(x(e));return r?R(r):null}function Se(e,t){return typeof e.seed=="string"&&A(e.seed)===A(t)}function Ce(e){let t=x(e),n=localStorage.getItem(t);if(!n)return;let r=R(n);if(r&&typeof r.bundle.seed=="string"){let a=x(e,r.bundle.seed);if(!localStorage.getItem(a))try{G(a,r.bundle)}catch{return}}localStorage.removeItem(t)}export{O as CYBERART_CANVAS_ATTR,M as HostChannel,g as IncompatibleCartStateError,H as KeyboardManager,B as PointerManager,I as Random,le as attachCartStatePersistence,ne as createRuntime,re as registerCartStateHotkeys};
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cyberart-io/engine",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Embeddable CyberArt host engine: mount a cart, pause, snapshot, and save/load state.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist/index.js",
|
|
9
|
+
"dist/index.d.ts"
|
|
10
|
+
],
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"module": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/cyberart-io/cyberart.io.git",
|
|
24
|
+
"directory": "packages/engine"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/cyberart-io/cyberart.io/tree/main/packages/engine",
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"cyberart",
|
|
32
|
+
"generative-art",
|
|
33
|
+
"canvas",
|
|
34
|
+
"animation"
|
|
35
|
+
],
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"tone": "^14.8.15"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"tone": {
|
|
41
|
+
"optional": true
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "node ../../scripts/build-engine.mjs",
|
|
46
|
+
"prepublishOnly": "node ../../scripts/build-engine.mjs"
|
|
47
|
+
}
|
|
48
|
+
}
|