koota-kit 0.1.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/CONTRIBUTING.md +51 -0
  3. package/LICENSE +21 -0
  4. package/README.md +215 -0
  5. package/SECURITY.md +25 -0
  6. package/dist/cjs/eventLog.cjs +81 -0
  7. package/dist/cjs/eventLog.d.cts +49 -0
  8. package/dist/cjs/eventLog.d.cts.map +1 -0
  9. package/dist/cjs/index.cjs +36 -0
  10. package/dist/cjs/index.d.cts +9 -0
  11. package/dist/cjs/index.d.cts.map +1 -0
  12. package/dist/cjs/package.json +3 -0
  13. package/dist/cjs/rng.cjs +168 -0
  14. package/dist/cjs/rng.d.cts +86 -0
  15. package/dist/cjs/rng.d.cts.map +1 -0
  16. package/dist/cjs/traits/index.cjs +54 -0
  17. package/dist/cjs/traits/index.d.cts +48 -0
  18. package/dist/cjs/traits/index.d.cts.map +1 -0
  19. package/dist/cjs/world.cjs +111 -0
  20. package/dist/cjs/world.d.cts +83 -0
  21. package/dist/cjs/world.d.cts.map +1 -0
  22. package/dist/esm/eventLog.d.ts +49 -0
  23. package/dist/esm/eventLog.d.ts.map +1 -0
  24. package/dist/esm/eventLog.js +78 -0
  25. package/dist/esm/index.d.ts +9 -0
  26. package/dist/esm/index.d.ts.map +1 -0
  27. package/dist/esm/index.js +14 -0
  28. package/dist/esm/rng.d.ts +86 -0
  29. package/dist/esm/rng.d.ts.map +1 -0
  30. package/dist/esm/rng.js +154 -0
  31. package/dist/esm/traits/index.d.ts +48 -0
  32. package/dist/esm/traits/index.d.ts.map +1 -0
  33. package/dist/esm/traits/index.js +51 -0
  34. package/dist/esm/world.d.ts +83 -0
  35. package/dist/esm/world.d.ts.map +1 -0
  36. package/dist/esm/world.js +101 -0
  37. package/docs/API.md +119 -0
  38. package/docs/ARCHITECTURE.md +57 -0
  39. package/docs/assets/favicon.svg +1 -0
  40. package/docs/assets/koota-kit-hero.webp +0 -0
  41. package/docs/changelog.md +13 -0
  42. package/docs/contributing.md +28 -0
  43. package/examples/basic.mjs +27 -0
  44. package/examples/commonjs.cjs +8 -0
  45. package/package.json +173 -0
package/docs/API.md ADDED
@@ -0,0 +1,119 @@
1
+ # API reference
2
+
3
+ All functions and types are exported from `koota-kit`. The
4
+ module-specific import paths shown below can reduce coupling in larger
5
+ codebases.
6
+
7
+ ## `world`
8
+
9
+ ### `createSimWorld(seeds)`
10
+
11
+ ```ts
12
+ function createSimWorld(seeds: RngSeeds): WorldHandle;
13
+ ```
14
+
15
+ Creates a Koota `World`, two seeded RNG streams, immutable copied seeds, a
16
+ zeroed clock, and an empty scratch map. Seeds must be strings or finite
17
+ numbers. Validation happens before the Koota world is allocated.
18
+
19
+ ### `destroySimWorld(handle)`
20
+
21
+ ```ts
22
+ function destroySimWorld(handle: WorldHandle): void;
23
+ ```
24
+
25
+ Clears scratch state and destroys the Koota world if it is still active. The
26
+ operation is idempotent.
27
+
28
+ ### `advanceClock(handle, dt)`
29
+
30
+ ```ts
31
+ function advanceClock(handle: WorldHandle, dt: number): void;
32
+ ```
33
+
34
+ Advances `tickIndex` by one and adds `dt` to `simSeconds`. `dt` must be finite
35
+ and non-negative. A zero-duration tick is allowed. Overflow and corrupted clock
36
+ values raise `RangeError` without changing the clock.
37
+
38
+ ### `snapshotWorld(handle)` / `restoreWorldHeader(handle, snapshot)`
39
+
40
+ ```ts
41
+ function snapshotWorld(handle: WorldHandle): WorldSnapshot;
42
+ function restoreWorldHeader(handle: WorldHandle, snapshot: WorldSnapshot): void;
43
+ ```
44
+
45
+ Snapshots and restores both RNG streams plus the simulation clock. The plain
46
+ object can be round-tripped through JSON. Entity and trait data are outside
47
+ this package's persistence boundary. Restore validates the complete replacement
48
+ before mutating the handle.
49
+
50
+ ### Koota exports
51
+
52
+ `createActions`, `relation`, and `trait` are re-exported so simulation code can
53
+ keep its Koota imports at one boundary. `Entity` and `World` are exported as
54
+ types.
55
+
56
+ ## `rng`
57
+
58
+ ```ts
59
+ type RngSeeds = {
60
+ readonly gen: string | number;
61
+ readonly events: string | number;
62
+ };
63
+
64
+ function createRng(seeds: RngSeeds): RngLayers;
65
+ function nextU32(stream: RngStream): number;
66
+ function nextFloat(stream: RngStream): number;
67
+ function nextInt(stream: RngStream, minInclusive: number, maxExclusive: number): number;
68
+ function chance(stream: RngStream, probability: number): boolean;
69
+ function snapshotStream(stream: RngStream): RngStreamSnapshot;
70
+ function restoreStream(snapshot: RngStreamSnapshot): RngStream;
71
+ function snapshotLayers(layers: RngLayers): RngLayersSnapshot;
72
+ function restoreLayers(snapshot: RngLayersSnapshot): RngLayers;
73
+ ```
74
+
75
+ - `nextU32` returns an unsigned 32-bit integer.
76
+ - `nextFloat` returns a value in `[0, 1)`.
77
+ - `nextInt` uses half-open bounds and exactly one float draw after validation.
78
+ - `chance` consumes one draw for every valid probability, including `0` and
79
+ `1`, keeping later draw positions predictable.
80
+ - Restore functions reject malformed ARC4 state and clone accepted state so a
81
+ snapshot can seed multiple independent replays.
82
+
83
+ ## `traits`
84
+
85
+ ```ts
86
+ function defineTrait<S extends Schema>(schema: S & SafeSchema<S>): Trait<Norm<S>>;
87
+ ```
88
+
89
+ Pass primitive fields directly. Wrap each object- or array-valued field in a
90
+ factory, or use a whole-trait factory:
91
+
92
+ ```ts
93
+ const Health = defineTrait({ value: 100 });
94
+ const Path = defineTrait({ points: () => [] as Array<{ x: number; y: number }> });
95
+ const Pose = defineTrait(() => ({ position: { x: 0, y: 0 } }));
96
+ ```
97
+
98
+ Bare object and array fields are rejected by `SafeSchema` and by the runtime
99
+ guard.
100
+
101
+ ## `eventLog`
102
+
103
+ ```ts
104
+ type EventLog<T> = {
105
+ readonly key: string;
106
+ push(handle: WorldHandle, event: T): void;
107
+ drain(handle: WorldHandle): T[];
108
+ peek(handle: WorldHandle): readonly T[];
109
+ clear(handle: WorldHandle): void;
110
+ size(handle: WorldHandle): number;
111
+ };
112
+
113
+ function defineEventLog<T>(key: string): EventLog<T>;
114
+ ```
115
+
116
+ Logs are created once at module scope but store their arrays in each handle's
117
+ scratch map. `drain` truncates the live array in place so an existing internal
118
+ reference is not orphaned. Empty reads do not create a scratch entry. `peek`
119
+ returns a detached array; `size` does not allocate.
@@ -0,0 +1,57 @@
1
+ # Architecture
2
+
3
+ koota-kit is intentionally a convention layer, not an abstraction over every
4
+ Koota capability. Consumers still use Koota entities, traits, relations,
5
+ queries, and actions directly.
6
+
7
+ ## Module boundaries
8
+
9
+ ```text
10
+ traits ───────────────> koota
11
+ rng ──────────────────> seedrandom
12
+ world ────────────────> koota + rng
13
+ eventLog ─────────────> WorldHandle scratch
14
+ index ────────────────> public re-exports only
15
+ ```
16
+
17
+ `traits` is a leaf module: it imports Koota but no sibling runtime module. This
18
+ keeps trait declarations out of world/scenario evaluation cycles and preserves
19
+ the reference identity Koota uses to identify traits.
20
+
21
+ `rng` has no Koota dependency. Its serialized contract is the seedrandom ARC4
22
+ state plus a two-stream container.
23
+
24
+ `world` owns allocation, teardown, time, and facade-owned persistence. It does
25
+ not own application entity serialization.
26
+
27
+ `eventLog` uses the handle's scratch map so event lifetime matches world
28
+ lifetime. It does not use a process-global queue.
29
+
30
+ ## Invariants
31
+
32
+ 1. Seed values on a handle are copied and frozen at creation.
33
+ 2. World-generation and runtime-event draws never share a PRNG instance.
34
+ 3. Invalid draw parameters do not advance a stream.
35
+ 4. Restoring a header is atomic from the caller's perspective.
36
+ 5. Object-valued structure-of-arrays fields use factories.
37
+ 6. Event logs have one consuming owner; observers use `peek`.
38
+ 7. World teardown clears facade-owned scratch state and releases the Koota ID.
39
+
40
+ ## Performance choices
41
+
42
+ - RNG draws delegate directly to seedrandom after one small validation step.
43
+ - Snapshots clone the 256-byte permutation explicitly instead of serializing
44
+ through JSON.
45
+ - Event logs allocate on first push, leave scratch untouched for empty reads,
46
+ truncate in place on drain, and expose a non-allocating `size` operation.
47
+ - The package is not bundled. ESM and CommonJS outputs preserve small subpath
48
+ entry points and let application bundlers tree-shake normally.
49
+
50
+ ## Intentional limits
51
+
52
+ - No entity/trait serializer: applications need versioned domain schemas.
53
+ - No scheduler or system runner: Koota's queries/actions remain the execution
54
+ model.
55
+ - No automatic derived-key hashing: the application owns stable domain keys.
56
+ - No multi-consumer queue semantics: publish separate logs when two systems
57
+ must independently consume the same fact.
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill-rule="evenodd" d="M81 36 64 0 47 36l-1 2-9-10a6 6 0 0 0-9 9l10 10h-2L0 64l36 17h2L28 91a6 6 0 1 0 9 9l9-10 1 2 17 36 17-36v-2l9 10a6 6 0 1 0 9-9l-9-9 2-1 36-17-36-17-2-1 9-9a6 6 0 1 0-9-9l-9 10v-2Zm-17 2-2 5c-4 8-11 15-19 19l-5 2 5 2c8 4 15 11 19 19l2 5 2-5c4-8 11-15 19-19l5-2-5-2c-8-4-15-11-19-19l-2-5Z" clip-rule="evenodd"/><path d="M118 19a6 6 0 0 0-9-9l-3 3a6 6 0 1 0 9 9l3-3Zm-96 4c-2 2-6 2-9 0l-3-3a6 6 0 1 1 9-9l3 3c3 2 3 6 0 9Zm0 82c-2-2-6-2-9 0l-3 3a6 6 0 1 0 9 9l3-3c3-2 3-6 0-9Zm96 4a6 6 0 0 1-9 9l-3-3a6 6 0 1 1 9-9l3 3Z"/></svg>
Binary file
@@ -0,0 +1,13 @@
1
+ ---
2
+ title: Changelog
3
+ description: Release history maintained by Release Please from Conventional Commits.
4
+ ---
5
+
6
+ Release Please maintains the canonical
7
+ [CHANGELOG.md](https://github.com/jbcom/koota-kit/blob/main/CHANGELOG.md),
8
+ version metadata, tags, and GitHub Releases. Published releases use npm
9
+ provenance and the project supports the latest `0.x` line.
10
+
11
+ For planned changes, use a Conventional Commit type such as `fix:`, `feat:`,
12
+ `docs:`, `refactor:`, `test:`, or `chore:`. Breaking public API changes need a
13
+ `!` or `BREAKING CHANGE:` footer.
@@ -0,0 +1,28 @@
1
+ ---
2
+ title: Contributing
3
+ description: Set up koota-kit, validate a change, and contribute through the protected workflow.
4
+ ---
5
+
6
+ ## Local workflow
7
+
8
+ ```sh
9
+ mise install
10
+ pnpm install --frozen-lockfile
11
+ pnpm verify
12
+ pnpm docs:build
13
+ ```
14
+
15
+ `pnpm verify` is the library gate: Biome, Markdown linting, strict TypeScript,
16
+ 100% coverage, dual-format builds, runnable examples, package validation, and
17
+ a clean-consumer runtime check. `pnpm docs:build` validates and renders the
18
+ Sourcey site.
19
+
20
+ Branch from `main`, make a focused Conventional Commit, open an upstream pull
21
+ request, and keep the branch current by merging `main` into it when necessary.
22
+ The protected path uses automated checks rather than a routine human approval;
23
+ merge commits preserve the constituent history. Do not hand-edit versions or
24
+ `CHANGELOG.md`: Release Please owns them.
25
+
26
+ Read the repository [contribution guide](https://github.com/jbcom/koota-kit/blob/main/CONTRIBUTING.md)
27
+ and [agent instructions](https://github.com/jbcom/koota-kit/blob/main/AGENTS.md)
28
+ before changing public APIs.
@@ -0,0 +1,27 @@
1
+ import {
2
+ advanceClock,
3
+ createSimWorld,
4
+ defineEventLog,
5
+ defineTrait,
6
+ destroySimWorld,
7
+ nextInt,
8
+ snapshotWorld,
9
+ } from "koota-kit";
10
+
11
+ const Position = defineTrait({ x: 0, y: 0 });
12
+ const moves = defineEventLog("movement:completed");
13
+ const sim = createSimWorld({ gen: "map-42", events: "run-1" });
14
+
15
+ try {
16
+ const scout = sim.world.spawn(Position({ x: 4, y: 9 }));
17
+ advanceClock(sim, 1 / 60);
18
+
19
+ const distance = nextInt(sim.rng.events, 1, 5);
20
+ scout.set(Position, ({ x, y }) => ({ x: x + distance, y }));
21
+ moves.push(sim, { entity: Number(scout), distance });
22
+
23
+ const saveHeader = snapshotWorld(sim);
24
+ console.log({ position: scout.get(Position), events: moves.drain(sim), saveHeader });
25
+ } finally {
26
+ destroySimWorld(sim);
27
+ }
@@ -0,0 +1,8 @@
1
+ const { createSimWorld, destroySimWorld, nextU32 } = require("koota-kit");
2
+
3
+ const sim = createSimWorld({ gen: "commonjs", events: "run-1" });
4
+ try {
5
+ console.log({ firstEventDraw: nextU32(sim.rng.events) });
6
+ } finally {
7
+ destroySimWorld(sim);
8
+ }
package/package.json ADDED
@@ -0,0 +1,173 @@
1
+ {
2
+ "name": "koota-kit",
3
+ "version": "0.1.1",
4
+ "description": "Deterministic Koota simulation conventions: world lifecycle, dual RNG streams, safe object traits, and world-scoped event logs.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "provenance": true
10
+ },
11
+ "sideEffects": false,
12
+ "main": "./dist/cjs/index.cjs",
13
+ "module": "./dist/esm/index.js",
14
+ "types": "./dist/esm/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "import": {
18
+ "types": "./dist/esm/index.d.ts",
19
+ "default": "./dist/esm/index.js"
20
+ },
21
+ "require": {
22
+ "types": "./dist/cjs/index.d.cts",
23
+ "default": "./dist/cjs/index.cjs"
24
+ }
25
+ },
26
+ "./world": {
27
+ "import": {
28
+ "types": "./dist/esm/world.d.ts",
29
+ "default": "./dist/esm/world.js"
30
+ },
31
+ "require": {
32
+ "types": "./dist/cjs/world.d.cts",
33
+ "default": "./dist/cjs/world.cjs"
34
+ }
35
+ },
36
+ "./rng": {
37
+ "import": {
38
+ "types": "./dist/esm/rng.d.ts",
39
+ "default": "./dist/esm/rng.js"
40
+ },
41
+ "require": {
42
+ "types": "./dist/cjs/rng.d.cts",
43
+ "default": "./dist/cjs/rng.cjs"
44
+ }
45
+ },
46
+ "./traits": {
47
+ "import": {
48
+ "types": "./dist/esm/traits/index.d.ts",
49
+ "default": "./dist/esm/traits/index.js"
50
+ },
51
+ "require": {
52
+ "types": "./dist/cjs/traits/index.d.cts",
53
+ "default": "./dist/cjs/traits/index.cjs"
54
+ }
55
+ },
56
+ "./eventLog": {
57
+ "import": {
58
+ "types": "./dist/esm/eventLog.d.ts",
59
+ "default": "./dist/esm/eventLog.js"
60
+ },
61
+ "require": {
62
+ "types": "./dist/cjs/eventLog.d.cts",
63
+ "default": "./dist/cjs/eventLog.cjs"
64
+ }
65
+ },
66
+ "./package.json": "./package.json"
67
+ },
68
+ "files": [
69
+ "CHANGELOG.md",
70
+ "CONTRIBUTING.md",
71
+ "SECURITY.md",
72
+ "dist",
73
+ "docs/API.md",
74
+ "docs/ARCHITECTURE.md",
75
+ "docs/assets",
76
+ "examples"
77
+ ],
78
+ "scripts": {
79
+ "build": "node scripts/build.mjs",
80
+ "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
81
+ "typecheck": "tsc --noEmit -p tsconfig.json",
82
+ "test": "vitest run",
83
+ "test:watch": "vitest",
84
+ "coverage": "vitest run --coverage",
85
+ "examples:check": "node examples/basic.mjs && node examples/commonjs.cjs",
86
+ "lint:docs": "markdownlint-cli2 README.md CONTRIBUTING.md SECURITY.md CODE_OF_CONDUCT.md CHANGELOG.md AGENTS.md \"docs/**/*.md\" \"!docs/node_modules/**\" \"!docs/dist/**\" \"!docs/superpowers/**\" .github/PULL_REQUEST_TEMPLATE.md",
87
+ "package:check": "publint && attw --pack . && node scripts/verify-package.mjs",
88
+ "verify": "pnpm run lint && pnpm run lint:docs && pnpm run typecheck && pnpm run coverage && pnpm run build && pnpm run examples:check && pnpm run package:check",
89
+ "lint": "biome check .",
90
+ "lint:fix": "biome check --write .",
91
+ "prepack": "node scripts/build.mjs",
92
+ "docs:dev": "pnpm --filter koota-kit-docs dev",
93
+ "docs:build": "pnpm --filter koota-kit-docs build",
94
+ "docs:check": "pnpm --filter koota-kit-docs check",
95
+ "prepare": "simple-git-hooks"
96
+ },
97
+ "dependencies": {
98
+ "@types/seedrandom": "^3.0.8",
99
+ "seedrandom": "^3.0.5"
100
+ },
101
+ "peerDependencies": {
102
+ "koota": "^0.6.6"
103
+ },
104
+ "devDependencies": {
105
+ "@arethetypeswrong/cli": "^0.18.5",
106
+ "@biomejs/biome": "^2.5.5",
107
+ "@commitlint/cli": "^21.2.2",
108
+ "@commitlint/config-conventional": "^21.2.2",
109
+ "@vitest/coverage-v8": "^3.2.7",
110
+ "koota": "^0.6.6",
111
+ "lint-staged": "^17.3.0",
112
+ "markdownlint-cli2": "^0.23.2",
113
+ "publint": "^0.3.24",
114
+ "simple-git-hooks": "^2.13.1",
115
+ "typescript": "^5.9.2",
116
+ "vitest": "^3.2.4"
117
+ },
118
+ "simple-git-hooks": {
119
+ "pre-commit": "pnpm exec lint-staged",
120
+ "commit-msg": "pnpm exec commitlint --edit $1"
121
+ },
122
+ "lint-staged": {
123
+ "*.{ts,js,mjs,cjs,json}": [
124
+ "biome check --write --no-errors-on-unmatched"
125
+ ],
126
+ "!(docs/superpowers/**)*.md": [
127
+ "markdownlint-cli2"
128
+ ]
129
+ },
130
+ "repository": {
131
+ "type": "git",
132
+ "url": "git+https://github.com/jbcom/koota-kit.git"
133
+ },
134
+ "packageManager": "pnpm@11.23.0+sha512.f00082e5b283a199b74e079da28d155c008fe232f44c8a06ea7ddfa014ecf719fc362f790ecc67b28106ddac2afb24c49a9a079159da560b2ce7d1e98efd11af",
135
+ "bugs": {
136
+ "url": "https://github.com/jbcom/koota-kit/issues"
137
+ },
138
+ "homepage": "https://github.com/jbcom/koota-kit#readme",
139
+ "engines": {
140
+ "node": ">=22"
141
+ },
142
+ "typesVersions": {
143
+ "*": {
144
+ "world": [
145
+ "./dist/esm/world.d.ts"
146
+ ],
147
+ "rng": [
148
+ "./dist/esm/rng.d.ts"
149
+ ],
150
+ "traits": [
151
+ "./dist/esm/traits/index.d.ts"
152
+ ],
153
+ "eventLog": [
154
+ "./dist/esm/eventLog.d.ts"
155
+ ],
156
+ "*": [
157
+ "./dist/esm/index.d.ts"
158
+ ]
159
+ }
160
+ },
161
+ "author": "Jon Bogaty <jon@jonbogaty.com>",
162
+ "keywords": [
163
+ "koota",
164
+ "ecs",
165
+ "deterministic",
166
+ "rng",
167
+ "seedrandom",
168
+ "game-dev",
169
+ "simulation",
170
+ "procedural-generation",
171
+ "replay"
172
+ ]
173
+ }