@simpludify/core 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/foundation/version.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +65 -10
- package/dist/index.js.map +1 -1
- package/dist/interaction/actions.d.ts +5 -0
- package/dist/interaction/input-source.d.ts +3 -0
- package/dist/interaction/input.d.ts +9 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# @simpludify/core
|
|
2
2
|
|
|
3
|
-
Experimental v0.1.
|
|
3
|
+
Experimental v0.1.4 of Simpludify, a code-first TypeScript 2D engine. APIs may change between 0.x releases. This package contains host-independent Scene/Actor/Component, collision, geometry, tilemaps, tween/animation, input and Canvas UI models. It has no browser or Node runtime dependencies.
|
|
4
4
|
|
|
5
|
-
Registry installation (check availability with `npm view @simpludify/core@0.1.
|
|
5
|
+
Registry installation (check availability with `npm view @simpludify/core@0.1.4 version`):
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
npm install @simpludify/core@0.1.
|
|
8
|
+
npm install @simpludify/core@0.1.4
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
```js
|
|
@@ -19,3 +19,5 @@ engine.dispose();
|
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
ESM only, ES2022, Node >=22.12 for headless usage. No CommonJS or renderer is included. For browser presentation install `@simpludify/web`, which depends on this package. JavaScript source maps embed the original sources; declarations are included. [Repository and API](https://github.com/oz-coden/Simpludify) · [Documentation](https://github.oz-coden.com/Simpludify/). MIT.
|
|
22
|
+
|
|
23
|
+
Registry availability is recorded in the repository release report; a source version alone does not establish publication.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Source workspace version; checked against package.json by the build validator. */
|
|
2
|
-
export declare const ENGINE_VERSION = "0.1.
|
|
2
|
+
export declare const ENGINE_VERSION = "0.1.4";
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export * from "./graphics/sprite-sheet.js";
|
|
|
20
20
|
export * from "./graphics/tile-geometry.js";
|
|
21
21
|
export type { ActionFrame, ActionSchema } from "./interaction/action-frame.js";
|
|
22
22
|
export * from "./interaction/actions.js";
|
|
23
|
+
export type { InputAdapter } from "./interaction/input.js";
|
|
23
24
|
export { InputSystem, KeyboardSource } from "./interaction/input.js";
|
|
24
25
|
export * from "./interaction/key-code.js";
|
|
25
26
|
export * from "./interaction/pointer-routing.js";
|
package/dist/index.js
CHANGED
|
@@ -1583,7 +1583,7 @@ var RandomManager = class {
|
|
|
1583
1583
|
};
|
|
1584
1584
|
// packages/core/src/foundation/version.ts
|
|
1585
1585
|
/** Source workspace version; checked against package.json by the build validator. */
|
|
1586
|
-
var ENGINE_VERSION = "0.1.
|
|
1586
|
+
var ENGINE_VERSION = "0.1.4";
|
|
1587
1587
|
// packages/core/src/scene/actor.ts
|
|
1588
1588
|
var inputPhase$1 = ComponentPhase.Input;
|
|
1589
1589
|
var updatePhase$1 = ComponentPhase.Update;
|
|
@@ -4452,16 +4452,27 @@ function defineActions(definitions) {
|
|
|
4452
4452
|
/** @internal Shared lifetime for logical and device contributions. */
|
|
4453
4453
|
var OwnedInputSource = class {
|
|
4454
4454
|
dead = false;
|
|
4455
|
+
accepting = true;
|
|
4455
4456
|
removeOwner;
|
|
4456
4457
|
get disposed() {
|
|
4457
4458
|
return this.dead;
|
|
4458
4459
|
}
|
|
4460
|
+
get enabled() {
|
|
4461
|
+
return this.accepting;
|
|
4462
|
+
}
|
|
4463
|
+
set enabled(value) {
|
|
4464
|
+
if (this.dead) throw new EngineError("OWNER_DISPOSED", "Input source has been disposed");
|
|
4465
|
+
if (typeof value !== "boolean") throw new EngineError("INVALID_VALUE", "Source enabled must be boolean");
|
|
4466
|
+
if (value === this.accepting) return;
|
|
4467
|
+
this.accepting = value;
|
|
4468
|
+
if (!value) this.release(false);
|
|
4469
|
+
}
|
|
4459
4470
|
/** @internal */ own(owner) {
|
|
4460
4471
|
if (owner.disposed) throw new EngineError("OWNER_DISPOSED", "Input owner has been disposed");
|
|
4461
4472
|
this.removeOwner = owner.onDispose(() => this.dispose());
|
|
4462
4473
|
}
|
|
4463
4474
|
writable(neutral) {
|
|
4464
|
-
if (!this.dead) return
|
|
4475
|
+
if (!this.dead) return this.accepting;
|
|
4465
4476
|
if (neutral) return false;
|
|
4466
4477
|
throw new EngineError("OWNER_DISPOSED", "Input source has been disposed");
|
|
4467
4478
|
}
|
|
@@ -4509,13 +4520,17 @@ var ActionController = class {
|
|
|
4509
4520
|
entries = /* @__PURE__ */ new Map();
|
|
4510
4521
|
sources = /* @__PURE__ */ new Map();
|
|
4511
4522
|
bus = new EventBus();
|
|
4523
|
+
keyboardAllowed = true;
|
|
4524
|
+
blockedKeys = /* @__PURE__ */ new Set();
|
|
4512
4525
|
pending = [];
|
|
4513
4526
|
transitions = [];
|
|
4514
4527
|
constructor(actions, options) {
|
|
4515
4528
|
this.actions = actions;
|
|
4516
4529
|
this.options = options;
|
|
4530
|
+
if (options.keyboard !== void 0 && typeof options.keyboard !== "boolean") throw new EngineError("INVALID_VALUE", "Keyboard enabled must be boolean");
|
|
4531
|
+
this.keyboardAllowed = options.keyboard ?? true;
|
|
4517
4532
|
for (const action of Object.values(actions)) {
|
|
4518
|
-
const binding = checkedBinding(action, options.bindings
|
|
4533
|
+
const binding = checkedBinding(action, options.bindings && Object.hasOwn(options.bindings, action.name) ? options.bindings[action.name] ?? {} : {});
|
|
4519
4534
|
const value = neutral(action);
|
|
4520
4535
|
this.entries.set(action, {
|
|
4521
4536
|
value,
|
|
@@ -4543,6 +4558,20 @@ var ActionController = class {
|
|
|
4543
4558
|
get active() {
|
|
4544
4559
|
return !this.disposed && this.options.scene.state === SceneState.Active && this.options.scene.inputOwner === this.options.input;
|
|
4545
4560
|
}
|
|
4561
|
+
get keyboardEnabled() {
|
|
4562
|
+
return this.keyboardAllowed;
|
|
4563
|
+
}
|
|
4564
|
+
set keyboardEnabled(value) {
|
|
4565
|
+
if (this.disposed) throw new EngineError("OWNER_DISPOSED", "Action controller has been disposed");
|
|
4566
|
+
if (typeof value !== "boolean") throw new EngineError("INVALID_VALUE", "Keyboard enabled must be boolean");
|
|
4567
|
+
if (value === this.keyboardAllowed) return;
|
|
4568
|
+
this.keyboardAllowed = value;
|
|
4569
|
+
this.blockedKeys.clear();
|
|
4570
|
+
if (value) {
|
|
4571
|
+
for (const entry of this.entries.values()) for (const list of keyLists(entry.binding)) for (const key of list.keys) if (this.options.input.keyDown(key)) this.blockedKeys.add(key);
|
|
4572
|
+
}
|
|
4573
|
+
for (const action of this.entries.keys()) this.recompute(action);
|
|
4574
|
+
}
|
|
4546
4575
|
get sourceCount() {
|
|
4547
4576
|
return this.sources.size;
|
|
4548
4577
|
}
|
|
@@ -4641,8 +4670,9 @@ var ActionController = class {
|
|
|
4641
4670
|
if (!this.disposed) for (const action of actions) this.recompute(action);
|
|
4642
4671
|
}
|
|
4643
4672
|
keyboard(action) {
|
|
4673
|
+
if (!this.keyboardAllowed) return neutral(action);
|
|
4644
4674
|
const binding = this.entry(action).binding;
|
|
4645
|
-
const held = (keys) => keys?.some((k) => this.options.input.keyDown(k)) ?? false;
|
|
4675
|
+
const held = (keys) => keys?.some((k) => !this.blockedKeys.has(k) && this.options.input.keyDown(k)) ?? false;
|
|
4646
4676
|
const axis = (b) => Number(held(b?.positive)) - Number(held(b?.negative));
|
|
4647
4677
|
if (action.kind === "button") return held(binding.keys);
|
|
4648
4678
|
if (action.kind === "axis1d") return axis(binding);
|
|
@@ -4696,10 +4726,11 @@ var ActionController = class {
|
|
|
4696
4726
|
entry.value = value;
|
|
4697
4727
|
}
|
|
4698
4728
|
/** @internal */ keyChanged() {
|
|
4729
|
+
for (const key of this.blockedKeys) if (!this.options.input.keyDown(key)) this.blockedKeys.delete(key);
|
|
4699
4730
|
if (this.active || !this.disposed && this.options.scene.inputOwner === this.options.input && (this.options.scene.state === SceneState.Preparing || this.options.scene.state === SceneState.Ready)) for (const action of this.entries.keys()) this.recompute(action);
|
|
4700
4731
|
}
|
|
4701
4732
|
/** @internal */ accepts(code) {
|
|
4702
|
-
return this.active && [...this.entries.values()].some((e) => keyLists(e.binding).some((list) => list.keys.includes(code)));
|
|
4733
|
+
return this.active && this.keyboardAllowed && [...this.entries.values()].some((e) => keyLists(e.binding).some((list) => list.keys.includes(code)));
|
|
4703
4734
|
}
|
|
4704
4735
|
/** @internal */ capture(override) {
|
|
4705
4736
|
if (!this.active) return;
|
|
@@ -4741,6 +4772,7 @@ var ActionController = class {
|
|
|
4741
4772
|
}
|
|
4742
4773
|
/** Clears held contributions and unconsumed edges; no synthetic button release. */
|
|
4743
4774
|
reset() {
|
|
4775
|
+
this.blockedKeys.clear();
|
|
4744
4776
|
for (const values of this.sources.values()) values.clear();
|
|
4745
4777
|
this.pending = [];
|
|
4746
4778
|
for (const [action, entry] of this.entries) entry.value = neutral(action);
|
|
@@ -4840,6 +4872,7 @@ var KeyboardSource = class extends OwnedInputSource {
|
|
|
4840
4872
|
/** Engine-owned routing and update boundary. No raw game-state/alternate movement API. */
|
|
4841
4873
|
var InputSystem = class {
|
|
4842
4874
|
actions = /* @__PURE__ */ new Set();
|
|
4875
|
+
adapters = /* @__PURE__ */ new Set();
|
|
4843
4876
|
keyboards = /* @__PURE__ */ new Map();
|
|
4844
4877
|
dead = false;
|
|
4845
4878
|
playback = false;
|
|
@@ -4856,6 +4889,14 @@ var InputSystem = class {
|
|
|
4856
4889
|
this.actions.delete(actions);
|
|
4857
4890
|
};
|
|
4858
4891
|
}
|
|
4892
|
+
/** Host integration boundary; ordinary game input uses ActionSource instead. */
|
|
4893
|
+
addAdapter(adapter) {
|
|
4894
|
+
if (this.dead) throw new EngineError("OWNER_DISPOSED", "Input has been disposed");
|
|
4895
|
+
this.adapters.add(adapter);
|
|
4896
|
+
return () => {
|
|
4897
|
+
this.adapters.delete(adapter);
|
|
4898
|
+
};
|
|
4899
|
+
}
|
|
4859
4900
|
acceptsKey(code) {
|
|
4860
4901
|
if (this.playback) return false;
|
|
4861
4902
|
for (const actions of this.actions) if (actions.accepts(code)) return true;
|
|
@@ -4871,6 +4912,9 @@ var InputSystem = class {
|
|
|
4871
4912
|
}
|
|
4872
4913
|
/** @internal Freeze every controller before invoking any callbacks. */
|
|
4873
4914
|
beginStep(captured) {
|
|
4915
|
+
if (!this.playback && this.adapters.size) {
|
|
4916
|
+
for (const adapter of [...this.adapters]) if (this.adapters.has(adapter)) adapter.sample();
|
|
4917
|
+
}
|
|
4874
4918
|
if (!this.actions.size) {
|
|
4875
4919
|
captured?.();
|
|
4876
4920
|
return;
|
|
@@ -4945,7 +4989,9 @@ var InputSystem = class {
|
|
|
4945
4989
|
}
|
|
4946
4990
|
clear() {
|
|
4947
4991
|
for (const keys of this.keyboards.values()) keys.clear();
|
|
4948
|
-
|
|
4992
|
+
cleanupAll([...[...this.actions].map((actions) => () => actions.reset()), ...[...this.adapters].map((adapter) => () => {
|
|
4993
|
+
if (this.adapters.has(adapter)) adapter.reset();
|
|
4994
|
+
})], "Input reset failed");
|
|
4949
4995
|
}
|
|
4950
4996
|
/** Device diagnostics only; game state is ActionController.snapshot(). */
|
|
4951
4997
|
snapshot() {
|
|
@@ -4957,8 +5003,13 @@ var InputSystem = class {
|
|
|
4957
5003
|
if (this.dead) return;
|
|
4958
5004
|
this.dead = true;
|
|
4959
5005
|
try {
|
|
4960
|
-
cleanupAll([
|
|
5006
|
+
cleanupAll([
|
|
5007
|
+
...[...this.adapters].map((a) => () => a.dispose()),
|
|
5008
|
+
...[...this.actions].map((a) => () => a.dispose()),
|
|
5009
|
+
...[...this.keyboards.keys()].map((s) => () => s.dispose())
|
|
5010
|
+
], "Input cleanup failed");
|
|
4961
5011
|
} finally {
|
|
5012
|
+
this.adapters.clear();
|
|
4962
5013
|
this.actions.clear();
|
|
4963
5014
|
this.keyboards.clear();
|
|
4964
5015
|
}
|
|
@@ -6269,12 +6320,12 @@ var ReplayPlayer = class {
|
|
|
6269
6320
|
this.engine = engine;
|
|
6270
6321
|
this.data = parseReplayData(data);
|
|
6271
6322
|
this.bound = bindings(engine, options.controllers);
|
|
6272
|
-
if (this.data.engineVersion !== "0.1.
|
|
6323
|
+
if (this.data.engineVersion !== "0.1.4" || engine.random.seed !== this.data.random.seed) throw new EngineError("REPLAY_INCOMPATIBLE", "Engine version or initial seed does not match Replay");
|
|
6273
6324
|
if (!compatible(this.bound.map(([id, c]) => ({
|
|
6274
6325
|
id,
|
|
6275
6326
|
actions: c.schema()
|
|
6276
6327
|
})), this.data.controllers)) throw new EngineError("REPLAY_INCOMPATIBLE", "Controller IDs or Action names/kinds/normalization do not match Replay");
|
|
6277
|
-
for (const [key, value] of Object.entries(options.expectedMetadata ?? {})) if (!compatible(copyJson(value), this.data.metadata[key])) throw new EngineError("REPLAY_INCOMPATIBLE", `Metadata mismatch: ${key}`);
|
|
6328
|
+
for (const [key, value] of Object.entries(options.expectedMetadata ?? {})) if (!Object.hasOwn(this.data.metadata, key) || !compatible(copyJson(value), this.data.metadata[key])) throw new EngineError("REPLAY_INCOMPATIBLE", `Metadata mismatch: ${key}`);
|
|
6278
6329
|
this.detach = engine.attachExecution(this);
|
|
6279
6330
|
try {
|
|
6280
6331
|
engine.input.lockPlayback();
|
|
@@ -6515,7 +6566,7 @@ function checkedStyle(value) {
|
|
|
6515
6566
|
"highlight"
|
|
6516
6567
|
]
|
|
6517
6568
|
};
|
|
6518
|
-
if (typeof v !== "string" || (choices
|
|
6569
|
+
if (typeof v !== "string" || (Object.hasOwn(choices, key) ? !choices[key]?.includes(v) : !validFontFamily(v))) throw new EngineError("INVALID_UI", `Invalid UI ${key}`);
|
|
6519
6570
|
}
|
|
6520
6571
|
}
|
|
6521
6572
|
return Object.freeze(result);
|
|
@@ -6947,6 +6998,10 @@ function CanvasScroll(children = [], style = {}) {
|
|
|
6947
6998
|
clip: true
|
|
6948
6999
|
});
|
|
6949
7000
|
}
|
|
7001
|
+
function validFontFamily(value) {
|
|
7002
|
+
const syntax = value.replace(/"(?:\\[^\r\n]|[^"\\])*"|'(?:\\[^\r\n]|[^'\\])*'/g, "family").replace(/\\(?:[0-9a-f]{1,6}[ \t]?|[^\r\n])/gi, "x");
|
|
7003
|
+
return value.trim().length > 0 && !/[\r\n\0]/.test(value) && !/[;{}<>\\"']|url\s*\(/i.test(syntax);
|
|
7004
|
+
}
|
|
6950
7005
|
// packages/core/src/ui/canvas-input.ts
|
|
6951
7006
|
var boolean = (value) => {
|
|
6952
7007
|
if (typeof value !== "boolean") throw new EngineError("INVALID_UI", "Control flag must be boolean");
|