koota-kit 0.4.0 → 0.6.0
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/CHANGELOG.md +21 -0
- package/README.md +66 -1
- package/dist/cjs/index.cjs +5 -2
- package/dist/cjs/index.d.cts +2 -0
- package/dist/cjs/index.d.cts.map +1 -1
- package/dist/cjs/react.cjs +39 -0
- package/dist/cjs/react.d.cts +21 -0
- package/dist/cjs/react.d.cts.map +1 -0
- package/dist/cjs/schedule.cjs +131 -0
- package/dist/cjs/schedule.d.cts +69 -0
- package/dist/cjs/schedule.d.cts.map +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +3 -1
- package/dist/esm/react.d.ts +21 -0
- package/dist/esm/react.d.ts.map +1 -0
- package/dist/esm/react.js +34 -0
- package/dist/esm/schedule.d.ts +69 -0
- package/dist/esm/schedule.d.ts.map +1 -0
- package/dist/esm/schedule.js +128 -0
- package/docs/API.md +85 -3
- package/docs/ARCHITECTURE.md +28 -3
- package/package.json +43 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.0](https://github.com/jbcom/koota-kit/compare/v0.5.0...v0.6.0) (2026-10-10)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **schedule:** add createSchedule, a fixed-step system schedule ([bb69bd6](https://github.com/jbcom/koota-kit/commit/bb69bd638e7a3dc6315ede827e4c58820facea3a))
|
|
9
|
+
* **schedule:** add createSchedule, a fixed-step system schedule ([21f6ffb](https://github.com/jbcom/koota-kit/commit/21f6ffbb2a6f4fae819f88197d02e30f50cc98ea))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* **schedule:** pause means no steps, and a refused step is never debited ([e2fbfad](https://github.com/jbcom/koota-kit/commit/e2fbfadb363764bc8c43232e19862223ef250ee6))
|
|
15
|
+
|
|
16
|
+
## [0.5.0](https://github.com/jbcom/koota-kit/compare/v0.4.0...v0.5.0) (2026-10-10)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
* **react:** add koota-kit/react with SimWorldProvider and useSimWorld ([3a9aeee](https://github.com/jbcom/koota-kit/commit/3a9aeee17d67af90bb8be925fd123bfba32f2968))
|
|
22
|
+
* **react:** add koota-kit/react with SimWorldProvider and useSimWorld ([74a5f1c](https://github.com/jbcom/koota-kit/commit/74a5f1c683246797dfa60010375a3a4f6333d37c))
|
|
23
|
+
|
|
3
24
|
## [0.4.0](https://github.com/jbcom/koota-kit/compare/v0.3.1...v0.4.0) (2026-10-08)
|
|
4
25
|
|
|
5
26
|
|
package/README.md
CHANGED
|
@@ -38,6 +38,8 @@ Requirements:
|
|
|
38
38
|
The package declares `>=22`; support follows maintained Node lines rather
|
|
39
39
|
than requiring one exact patch release.
|
|
40
40
|
- Koota `^0.6.6`, installed by the application as a peer dependency
|
|
41
|
+
- React `>=18`, only if you import `koota-kit/react`; it is an optional peer
|
|
42
|
+
dependency, and no other entry point loads it
|
|
41
43
|
|
|
42
44
|
The package ships native ESM and CommonJS entry points with format-correct
|
|
43
45
|
TypeScript declarations.
|
|
@@ -135,6 +137,26 @@ defineTrait({ storage: { logs: 0 } }); // TypeError: shared object footg
|
|
|
135
137
|
Declare traits once in a leaf module and export each trait from one location.
|
|
136
138
|
Koota identifies traits by reference, not by a string name.
|
|
137
139
|
|
|
140
|
+
### A fixed-step schedule removes the frame rate from the sim
|
|
141
|
+
|
|
142
|
+
`createSchedule(systems, { step, maxSteps })` returns a `tick(handle, frameDt)`
|
|
143
|
+
for your frame loop. The frame's dt fills an accumulator kept in `handle.scratch`
|
|
144
|
+
(so it dies with the run); each whole `step` advances the clock, then calls every
|
|
145
|
+
system in order with `dt === step`. At most `maxSteps` run per tick and any
|
|
146
|
+
excess is dropped, so one huge dt after a backgrounded tab cannot cause a
|
|
147
|
+
catch-up storm. `tick(handle, 0)` is a valid pause and returns the steps run.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { createSchedule } from "koota-kit";
|
|
151
|
+
|
|
152
|
+
const schedule = createSchedule([stove, mixer, serving], { step: 1 / 60, maxSteps: 6 });
|
|
153
|
+
// once per rendered frame:
|
|
154
|
+
const steps = schedule.tick(sim, frameDt);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Sixty frames of 1/60 s and thirty of 1/30 s run the same sixty steps.
|
|
158
|
+
`schedule.remainder(sim) / schedule.step` is the interpolation alpha.
|
|
159
|
+
|
|
138
160
|
### Event logs have one consuming owner
|
|
139
161
|
|
|
140
162
|
`push` appends, `drain` returns every pending event and empties the log, and
|
|
@@ -154,9 +176,48 @@ and restores atomically.
|
|
|
154
176
|
Persist a version beside the header and your ECS state so the application can
|
|
155
177
|
migrate its own schema.
|
|
156
178
|
|
|
179
|
+
## React
|
|
180
|
+
|
|
181
|
+
`koota/react` hooks reach only the Koota `World`. `koota-kit/react` adds a
|
|
182
|
+
provider for the whole `WorldHandle`, so components can also reach its RNG
|
|
183
|
+
streams, seeds, clock and scratch:
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
import { useQuery } from "koota/react";
|
|
187
|
+
import { createSimWorld, defineTrait, nextInt } from "koota-kit";
|
|
188
|
+
import { SimWorldProvider, useSimWorld } from "koota-kit/react";
|
|
189
|
+
|
|
190
|
+
const Position = defineTrait({ x: 0, y: 0 });
|
|
191
|
+
const handle = createSimWorld({ gen: "world-7", events: "run-7" });
|
|
192
|
+
|
|
193
|
+
function Scouts() {
|
|
194
|
+
const { rng } = useSimWorld(); // the handle: rng, seeds, clock, scratch
|
|
195
|
+
const scouts = useQuery(Position); // koota/react hooks share the same provider
|
|
196
|
+
return (
|
|
197
|
+
<button type="button" onClick={() => nextInt(rng.events, 1, 7)}>
|
|
198
|
+
Roll for {scouts.length} scouts
|
|
199
|
+
</button>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
root.render(
|
|
204
|
+
<SimWorldProvider handle={handle}>
|
|
205
|
+
<Scouts />
|
|
206
|
+
</SimWorldProvider>,
|
|
207
|
+
);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`SimWorldProvider` renders Koota's own `WorldProvider` for `handle.world`, so
|
|
211
|
+
`useTrait`, `useQuery` and `useActions` need no second provider. `useSimWorld()`
|
|
212
|
+
throws outside a provider. Passing a different `handle` re-provides every
|
|
213
|
+
consumer; the provider does not create or destroy the handle, so pair
|
|
214
|
+
`createSimWorld` with `destroySimWorld` yourself.
|
|
215
|
+
|
|
157
216
|
## API and architecture
|
|
158
217
|
|
|
159
|
-
The root package exports the full API
|
|
218
|
+
The root package exports the full API except the React bindings, which live
|
|
219
|
+
only at `koota-kit/react` so the root never loads React. Focused entry points
|
|
220
|
+
are also available:
|
|
160
221
|
|
|
161
222
|
| Entry point | Purpose |
|
|
162
223
|
| --- | --- |
|
|
@@ -164,7 +225,9 @@ The root package exports the full API. Focused entry points are also available:
|
|
|
164
225
|
| `koota-kit/rng` | Seeded streams, named substreams, bounded draws, snapshots |
|
|
165
226
|
| `koota-kit/seed` | Seed derivation and 128-bit master seeds |
|
|
166
227
|
| `koota-kit/traits` | Safe trait declaration |
|
|
228
|
+
| `koota-kit/schedule` | Fixed-step system schedule with a step cap |
|
|
167
229
|
| `koota-kit/eventLog` | World-scoped publish/drain logs |
|
|
230
|
+
| `koota-kit/react` | `SimWorldProvider` and `useSimWorld` (optional React peer) |
|
|
168
231
|
|
|
169
232
|
See the [API reference](./docs/API.md) for signatures and validation behavior,
|
|
170
233
|
and [architecture notes](./docs/ARCHITECTURE.md) for module boundaries and
|
|
@@ -182,6 +245,8 @@ For task-oriented usage, read the Sourcey guides on
|
|
|
182
245
|
- `createMasterSeed` throws when no entropy source exists or the source leaves
|
|
183
246
|
the buffer all zero.
|
|
184
247
|
- `advanceClock` accepts finite, non-negative `dt`; `0` is a valid paused tick.
|
|
248
|
+
- `createSchedule` needs a finite `step > 0` and a safe-integer `maxSteps >= 1`;
|
|
249
|
+
a negative, `NaN` or infinite `frameDt` counts as `0`.
|
|
185
250
|
- `nextInt` requires safe-integer bounds with `maxExclusive > minInclusive` and
|
|
186
251
|
a range no larger than `2**32`.
|
|
187
252
|
- `chance` accepts only finite probabilities in `[0, 1]`.
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// koota-kit — thin koota ECS conventions layer.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
4
|
+
// Six modules, also importable as subpaths:
|
|
5
5
|
// ./world — WorldHandle lifecycle (world + dual-stream rng + clock + scratch)
|
|
6
|
+
// ./schedule — fixed-step system schedule (accumulator in scratch, step cap)
|
|
6
7
|
// ./rng — dual-layer deterministic seedrandom PRNG and named substreams
|
|
7
8
|
// with byte-exact snapshot/restore
|
|
8
9
|
// ./seed — length-prefixed SHA-256 seed derivation and 128-bit master
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
// ./eventLog — scratch-backed publish/drain event logs with a peek seam for
|
|
13
14
|
// observers (harnesses, tests, HUDs)
|
|
14
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.defineTrait = exports.defineEventLog = exports.MASTER_SEED_BYTES = exports.isMasterSeed = exports.deriveSeed = exports.createMasterSeed = exports.substream = exports.snapshotSubstreams = exports.snapshotStream = exports.snapshotLayers = exports.shuffle = exports.restoreSubstreams = exports.restoreStream = exports.restoreLayers = exports.pick = exports.nextU32 = exports.nextInt = exports.nextFloat = exports.getSubstream = exports.createSubstreams = exports.createRng = exports.chance = exports.trait = exports.snapshotWorld = exports.restoreWorldHeader = exports.relation = exports.destroySimWorld = exports.createSimWorld = exports.createActions = exports.advanceClock = void 0;
|
|
16
|
+
exports.defineTrait = exports.defineEventLog = exports.createSchedule = exports.MASTER_SEED_BYTES = exports.isMasterSeed = exports.deriveSeed = exports.createMasterSeed = exports.substream = exports.snapshotSubstreams = exports.snapshotStream = exports.snapshotLayers = exports.shuffle = exports.restoreSubstreams = exports.restoreStream = exports.restoreLayers = exports.pick = exports.nextU32 = exports.nextInt = exports.nextFloat = exports.getSubstream = exports.createSubstreams = exports.createRng = exports.chance = exports.trait = exports.snapshotWorld = exports.restoreWorldHeader = exports.relation = exports.destroySimWorld = exports.createSimWorld = exports.createActions = exports.advanceClock = void 0;
|
|
16
17
|
var world_js_1 = require("./world.cjs");
|
|
17
18
|
Object.defineProperty(exports, "advanceClock", { enumerable: true, get: function () { return world_js_1.advanceClock; } });
|
|
18
19
|
Object.defineProperty(exports, "createActions", { enumerable: true, get: function () { return world_js_1.createActions; } });
|
|
@@ -44,6 +45,8 @@ Object.defineProperty(exports, "createMasterSeed", { enumerable: true, get: func
|
|
|
44
45
|
Object.defineProperty(exports, "deriveSeed", { enumerable: true, get: function () { return seed_js_1.deriveSeed; } });
|
|
45
46
|
Object.defineProperty(exports, "isMasterSeed", { enumerable: true, get: function () { return seed_js_1.isMasterSeed; } });
|
|
46
47
|
Object.defineProperty(exports, "MASTER_SEED_BYTES", { enumerable: true, get: function () { return seed_js_1.MASTER_SEED_BYTES; } });
|
|
48
|
+
var schedule_js_1 = require("./schedule.cjs");
|
|
49
|
+
Object.defineProperty(exports, "createSchedule", { enumerable: true, get: function () { return schedule_js_1.createSchedule; } });
|
|
47
50
|
var eventLog_js_1 = require("./eventLog.cjs");
|
|
48
51
|
Object.defineProperty(exports, "defineEventLog", { enumerable: true, get: function () { return eventLog_js_1.defineEventLog; } });
|
|
49
52
|
var index_js_1 = require("./traits/index.cjs");
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -4,6 +4,8 @@ export { chance, createRng, createSubstreams, getSubstream, nextFloat, nextInt,
|
|
|
4
4
|
export type { RngLayers, RngLayersSnapshot, RngSeeds, RngStream, RngStreamSnapshot, Substreams, SubstreamsSnapshot, } from "./rng.cjs";
|
|
5
5
|
export { createMasterSeed, deriveSeed, isMasterSeed, MASTER_SEED_BYTES } from "./seed.cjs";
|
|
6
6
|
export type { EntropySource, SeedPart } from "./seed.cjs";
|
|
7
|
+
export { createSchedule } from "./schedule.cjs";
|
|
8
|
+
export type { Schedule, ScheduleOptions, System } from "./schedule.cjs";
|
|
7
9
|
export { defineEventLog } from "./eventLog.cjs";
|
|
8
10
|
export type { EventLog } from "./eventLog.cjs";
|
|
9
11
|
export { defineTrait } from "./traits/index.cjs";
|
package/dist/cjs/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,cAAc,EACd,eAAe,EACf,QAAQ,EACR,kBAAkB,EAClB,aAAa,EACb,KAAK,GACN,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE5E,OAAO,EACL,MAAM,EACN,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,OAAO,EACP,OAAO,EACP,IAAI,EACJ,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,OAAO,EACP,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,SAAS,GACV,MAAM,UAAU,CAAC;AAClB,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,QAAQ,EACR,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,kBAAkB,GACnB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC1F,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAEzD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvE,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// React bindings for the sim handle. This is the ONLY module that imports
|
|
3
|
+
// React (through koota/react), and nothing else in the package imports it, so
|
|
4
|
+
// the root entry and every other subpath stay React-free. `react` is an
|
|
5
|
+
// optional peer dependency: consumers who never import `koota-kit/react` never
|
|
6
|
+
// need it installed.
|
|
7
|
+
//
|
|
8
|
+
// koota/react's own `WorldProvider` / `useWorld` carry only the koota `World`.
|
|
9
|
+
// A component that needs the handle's RNG streams, seeds, clock or scratch
|
|
10
|
+
// cannot reach them from there. `SimWorldProvider` supplies the whole
|
|
11
|
+
// `WorldHandle` AND mounts koota's provider for `handle.world`, so
|
|
12
|
+
// `useTrait`, `useQuery` and `useActions` work underneath it unchanged.
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.SimWorldProvider = void 0;
|
|
15
|
+
exports.useSimWorld = useSimWorld;
|
|
16
|
+
const react_1 = require("koota/react");
|
|
17
|
+
const react_2 = require("react");
|
|
18
|
+
const SimWorldContext = (0, react_2.createContext)(null);
|
|
19
|
+
SimWorldContext.displayName = "SimWorldContext";
|
|
20
|
+
/**
|
|
21
|
+
* Provide a `WorldHandle` to the tree below. Renders koota's `WorldProvider`
|
|
22
|
+
* for `handle.world`, so every `koota/react` hook works under it without a
|
|
23
|
+
* second provider. The provider does not own the handle's lifecycle: create it
|
|
24
|
+
* with `createSimWorld` and release it with `destroySimWorld` yourself.
|
|
25
|
+
*/
|
|
26
|
+
const SimWorldProvider = ({ handle, children }) => (0, react_2.createElement)(SimWorldContext.Provider, { value: handle }, (0, react_2.createElement)(react_1.WorldProvider, { world: handle.world, children }));
|
|
27
|
+
exports.SimWorldProvider = SimWorldProvider;
|
|
28
|
+
/**
|
|
29
|
+
* The `WorldHandle` provided by the nearest `SimWorldProvider`: its `world`,
|
|
30
|
+
* `rng`, `seeds`, `clock` and `scratch`. Throws when called outside one — a
|
|
31
|
+
* missing provider is a wiring bug, not a state to render around.
|
|
32
|
+
*/
|
|
33
|
+
function useSimWorld() {
|
|
34
|
+
const handle = (0, react_2.useContext)(SimWorldContext);
|
|
35
|
+
if (handle === null) {
|
|
36
|
+
throw new Error("useSimWorld must be used within a <SimWorldProvider>");
|
|
37
|
+
}
|
|
38
|
+
return handle;
|
|
39
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FunctionComponent, ReactNode } from "react";
|
|
2
|
+
import type { WorldHandle } from "./world.cjs";
|
|
3
|
+
export type SimWorldProviderProps = {
|
|
4
|
+
/** The handle from `createSimWorld`. Swapping it re-provides every consumer. */
|
|
5
|
+
readonly handle: WorldHandle;
|
|
6
|
+
readonly children?: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Provide a `WorldHandle` to the tree below. Renders koota's `WorldProvider`
|
|
10
|
+
* for `handle.world`, so every `koota/react` hook works under it without a
|
|
11
|
+
* second provider. The provider does not own the handle's lifecycle: create it
|
|
12
|
+
* with `createSimWorld` and release it with `destroySimWorld` yourself.
|
|
13
|
+
*/
|
|
14
|
+
export declare const SimWorldProvider: FunctionComponent<SimWorldProviderProps>;
|
|
15
|
+
/**
|
|
16
|
+
* The `WorldHandle` provided by the nearest `SimWorldProvider`: its `world`,
|
|
17
|
+
* `rng`, `seeds`, `clock` and `scratch`. Throws when called outside one — a
|
|
18
|
+
* missing provider is a wiring bug, not a state to render around.
|
|
19
|
+
*/
|
|
20
|
+
export declare function useSimWorld(): WorldHandle;
|
|
21
|
+
//# sourceMappingURL=react.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/react.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,MAAM,qBAAqB,GAAG;IAClC,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;CAC/B,CAAC;AAKF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,iBAAiB,CAAC,qBAAqB,CAKnE,CAAC;AAEJ;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,WAAW,CAMzC"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Fixed-step system schedule — the loop that turns "a frame passed" into
|
|
3
|
+
// "the sim advanced by whole steps".
|
|
4
|
+
//
|
|
5
|
+
// A simulation that integrates the frame's own dt drifts with the frame rate:
|
|
6
|
+
// a 120 Hz phone and a test that advances three seconds in one call disagree,
|
|
7
|
+
// and anything counted in real time (a save every 0.25 s) lands on different
|
|
8
|
+
// ticks. A fixed-step schedule removes the frame rate from the sim: the frame's
|
|
9
|
+
// dt fills an accumulator, and the sim only ever advances by whole `step`s.
|
|
10
|
+
//
|
|
11
|
+
// The loop is generic sim plumbing, so it lives here and not in the game: it
|
|
12
|
+
// knows nothing about React, a renderer, or what a system does. A renderer's
|
|
13
|
+
// frame callback calls `schedule.tick(handle, dt)` and that is the entire
|
|
14
|
+
// integration. The accumulator lives in the handle's `scratch`, so it dies with
|
|
15
|
+
// the run: a new world starts with nothing owed, and two schedules (or two
|
|
16
|
+
// worlds) never share one.
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.createSchedule = createSchedule;
|
|
19
|
+
const world_js_1 = require("./world.cjs");
|
|
20
|
+
let nextScheduleId = 0;
|
|
21
|
+
// Keys are process-wide because schedules are declared once at module scope and
|
|
22
|
+
// used on many handles. An explicit key may be shared on purpose; a generated
|
|
23
|
+
// one must stay private to its schedule.
|
|
24
|
+
const claimedKeys = new Set();
|
|
25
|
+
const generatedKeys = new Set();
|
|
26
|
+
// Float accumulation: 432 frames of 1/144 add up to a hair under 3 s, and
|
|
27
|
+
// without a tolerance the sim would owe — and run — one step fewer than the
|
|
28
|
+
// same time delivered at 60 Hz. A billionth of a step is far below anything a
|
|
29
|
+
// frame clock can resolve, so treating "within it" as whole costs nothing.
|
|
30
|
+
const STEP_TOLERANCE = 1e-9;
|
|
31
|
+
/**
|
|
32
|
+
* Build a fixed-step schedule over `systems`, run in the order given.
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* const schedule = createSchedule([intro, stove, mixer, serving], {
|
|
36
|
+
* step: 1 / 60,
|
|
37
|
+
* maxSteps: 6,
|
|
38
|
+
* });
|
|
39
|
+
* // once per rendered frame, with the frame's dt:
|
|
40
|
+
* schedule.tick(run, frameDt);
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* The systems array is copied; later changes to it do not affect the schedule.
|
|
44
|
+
*/
|
|
45
|
+
function createSchedule(systems, options) {
|
|
46
|
+
if (!Array.isArray(systems)) {
|
|
47
|
+
throw new TypeError("createSchedule: systems must be an array of functions.");
|
|
48
|
+
}
|
|
49
|
+
const { step, maxSteps } = options;
|
|
50
|
+
if (!Number.isFinite(step) || step <= 0) {
|
|
51
|
+
throw new RangeError(`createSchedule: step must be finite and greater than zero; received ${step}.`);
|
|
52
|
+
}
|
|
53
|
+
if (!Number.isSafeInteger(maxSteps) || maxSteps < 1) {
|
|
54
|
+
throw new RangeError(`createSchedule: maxSteps must be a safe integer of at least 1; received ${maxSteps}.`);
|
|
55
|
+
}
|
|
56
|
+
if (options.key !== undefined && (typeof options.key !== "string" || options.key === "")) {
|
|
57
|
+
throw new TypeError("createSchedule: key must be a non-empty string when provided.");
|
|
58
|
+
}
|
|
59
|
+
const ordered = Object.freeze([...systems]);
|
|
60
|
+
for (const system of ordered) {
|
|
61
|
+
if (typeof system !== "function") {
|
|
62
|
+
throw new TypeError("createSchedule: every system must be a function.");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
let key;
|
|
66
|
+
if (options.key === undefined) {
|
|
67
|
+
// Skip any key a caller has already named, so a default never lands on one.
|
|
68
|
+
do {
|
|
69
|
+
key = `schedule:${nextScheduleId++}`;
|
|
70
|
+
} while (claimedKeys.has(key));
|
|
71
|
+
generatedKeys.add(key);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
if (generatedKeys.has(options.key)) {
|
|
75
|
+
throw new TypeError(`createSchedule: key "${options.key}" is already the generated key of another schedule.`);
|
|
76
|
+
}
|
|
77
|
+
key = options.key;
|
|
78
|
+
}
|
|
79
|
+
claimedKeys.add(key);
|
|
80
|
+
const tolerance = step * STEP_TOLERANCE;
|
|
81
|
+
function read(handle) {
|
|
82
|
+
const stored = handle.scratch.get(key);
|
|
83
|
+
if (stored === undefined)
|
|
84
|
+
return 0;
|
|
85
|
+
if (typeof stored !== "number" || !Number.isFinite(stored) || stored < 0) {
|
|
86
|
+
throw new TypeError(`createSchedule: scratch key "${key}" already holds something other than a schedule accumulator.`);
|
|
87
|
+
}
|
|
88
|
+
return stored;
|
|
89
|
+
}
|
|
90
|
+
function tick(handle, frameDt) {
|
|
91
|
+
const previous = read(handle);
|
|
92
|
+
// A pause (and any frame dt that counts as 0) runs nothing, even if a
|
|
93
|
+
// failed tick left whole steps owed: those wait for a frame that moves time.
|
|
94
|
+
if (!(Number.isFinite(frameDt) && frameDt > 0))
|
|
95
|
+
return 0;
|
|
96
|
+
const sum = previous + frameDt;
|
|
97
|
+
// Two huge finite frames can sum past the double range; saturate instead of
|
|
98
|
+
// letting Infinity poison the stored accumulator.
|
|
99
|
+
let owed = Number.isFinite(sum) ? sum : Number.MAX_VALUE;
|
|
100
|
+
let ran = 0;
|
|
101
|
+
try {
|
|
102
|
+
while (owed + tolerance >= step && ran < maxSteps) {
|
|
103
|
+
// The clock moves first, so a step it refuses (an exhausted clock) is
|
|
104
|
+
// never debited. After that the step is spent before any system runs:
|
|
105
|
+
// if one throws, this step's time is gone with the tick it caused.
|
|
106
|
+
(0, world_js_1.advanceClock)(handle, step);
|
|
107
|
+
owed = Math.max(0, owed - step);
|
|
108
|
+
ran++;
|
|
109
|
+
for (const system of ordered)
|
|
110
|
+
system(handle, step);
|
|
111
|
+
}
|
|
112
|
+
if (owed + tolerance >= step) {
|
|
113
|
+
// Capped with whole steps still owed: drop them, keep the fraction.
|
|
114
|
+
owed %= step;
|
|
115
|
+
if (owed + tolerance >= step)
|
|
116
|
+
owed = 0;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
handle.scratch.set(key, owed);
|
|
121
|
+
}
|
|
122
|
+
return ran;
|
|
123
|
+
}
|
|
124
|
+
return Object.freeze({
|
|
125
|
+
step,
|
|
126
|
+
maxSteps,
|
|
127
|
+
key,
|
|
128
|
+
tick,
|
|
129
|
+
remainder: read,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { WorldHandle } from "./world.cjs";
|
|
2
|
+
/**
|
|
3
|
+
* One unit of sim work, run once per fixed step.
|
|
4
|
+
*
|
|
5
|
+
* `dt` is always the schedule's `step`, never the frame's dt, so a system can
|
|
6
|
+
* integrate it without knowing the frame rate. Systems that need randomness
|
|
7
|
+
* should draw from a named substream rather than the handle's positional `rng`,
|
|
8
|
+
* so adding a system does not shift another system's draws.
|
|
9
|
+
*/
|
|
10
|
+
export type System = (handle: WorldHandle, dt: number) => void;
|
|
11
|
+
export type ScheduleOptions = {
|
|
12
|
+
/** Seconds of sim time one step represents. Finite and greater than zero. */
|
|
13
|
+
step: number;
|
|
14
|
+
/**
|
|
15
|
+
* The most steps a single `tick` may run. A safe integer of at least 1. Time
|
|
16
|
+
* owed beyond this is dropped, not carried, so one huge frame dt (a hidden
|
|
17
|
+
* tab returning after a minute) cannot trigger a catch-up storm.
|
|
18
|
+
*/
|
|
19
|
+
maxSteps: number;
|
|
20
|
+
/**
|
|
21
|
+
* The `scratch` key holding this schedule's accumulator, for callers who want
|
|
22
|
+
* to namespace it (`"run:schedule"`). Defaults to a key unique to this
|
|
23
|
+
* schedule. Two schedules given the same key share one accumulator.
|
|
24
|
+
*/
|
|
25
|
+
key?: string;
|
|
26
|
+
};
|
|
27
|
+
export type Schedule = {
|
|
28
|
+
readonly step: number;
|
|
29
|
+
readonly maxSteps: number;
|
|
30
|
+
/** The `scratch` key holding this schedule's accumulator on a handle. */
|
|
31
|
+
readonly key: string;
|
|
32
|
+
/**
|
|
33
|
+
* Add `frameDt` seconds to `handle`'s accumulator and run every whole step it
|
|
34
|
+
* now covers, up to `maxSteps`: each step advances the clock by `step`, then
|
|
35
|
+
* calls every system in order with `dt === step`. Returns how many steps ran.
|
|
36
|
+
*
|
|
37
|
+
* A `frameDt` that is negative, `NaN` or infinite counts as `0`, and `0` is a
|
|
38
|
+
* valid pause: no step runs and the clock does not move. Whole steps beyond
|
|
39
|
+
* `maxSteps` are dropped; the sub-step remainder is kept.
|
|
40
|
+
*
|
|
41
|
+
* If a system throws, the steps that had already started are consumed (the
|
|
42
|
+
* clock moved for them) and the rest stay owed, so a retry does not replay
|
|
43
|
+
* time the sim already spent.
|
|
44
|
+
*/
|
|
45
|
+
tick: (handle: WorldHandle, frameDt: number) => number;
|
|
46
|
+
/**
|
|
47
|
+
* Seconds currently owed to the next step, in `[0, step)` between ticks.
|
|
48
|
+
* Dividing it by `step` gives the interpolation alpha for a renderer drawing
|
|
49
|
+
* between the previous and current sim state. `0` on a handle that has not
|
|
50
|
+
* ticked.
|
|
51
|
+
*/
|
|
52
|
+
remainder: (handle: WorldHandle) => number;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Build a fixed-step schedule over `systems`, run in the order given.
|
|
56
|
+
*
|
|
57
|
+
* ```ts
|
|
58
|
+
* const schedule = createSchedule([intro, stove, mixer, serving], {
|
|
59
|
+
* step: 1 / 60,
|
|
60
|
+
* maxSteps: 6,
|
|
61
|
+
* });
|
|
62
|
+
* // once per rendered frame, with the frame's dt:
|
|
63
|
+
* schedule.tick(run, frameDt);
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* The systems array is copied; later changes to it do not affect the schedule.
|
|
67
|
+
*/
|
|
68
|
+
export declare function createSchedule(systems: readonly System[], options: ScheduleOptions): Readonly<Schedule>;
|
|
69
|
+
//# sourceMappingURL=schedule.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule.d.ts","sourceRoot":"","sources":["../../src/schedule.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG9C;;;;;;;GAOG;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;AAE/D,MAAM,MAAM,eAAe,GAAG;IAC5B,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yEAAyE;IACzE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;OAYG;IACH,IAAI,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IACvD;;;;;OAKG;IACH,SAAS,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,MAAM,CAAC;CAC5C,CAAC;AAeF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,eAAe,GACvB,QAAQ,CAAC,QAAQ,CAAC,CA2FpB"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export { chance, createRng, createSubstreams, getSubstream, nextFloat, nextInt,
|
|
|
4
4
|
export type { RngLayers, RngLayersSnapshot, RngSeeds, RngStream, RngStreamSnapshot, Substreams, SubstreamsSnapshot, } from "./rng.js";
|
|
5
5
|
export { createMasterSeed, deriveSeed, isMasterSeed, MASTER_SEED_BYTES } from "./seed.js";
|
|
6
6
|
export type { EntropySource, SeedPart } from "./seed.js";
|
|
7
|
+
export { createSchedule } from "./schedule.js";
|
|
8
|
+
export type { Schedule, ScheduleOptions, System } from "./schedule.js";
|
|
7
9
|
export { defineEventLog } from "./eventLog.js";
|
|
8
10
|
export type { EventLog } from "./eventLog.js";
|
|
9
11
|
export { defineTrait } from "./traits/index.js";
|
package/dist/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,cAAc,EACd,eAAe,EACf,QAAQ,EACR,kBAAkB,EAClB,aAAa,EACb,KAAK,GACN,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE5E,OAAO,EACL,MAAM,EACN,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,OAAO,EACP,OAAO,EACP,IAAI,EACJ,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,OAAO,EACP,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,SAAS,GACV,MAAM,UAAU,CAAC;AAClB,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,QAAQ,EACR,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,kBAAkB,GACnB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC1F,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAEzD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvE,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/esm/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// koota-kit — thin koota ECS conventions layer.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
3
|
+
// Six modules, also importable as subpaths:
|
|
4
4
|
// ./world — WorldHandle lifecycle (world + dual-stream rng + clock + scratch)
|
|
5
|
+
// ./schedule — fixed-step system schedule (accumulator in scratch, step cap)
|
|
5
6
|
// ./rng — dual-layer deterministic seedrandom PRNG and named substreams
|
|
6
7
|
// with byte-exact snapshot/restore
|
|
7
8
|
// ./seed — length-prefixed SHA-256 seed derivation and 128-bit master
|
|
@@ -13,5 +14,6 @@
|
|
|
13
14
|
export { advanceClock, createActions, createSimWorld, destroySimWorld, relation, restoreWorldHeader, snapshotWorld, trait, } from "./world.js";
|
|
14
15
|
export { chance, createRng, createSubstreams, getSubstream, nextFloat, nextInt, nextU32, pick, restoreLayers, restoreStream, restoreSubstreams, shuffle, snapshotLayers, snapshotStream, snapshotSubstreams, substream, } from "./rng.js";
|
|
15
16
|
export { createMasterSeed, deriveSeed, isMasterSeed, MASTER_SEED_BYTES } from "./seed.js";
|
|
17
|
+
export { createSchedule } from "./schedule.js";
|
|
16
18
|
export { defineEventLog } from "./eventLog.js";
|
|
17
19
|
export { defineTrait } from "./traits/index.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FunctionComponent, ReactNode } from "react";
|
|
2
|
+
import type { WorldHandle } from "./world.js";
|
|
3
|
+
export type SimWorldProviderProps = {
|
|
4
|
+
/** The handle from `createSimWorld`. Swapping it re-provides every consumer. */
|
|
5
|
+
readonly handle: WorldHandle;
|
|
6
|
+
readonly children?: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Provide a `WorldHandle` to the tree below. Renders koota's `WorldProvider`
|
|
10
|
+
* for `handle.world`, so every `koota/react` hook works under it without a
|
|
11
|
+
* second provider. The provider does not own the handle's lifecycle: create it
|
|
12
|
+
* with `createSimWorld` and release it with `destroySimWorld` yourself.
|
|
13
|
+
*/
|
|
14
|
+
export declare const SimWorldProvider: FunctionComponent<SimWorldProviderProps>;
|
|
15
|
+
/**
|
|
16
|
+
* The `WorldHandle` provided by the nearest `SimWorldProvider`: its `world`,
|
|
17
|
+
* `rng`, `seeds`, `clock` and `scratch`. Throws when called outside one — a
|
|
18
|
+
* missing provider is a wiring bug, not a state to render around.
|
|
19
|
+
*/
|
|
20
|
+
export declare function useSimWorld(): WorldHandle;
|
|
21
|
+
//# sourceMappingURL=react.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/react.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,MAAM,qBAAqB,GAAG;IAClC,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;CAC/B,CAAC;AAKF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,iBAAiB,CAAC,qBAAqB,CAKnE,CAAC;AAEJ;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,WAAW,CAMzC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// React bindings for the sim handle. This is the ONLY module that imports
|
|
2
|
+
// React (through koota/react), and nothing else in the package imports it, so
|
|
3
|
+
// the root entry and every other subpath stay React-free. `react` is an
|
|
4
|
+
// optional peer dependency: consumers who never import `koota-kit/react` never
|
|
5
|
+
// need it installed.
|
|
6
|
+
//
|
|
7
|
+
// koota/react's own `WorldProvider` / `useWorld` carry only the koota `World`.
|
|
8
|
+
// A component that needs the handle's RNG streams, seeds, clock or scratch
|
|
9
|
+
// cannot reach them from there. `SimWorldProvider` supplies the whole
|
|
10
|
+
// `WorldHandle` AND mounts koota's provider for `handle.world`, so
|
|
11
|
+
// `useTrait`, `useQuery` and `useActions` work underneath it unchanged.
|
|
12
|
+
import { WorldProvider } from "koota/react";
|
|
13
|
+
import { createContext, createElement, useContext } from "react";
|
|
14
|
+
const SimWorldContext = createContext(null);
|
|
15
|
+
SimWorldContext.displayName = "SimWorldContext";
|
|
16
|
+
/**
|
|
17
|
+
* Provide a `WorldHandle` to the tree below. Renders koota's `WorldProvider`
|
|
18
|
+
* for `handle.world`, so every `koota/react` hook works under it without a
|
|
19
|
+
* second provider. The provider does not own the handle's lifecycle: create it
|
|
20
|
+
* with `createSimWorld` and release it with `destroySimWorld` yourself.
|
|
21
|
+
*/
|
|
22
|
+
export const SimWorldProvider = ({ handle, children }) => createElement(SimWorldContext.Provider, { value: handle }, createElement(WorldProvider, { world: handle.world, children }));
|
|
23
|
+
/**
|
|
24
|
+
* The `WorldHandle` provided by the nearest `SimWorldProvider`: its `world`,
|
|
25
|
+
* `rng`, `seeds`, `clock` and `scratch`. Throws when called outside one — a
|
|
26
|
+
* missing provider is a wiring bug, not a state to render around.
|
|
27
|
+
*/
|
|
28
|
+
export function useSimWorld() {
|
|
29
|
+
const handle = useContext(SimWorldContext);
|
|
30
|
+
if (handle === null) {
|
|
31
|
+
throw new Error("useSimWorld must be used within a <SimWorldProvider>");
|
|
32
|
+
}
|
|
33
|
+
return handle;
|
|
34
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { WorldHandle } from "./world.js";
|
|
2
|
+
/**
|
|
3
|
+
* One unit of sim work, run once per fixed step.
|
|
4
|
+
*
|
|
5
|
+
* `dt` is always the schedule's `step`, never the frame's dt, so a system can
|
|
6
|
+
* integrate it without knowing the frame rate. Systems that need randomness
|
|
7
|
+
* should draw from a named substream rather than the handle's positional `rng`,
|
|
8
|
+
* so adding a system does not shift another system's draws.
|
|
9
|
+
*/
|
|
10
|
+
export type System = (handle: WorldHandle, dt: number) => void;
|
|
11
|
+
export type ScheduleOptions = {
|
|
12
|
+
/** Seconds of sim time one step represents. Finite and greater than zero. */
|
|
13
|
+
step: number;
|
|
14
|
+
/**
|
|
15
|
+
* The most steps a single `tick` may run. A safe integer of at least 1. Time
|
|
16
|
+
* owed beyond this is dropped, not carried, so one huge frame dt (a hidden
|
|
17
|
+
* tab returning after a minute) cannot trigger a catch-up storm.
|
|
18
|
+
*/
|
|
19
|
+
maxSteps: number;
|
|
20
|
+
/**
|
|
21
|
+
* The `scratch` key holding this schedule's accumulator, for callers who want
|
|
22
|
+
* to namespace it (`"run:schedule"`). Defaults to a key unique to this
|
|
23
|
+
* schedule. Two schedules given the same key share one accumulator.
|
|
24
|
+
*/
|
|
25
|
+
key?: string;
|
|
26
|
+
};
|
|
27
|
+
export type Schedule = {
|
|
28
|
+
readonly step: number;
|
|
29
|
+
readonly maxSteps: number;
|
|
30
|
+
/** The `scratch` key holding this schedule's accumulator on a handle. */
|
|
31
|
+
readonly key: string;
|
|
32
|
+
/**
|
|
33
|
+
* Add `frameDt` seconds to `handle`'s accumulator and run every whole step it
|
|
34
|
+
* now covers, up to `maxSteps`: each step advances the clock by `step`, then
|
|
35
|
+
* calls every system in order with `dt === step`. Returns how many steps ran.
|
|
36
|
+
*
|
|
37
|
+
* A `frameDt` that is negative, `NaN` or infinite counts as `0`, and `0` is a
|
|
38
|
+
* valid pause: no step runs and the clock does not move. Whole steps beyond
|
|
39
|
+
* `maxSteps` are dropped; the sub-step remainder is kept.
|
|
40
|
+
*
|
|
41
|
+
* If a system throws, the steps that had already started are consumed (the
|
|
42
|
+
* clock moved for them) and the rest stay owed, so a retry does not replay
|
|
43
|
+
* time the sim already spent.
|
|
44
|
+
*/
|
|
45
|
+
tick: (handle: WorldHandle, frameDt: number) => number;
|
|
46
|
+
/**
|
|
47
|
+
* Seconds currently owed to the next step, in `[0, step)` between ticks.
|
|
48
|
+
* Dividing it by `step` gives the interpolation alpha for a renderer drawing
|
|
49
|
+
* between the previous and current sim state. `0` on a handle that has not
|
|
50
|
+
* ticked.
|
|
51
|
+
*/
|
|
52
|
+
remainder: (handle: WorldHandle) => number;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Build a fixed-step schedule over `systems`, run in the order given.
|
|
56
|
+
*
|
|
57
|
+
* ```ts
|
|
58
|
+
* const schedule = createSchedule([intro, stove, mixer, serving], {
|
|
59
|
+
* step: 1 / 60,
|
|
60
|
+
* maxSteps: 6,
|
|
61
|
+
* });
|
|
62
|
+
* // once per rendered frame, with the frame's dt:
|
|
63
|
+
* schedule.tick(run, frameDt);
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* The systems array is copied; later changes to it do not affect the schedule.
|
|
67
|
+
*/
|
|
68
|
+
export declare function createSchedule(systems: readonly System[], options: ScheduleOptions): Readonly<Schedule>;
|
|
69
|
+
//# sourceMappingURL=schedule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule.d.ts","sourceRoot":"","sources":["../../src/schedule.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG9C;;;;;;;GAOG;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;AAE/D,MAAM,MAAM,eAAe,GAAG;IAC5B,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yEAAyE;IACzE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;OAYG;IACH,IAAI,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IACvD;;;;;OAKG;IACH,SAAS,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,MAAM,CAAC;CAC5C,CAAC;AAeF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,eAAe,GACvB,QAAQ,CAAC,QAAQ,CAAC,CA2FpB"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Fixed-step system schedule — the loop that turns "a frame passed" into
|
|
2
|
+
// "the sim advanced by whole steps".
|
|
3
|
+
//
|
|
4
|
+
// A simulation that integrates the frame's own dt drifts with the frame rate:
|
|
5
|
+
// a 120 Hz phone and a test that advances three seconds in one call disagree,
|
|
6
|
+
// and anything counted in real time (a save every 0.25 s) lands on different
|
|
7
|
+
// ticks. A fixed-step schedule removes the frame rate from the sim: the frame's
|
|
8
|
+
// dt fills an accumulator, and the sim only ever advances by whole `step`s.
|
|
9
|
+
//
|
|
10
|
+
// The loop is generic sim plumbing, so it lives here and not in the game: it
|
|
11
|
+
// knows nothing about React, a renderer, or what a system does. A renderer's
|
|
12
|
+
// frame callback calls `schedule.tick(handle, dt)` and that is the entire
|
|
13
|
+
// integration. The accumulator lives in the handle's `scratch`, so it dies with
|
|
14
|
+
// the run: a new world starts with nothing owed, and two schedules (or two
|
|
15
|
+
// worlds) never share one.
|
|
16
|
+
import { advanceClock } from "./world.js";
|
|
17
|
+
let nextScheduleId = 0;
|
|
18
|
+
// Keys are process-wide because schedules are declared once at module scope and
|
|
19
|
+
// used on many handles. An explicit key may be shared on purpose; a generated
|
|
20
|
+
// one must stay private to its schedule.
|
|
21
|
+
const claimedKeys = new Set();
|
|
22
|
+
const generatedKeys = new Set();
|
|
23
|
+
// Float accumulation: 432 frames of 1/144 add up to a hair under 3 s, and
|
|
24
|
+
// without a tolerance the sim would owe — and run — one step fewer than the
|
|
25
|
+
// same time delivered at 60 Hz. A billionth of a step is far below anything a
|
|
26
|
+
// frame clock can resolve, so treating "within it" as whole costs nothing.
|
|
27
|
+
const STEP_TOLERANCE = 1e-9;
|
|
28
|
+
/**
|
|
29
|
+
* Build a fixed-step schedule over `systems`, run in the order given.
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* const schedule = createSchedule([intro, stove, mixer, serving], {
|
|
33
|
+
* step: 1 / 60,
|
|
34
|
+
* maxSteps: 6,
|
|
35
|
+
* });
|
|
36
|
+
* // once per rendered frame, with the frame's dt:
|
|
37
|
+
* schedule.tick(run, frameDt);
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* The systems array is copied; later changes to it do not affect the schedule.
|
|
41
|
+
*/
|
|
42
|
+
export function createSchedule(systems, options) {
|
|
43
|
+
if (!Array.isArray(systems)) {
|
|
44
|
+
throw new TypeError("createSchedule: systems must be an array of functions.");
|
|
45
|
+
}
|
|
46
|
+
const { step, maxSteps } = options;
|
|
47
|
+
if (!Number.isFinite(step) || step <= 0) {
|
|
48
|
+
throw new RangeError(`createSchedule: step must be finite and greater than zero; received ${step}.`);
|
|
49
|
+
}
|
|
50
|
+
if (!Number.isSafeInteger(maxSteps) || maxSteps < 1) {
|
|
51
|
+
throw new RangeError(`createSchedule: maxSteps must be a safe integer of at least 1; received ${maxSteps}.`);
|
|
52
|
+
}
|
|
53
|
+
if (options.key !== undefined && (typeof options.key !== "string" || options.key === "")) {
|
|
54
|
+
throw new TypeError("createSchedule: key must be a non-empty string when provided.");
|
|
55
|
+
}
|
|
56
|
+
const ordered = Object.freeze([...systems]);
|
|
57
|
+
for (const system of ordered) {
|
|
58
|
+
if (typeof system !== "function") {
|
|
59
|
+
throw new TypeError("createSchedule: every system must be a function.");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
let key;
|
|
63
|
+
if (options.key === undefined) {
|
|
64
|
+
// Skip any key a caller has already named, so a default never lands on one.
|
|
65
|
+
do {
|
|
66
|
+
key = `schedule:${nextScheduleId++}`;
|
|
67
|
+
} while (claimedKeys.has(key));
|
|
68
|
+
generatedKeys.add(key);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
if (generatedKeys.has(options.key)) {
|
|
72
|
+
throw new TypeError(`createSchedule: key "${options.key}" is already the generated key of another schedule.`);
|
|
73
|
+
}
|
|
74
|
+
key = options.key;
|
|
75
|
+
}
|
|
76
|
+
claimedKeys.add(key);
|
|
77
|
+
const tolerance = step * STEP_TOLERANCE;
|
|
78
|
+
function read(handle) {
|
|
79
|
+
const stored = handle.scratch.get(key);
|
|
80
|
+
if (stored === undefined)
|
|
81
|
+
return 0;
|
|
82
|
+
if (typeof stored !== "number" || !Number.isFinite(stored) || stored < 0) {
|
|
83
|
+
throw new TypeError(`createSchedule: scratch key "${key}" already holds something other than a schedule accumulator.`);
|
|
84
|
+
}
|
|
85
|
+
return stored;
|
|
86
|
+
}
|
|
87
|
+
function tick(handle, frameDt) {
|
|
88
|
+
const previous = read(handle);
|
|
89
|
+
// A pause (and any frame dt that counts as 0) runs nothing, even if a
|
|
90
|
+
// failed tick left whole steps owed: those wait for a frame that moves time.
|
|
91
|
+
if (!(Number.isFinite(frameDt) && frameDt > 0))
|
|
92
|
+
return 0;
|
|
93
|
+
const sum = previous + frameDt;
|
|
94
|
+
// Two huge finite frames can sum past the double range; saturate instead of
|
|
95
|
+
// letting Infinity poison the stored accumulator.
|
|
96
|
+
let owed = Number.isFinite(sum) ? sum : Number.MAX_VALUE;
|
|
97
|
+
let ran = 0;
|
|
98
|
+
try {
|
|
99
|
+
while (owed + tolerance >= step && ran < maxSteps) {
|
|
100
|
+
// The clock moves first, so a step it refuses (an exhausted clock) is
|
|
101
|
+
// never debited. After that the step is spent before any system runs:
|
|
102
|
+
// if one throws, this step's time is gone with the tick it caused.
|
|
103
|
+
advanceClock(handle, step);
|
|
104
|
+
owed = Math.max(0, owed - step);
|
|
105
|
+
ran++;
|
|
106
|
+
for (const system of ordered)
|
|
107
|
+
system(handle, step);
|
|
108
|
+
}
|
|
109
|
+
if (owed + tolerance >= step) {
|
|
110
|
+
// Capped with whole steps still owed: drop them, keep the fraction.
|
|
111
|
+
owed %= step;
|
|
112
|
+
if (owed + tolerance >= step)
|
|
113
|
+
owed = 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
handle.scratch.set(key, owed);
|
|
118
|
+
}
|
|
119
|
+
return ran;
|
|
120
|
+
}
|
|
121
|
+
return Object.freeze({
|
|
122
|
+
step,
|
|
123
|
+
maxSteps,
|
|
124
|
+
key,
|
|
125
|
+
tick,
|
|
126
|
+
remainder: read,
|
|
127
|
+
});
|
|
128
|
+
}
|
package/docs/API.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# API reference
|
|
2
2
|
|
|
3
|
-
All functions and types are exported from `koota-kit
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
All functions and types are exported from `koota-kit`, except the React
|
|
4
|
+
bindings, which are only available from `koota-kit/react` so the root entry
|
|
5
|
+
never loads React. The module-specific import paths shown below can reduce
|
|
6
|
+
coupling in larger codebases.
|
|
6
7
|
|
|
7
8
|
## `world`
|
|
8
9
|
|
|
@@ -173,6 +174,63 @@ const Pose = defineTrait(() => ({ position: { x: 0, y: 0 } }));
|
|
|
173
174
|
Bare object and array fields are rejected by `SafeSchema` and by the runtime
|
|
174
175
|
guard.
|
|
175
176
|
|
|
177
|
+
## `schedule`
|
|
178
|
+
|
|
179
|
+
Import from `koota-kit/schedule` or the root.
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
type System = (handle: WorldHandle, dt: number) => void;
|
|
183
|
+
|
|
184
|
+
type ScheduleOptions = {
|
|
185
|
+
step: number; // seconds per step; finite, > 0
|
|
186
|
+
maxSteps: number; // safe integer >= 1: most steps one tick may run
|
|
187
|
+
key?: string; // scratch key for the accumulator; unique by default
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
type Schedule = {
|
|
191
|
+
readonly step: number;
|
|
192
|
+
readonly maxSteps: number;
|
|
193
|
+
readonly key: string;
|
|
194
|
+
tick(handle: WorldHandle, frameDt: number): number;
|
|
195
|
+
remainder(handle: WorldHandle): number;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
function createSchedule(systems: readonly System[], options: ScheduleOptions): Readonly<Schedule>;
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`createSchedule` copies `systems` and validates `step`, `maxSteps`, `key` and
|
|
202
|
+
every system before returning; bad configuration throws `TypeError` or
|
|
203
|
+
`RangeError` naming `createSchedule`.
|
|
204
|
+
|
|
205
|
+
`tick(handle, frameDt)` adds `frameDt` to an accumulator stored in
|
|
206
|
+
`handle.scratch` under `key`, then, while at least one whole `step` is owed and
|
|
207
|
+
fewer than `maxSteps` have run, calls `advanceClock(handle, step)` and then
|
|
208
|
+
every system in order with `dt === step`. It returns the number of steps run.
|
|
209
|
+
Systems read `dt`, never the frame's dt, so the same elapsed time gives the same
|
|
210
|
+
run at any frame rate, provided no single tick owes more than `maxSteps` (the
|
|
211
|
+
excess is dropped, so one long frame and many short ones then differ).
|
|
212
|
+
|
|
213
|
+
- A `frameDt` that is negative, `NaN` or infinite counts as `0`. `0` is a valid
|
|
214
|
+
pause: no step runs and the clock does not move, even if a failed tick left
|
|
215
|
+
whole steps owed; those wait for the next frame that moves time.
|
|
216
|
+
- When `maxSteps` is reached with whole steps still owed, those steps are
|
|
217
|
+
dropped, not carried. The sub-step fraction is kept, so phase is preserved.
|
|
218
|
+
- Owed time saturates at the largest finite double instead of overflowing.
|
|
219
|
+
- A billionth of a step of tolerance absorbs float drift, so 432 frames of
|
|
220
|
+
1/144 s run the same 180 steps as 180 frames of 1/60 s.
|
|
221
|
+
- The accumulator lives in `scratch`, so it dies with the world and two
|
|
222
|
+
handles never share one. A `key` already holding something other than a
|
|
223
|
+
schedule accumulator throws `TypeError` before the clock moves. Two schedules
|
|
224
|
+
given the same explicit `key` share one accumulator, but an explicit `key`
|
|
225
|
+
may not equal another schedule's generated key, and a generated key skips any
|
|
226
|
+
explicit one.
|
|
227
|
+
- If a system throws, the step it was in is already spent (the clock moved) and
|
|
228
|
+
the steps not yet started stay owed, so a retry does not replay spent time.
|
|
229
|
+
|
|
230
|
+
`remainder(handle)` returns the seconds currently owed, in `[0, step)` between
|
|
231
|
+
ticks (`0` on a handle that has not ticked). `remainder / step` is the
|
|
232
|
+
interpolation alpha for a renderer drawing between sim states.
|
|
233
|
+
|
|
176
234
|
## `eventLog`
|
|
177
235
|
|
|
178
236
|
```ts
|
|
@@ -192,3 +250,27 @@ Logs are created once at module scope but store their arrays in each handle's
|
|
|
192
250
|
scratch map. `drain` truncates the live array in place so an existing internal
|
|
193
251
|
reference is not orphaned. Empty reads do not create a scratch entry. `peek`
|
|
194
252
|
returns a detached array; `size` does not allocate.
|
|
253
|
+
|
|
254
|
+
## `react`
|
|
255
|
+
|
|
256
|
+
Import from `koota-kit/react`. Requires `react` (`>=18`), an optional peer
|
|
257
|
+
dependency; no other entry point imports it.
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
type SimWorldProviderProps = {
|
|
261
|
+
readonly handle: WorldHandle;
|
|
262
|
+
readonly children?: ReactNode;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const SimWorldProvider: FunctionComponent<SimWorldProviderProps>;
|
|
266
|
+
function useSimWorld(): WorldHandle;
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
`SimWorldProvider` supplies `handle` to the tree below it and renders
|
|
270
|
+
`koota/react`'s `WorldProvider` for `handle.world`, so `useWorld`, `useTrait`,
|
|
271
|
+
`useQuery` and `useActions` work under it without a second provider.
|
|
272
|
+
`useSimWorld` returns the provided handle, including its `rng`, `seeds`,
|
|
273
|
+
`clock` and `scratch`, and throws an `Error` when no `SimWorldProvider` is above
|
|
274
|
+
it. Passing a different `handle` re-provides every consumer. The provider does
|
|
275
|
+
not create or destroy the handle: pair `createSimWorld` with `destroySimWorld`
|
|
276
|
+
yourself.
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -11,8 +11,10 @@ traits ───────────────> koota
|
|
|
11
11
|
seed ─────────────────> (leaf: built-in SHA-256)
|
|
12
12
|
rng ──────────────────> seedrandom + seed
|
|
13
13
|
world ────────────────> koota + rng
|
|
14
|
+
schedule ─────────────> world (advanceClock) + WorldHandle scratch
|
|
14
15
|
eventLog ─────────────> WorldHandle scratch
|
|
15
|
-
|
|
16
|
+
react ────────────────> koota/react + react + world (type only)
|
|
17
|
+
index ────────────────> public re-exports only (never react)
|
|
16
18
|
```
|
|
17
19
|
|
|
18
20
|
`traits` is a leaf module: it imports Koota but no sibling runtime module. This
|
|
@@ -33,9 +35,25 @@ text) is a stable contract: changing it would change every derived world.
|
|
|
33
35
|
`world` owns allocation, teardown, time, and facade-owned persistence. It does
|
|
34
36
|
not own application entity serialization.
|
|
35
37
|
|
|
38
|
+
`schedule` turns a frame's dt into whole fixed steps. Its accumulator lives in
|
|
39
|
+
the handle's scratch map, so time owed dies with the run and two schedules or
|
|
40
|
+
two worlds never share it. It imports no renderer: a frame callback calls
|
|
41
|
+
`tick` and that is the whole integration. The step cap drops excess time rather
|
|
42
|
+
than carrying it, so one huge dt cannot cause a catch-up storm.
|
|
43
|
+
|
|
36
44
|
`eventLog` uses the handle's scratch map so event lifetime matches world
|
|
37
45
|
lifetime. It does not use a process-global queue.
|
|
38
46
|
|
|
47
|
+
`react` is the only module that imports React, through `koota/react`. It is
|
|
48
|
+
reachable only from the `koota-kit/react` subpath: `index` does not re-export it,
|
|
49
|
+
and `react` is an optional peer dependency, so an application that never imports
|
|
50
|
+
that subpath never needs React installed. It carries the `WorldHandle` through a
|
|
51
|
+
context and mounts Koota's own `WorldProvider` for `handle.world`, which keeps
|
|
52
|
+
one source of truth for the world. It imports `world` for types only and owns no
|
|
53
|
+
lifecycle: creating and destroying the handle stays with the application.
|
|
54
|
+
`pnpm package:check` walks every other built entry's import graph and fails if
|
|
55
|
+
it reaches `react` or `koota/react`.
|
|
56
|
+
|
|
39
57
|
## Invariants
|
|
40
58
|
|
|
41
59
|
1. Seed values on a handle are copied and frozen at creation.
|
|
@@ -47,6 +65,11 @@ lifetime. It does not use a process-global queue.
|
|
|
47
65
|
7. World teardown clears facade-owned scratch state and releases the Koota ID.
|
|
48
66
|
8. A named substream's sequence depends only on its seed and name; creating or
|
|
49
67
|
consuming another substream never shifts it.
|
|
68
|
+
9. Only `koota-kit/react` imports React; every other entry stays React-free.
|
|
69
|
+
10. A schedule's systems only ever see `dt === step`; the same elapsed time
|
|
70
|
+
delivered at any frame rate runs the same steps in the same order, as long
|
|
71
|
+
as no single tick owes more than `maxSteps` (beyond that, the excess is
|
|
72
|
+
dropped by design, so a one-second frame and sixty 1/60 s frames differ).
|
|
50
73
|
|
|
51
74
|
## Performance choices
|
|
52
75
|
|
|
@@ -61,8 +84,10 @@ lifetime. It does not use a process-global queue.
|
|
|
61
84
|
## Intentional limits
|
|
62
85
|
|
|
63
86
|
- No entity/trait serializer: applications need versioned domain schemas.
|
|
64
|
-
- No
|
|
65
|
-
|
|
87
|
+
- No system graph: `createSchedule` runs an ordered list once per fixed step,
|
|
88
|
+
and nothing more. There is no dependency resolution, parallelism, or
|
|
89
|
+
per-system rate; a system that wants a slower cadence counts steps itself.
|
|
90
|
+
Koota's queries/actions remain the execution model inside each system.
|
|
66
91
|
- No implicit derived keys: `deriveSeed` hashes only the parts the application
|
|
67
92
|
passes, so the application still owns stable domain keys.
|
|
68
93
|
- No multi-consumer queue semantics: publish separate logs when two systems
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koota-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Deterministic Koota simulation conventions: world lifecycle, dual RNG streams, safe object traits, and world-scoped event logs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -63,6 +63,16 @@
|
|
|
63
63
|
"default": "./dist/cjs/traits/index.cjs"
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
|
+
"./schedule": {
|
|
67
|
+
"import": {
|
|
68
|
+
"types": "./dist/esm/schedule.d.ts",
|
|
69
|
+
"default": "./dist/esm/schedule.js"
|
|
70
|
+
},
|
|
71
|
+
"require": {
|
|
72
|
+
"types": "./dist/cjs/schedule.d.cts",
|
|
73
|
+
"default": "./dist/cjs/schedule.cjs"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
66
76
|
"./eventLog": {
|
|
67
77
|
"import": {
|
|
68
78
|
"types": "./dist/esm/eventLog.d.ts",
|
|
@@ -73,6 +83,16 @@
|
|
|
73
83
|
"default": "./dist/cjs/eventLog.cjs"
|
|
74
84
|
}
|
|
75
85
|
},
|
|
86
|
+
"./react": {
|
|
87
|
+
"import": {
|
|
88
|
+
"types": "./dist/esm/react.d.ts",
|
|
89
|
+
"default": "./dist/esm/react.js"
|
|
90
|
+
},
|
|
91
|
+
"require": {
|
|
92
|
+
"types": "./dist/cjs/react.d.cts",
|
|
93
|
+
"default": "./dist/cjs/react.cjs"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
76
96
|
"./package.json": "./package.json"
|
|
77
97
|
},
|
|
78
98
|
"files": [
|
|
@@ -109,18 +129,33 @@
|
|
|
109
129
|
"seedrandom": "^3.0.5"
|
|
110
130
|
},
|
|
111
131
|
"peerDependencies": {
|
|
112
|
-
"
|
|
132
|
+
"@types/react": ">=18.0.0",
|
|
133
|
+
"koota": "^0.6.6",
|
|
134
|
+
"react": ">=18.0.0"
|
|
135
|
+
},
|
|
136
|
+
"peerDependenciesMeta": {
|
|
137
|
+
"@types/react": {
|
|
138
|
+
"optional": true
|
|
139
|
+
},
|
|
140
|
+
"react": {
|
|
141
|
+
"optional": true
|
|
142
|
+
}
|
|
113
143
|
},
|
|
114
144
|
"devDependencies": {
|
|
115
145
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
116
146
|
"@biomejs/biome": "^2.5.5",
|
|
117
147
|
"@commitlint/cli": "^21.2.2",
|
|
118
148
|
"@commitlint/config-conventional": "^21.2.2",
|
|
149
|
+
"@types/react": "^19.3.0",
|
|
150
|
+
"@types/react-dom": "^19.3.0",
|
|
119
151
|
"@vitest/coverage-v8": "^3.2.7",
|
|
152
|
+
"jsdom": "^30.1.2",
|
|
120
153
|
"koota": "^0.6.6",
|
|
121
154
|
"lint-staged": "^17.3.0",
|
|
122
155
|
"markdownlint-cli2": "^0.23.2",
|
|
123
156
|
"publint": "^0.3.24",
|
|
157
|
+
"react": "^19.3.0",
|
|
158
|
+
"react-dom": "^19.3.0",
|
|
124
159
|
"simple-git-hooks": "^2.13.1",
|
|
125
160
|
"typescript": "^5.9.2",
|
|
126
161
|
"vitest": "^3.2.4"
|
|
@@ -163,9 +198,15 @@
|
|
|
163
198
|
"traits": [
|
|
164
199
|
"./dist/esm/traits/index.d.ts"
|
|
165
200
|
],
|
|
201
|
+
"schedule": [
|
|
202
|
+
"./dist/esm/schedule.d.ts"
|
|
203
|
+
],
|
|
166
204
|
"eventLog": [
|
|
167
205
|
"./dist/esm/eventLog.d.ts"
|
|
168
206
|
],
|
|
207
|
+
"react": [
|
|
208
|
+
"./dist/esm/react.d.ts"
|
|
209
|
+
],
|
|
169
210
|
"*": [
|
|
170
211
|
"./dist/esm/index.d.ts"
|
|
171
212
|
]
|