@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 CHANGED
@@ -1,11 +1,11 @@
1
1
  # @simpludify/core
2
2
 
3
- Experimental v0.1.3 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.
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.3 version`):
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.3
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.3";
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.3";
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 true;
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?.[action.name] ?? {});
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
- for (const actions of this.actions) actions.reset();
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([...[...this.actions].map((a) => () => a.dispose()), ...[...this.keyboards.keys()].map((s) => () => s.dispose())], "Input cleanup failed");
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.3" || engine.random.seed !== this.data.random.seed) throw new EngineError("REPLAY_INCOMPATIBLE", "Engine version or initial seed does not match Replay");
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[key] ? !choices[key].includes(v) : !v.length)) throw new EngineError("INVALID_UI", `Invalid UI ${key}`);
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");