@ouidesigner/toubani 0.0.9 → 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.
package/README.md CHANGED
@@ -1,19 +1,24 @@
1
1
  # Toubani (tourterelle)
2
2
 
3
- A small TypeScript 2D game helper built on top of the [ouider](https://www.npmjs.com/package/ouider) bridge. It wraps ODOM canvas APIs with a lightweight game loop, asset pipeline, sprite/scene graph utilities, tile rendering, simple physics/collision helpers, camera controls, and Web Audio playback.
3
+ A small TypeScript 2D game helper built on top of the [ouider](https://www.npmjs.com/package/@ouidesigner%2Fouider) bridge. It wraps ODOM canvas APIs with a lightweight game loop, asset pipeline, sprite/scene graph utilities, tile rendering, simple physics/collision helpers, camera controls, and Web Audio playback.
4
4
 
5
5
  ## Features
6
6
  - Simple game loop with lifecycle hooks (`preload`, `setup`, `update`, `draw`)
7
- - Asset manager for images, sprite sheets, and decoded audio buffers
7
+ - Asset manager for images, sprite sheets, and `OUID.media.audio` decoded sounds
8
8
  - Sprite sheets and animations, plus a scene graph for transforms/parallax
9
9
  - Tile map rendering with camera-aware culling
10
10
  - Basic input handling (keyboard and mouse pressed/held state)
11
11
  - Minimal physics for AABB bodies on a tilemap and tag-based collision rules
12
12
  - Camera helper for pan/zoom/anchor transforms
13
- - Web Audio playback via ouider (no direct DOM audio)
13
+ - Web Audio playback through `OUID.media.audio` from `@ouidesigner/ouider-device`
14
14
 
15
15
  ## Installation
16
16
  ```bash
17
+ npm install @ouidesigner/toubani @ouidesigner/ouider @ouidesigner/ouider-device @ouidesigner/ouider-network
18
+ ```
19
+
20
+ For local package development:
21
+ ```bash
17
22
  npm install
18
23
  npm run build # bundles to dist/ via tsup
19
24
  ```
@@ -23,8 +28,8 @@ Toubani expects to run inside an ouider environment. Your app should already ini
23
28
  ## Quick start
24
29
  Create a canvas (provided by ouider) and hand it to `tulon`:
25
30
  ```ts
26
- import { tulon, GameContext } from "toubani";
27
- import { ODOM } from "ouider";
31
+ import { tulon, GameContext } from "@ouidesigner/toubani";
32
+ import { ODOM } from "@ouidesigner/ouider";
28
33
 
29
34
  const canvas = new ODOM.CanvasElement(/* ouider element */);
30
35
 
@@ -59,11 +64,37 @@ game.start();
59
64
 
60
65
  ## Library modules
61
66
  - **Game/core**: `Game`, `tulon`, and the `GameConfig/GameContext` contracts in `src/game.ts` and `src/core.ts`.
62
- - **Assets/audio**: `AssetManager` loads images/sounds and builds sprite sheets; `AudioManager` decodes and plays sounds through Web Audio via ouider.
67
+ - **Assets/audio**: `AssetManager` loads images/sounds and builds sprite sheets; `AudioManager` delegates decoded playback to `OUID.media.audio`.
63
68
  - **Sprites/scene**: `SpriteSheet`, `SpriteAnimation`, `Sprite`, `SpriteLayer`, and a small scene graph (`Scene`, `SceneNode`, `SpriteNode`) for nested transforms.
64
69
  - **Tiles/camera**: `TileRenderer` and helpers for tile maps/tilesets, plus `Camera2D` for pan/zoom/rotation with configurable focus anchors.
65
70
  - **Physics/collision**: `PhysicsBody` for AABB tile collisions with gravity/friction and `CollisionSystem` for tag-based collider handlers.
66
- - **Input**: `InputManager` tracks key and mouse pressed/held state per frame.
71
+ - **Input/network**: `InputManager` tracks key and mouse pressed/held state per frame; `WebSocketTransport` uses `@ouidesigner/ouider-network` under the hood.
72
+
73
+ ## Network reconnects
74
+ `WebSocketTransport` reconnects by default after unexpected close/connect failures and retries immediately when the app returns from background. Manual `close()` stops reconnecting.
75
+
76
+ Use `NetworkClient.onOpen()` for session setup that must run after both initial connect and reconnect:
77
+ ```ts
78
+ const socket = new NetworkClient(new WebSocketTransport({
79
+ reconnect: {
80
+ initialDelayMs: 500,
81
+ maxDelayMs: 10000,
82
+ maxAttempts: Infinity,
83
+ },
84
+ }));
85
+
86
+ socket.onOpen(() => {
87
+ void socket.send("joinRoom", { roomId });
88
+ });
89
+
90
+ socket.onReconnect(({ attempt, reason }) => {
91
+ console.log("reconnecting", attempt, reason);
92
+ });
93
+
94
+ await socket.connect("wss://example.com/game");
95
+ ```
96
+
97
+ Disable reconnecting with `new WebSocketTransport({ reconnect: false })`. Pending RPC calls are rejected on disconnect; reconnect handlers should request fresh state from the server instead of replaying stale gameplay messages.
67
98
 
68
99
  ## Examples
69
100
  See `examples/simple` for runnable demos:
@@ -77,5 +108,5 @@ Each example has its own `package.json`/webpack config; install and run from `ex
77
108
  - TypeScript config: `tsconfig.json` targets CommonJS output for the bundled library.
78
109
 
79
110
  ## Notes
80
- - Audio uses Web Audio via ouider’s `AudioContext` bridge; sounds are fetched through `OUID.fetch`, decoded once, and played from buffers (no DOM audio elements).
111
+ - Audio uses `OUID.media.audio`, so apps should install `@ouidesigner/ouider-device` and declare the `media` manifest permission when required by the host.
81
112
  - Assets cache image sizes on load to support sprite and tile calculations.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { ODOM } from '@ouidesigner/ouider';
2
+ import { WebAudioPlayOptions } from '@ouidesigner/ouider-device';
3
+ import { WebSocketOptions } from '@ouidesigner/ouider-network';
2
4
 
3
5
  interface SpriteSheetConfig {
4
6
  image: string;
@@ -81,23 +83,14 @@ declare class AssetManager implements AssetRegistry {
81
83
  getImage(key: string): ODOM.Image | undefined;
82
84
  getSpriteSheet(key: string): SpriteSheet | undefined;
83
85
  createAnimation(sheetKey: string, fps?: number, loop?: boolean): SpriteAnimation;
84
- getSound(key: string): ODOM.OObject | undefined;
86
+ getSound(key: string): string | undefined;
85
87
  clean(): void;
86
88
  }
87
89
 
88
- type VolumeOptions = {
89
- loop?: boolean;
90
- volume?: number;
91
- };
90
+ type VolumeOptions = WebAudioPlayOptions;
92
91
  declare class AudioManager {
93
92
  private assets;
94
- private audioContext;
95
- private audioDestination;
96
- private decodedBuffers;
97
93
  constructor(assets: AssetManager);
98
- private getContext;
99
- private getDestination;
100
- private getBuffer;
101
94
  clean(): void;
102
95
  play(key: string, options?: VolumeOptions): Promise<void>;
103
96
  }
@@ -357,18 +350,59 @@ interface NetworkTransport {
357
350
  onOpen(cb: () => void): void;
358
351
  onClose(cb: (ev?: any) => void): void;
359
352
  onError(cb: (err: any) => void): void;
353
+ onReconnect?(cb: (event: WebSocketReconnectEvent) => void): void;
354
+ offOpen?(cb: () => void): void;
355
+ offClose?(cb: (ev?: any) => void): void;
356
+ offError?(cb: (err: any) => void): void;
357
+ offReconnect?(cb: (event: WebSocketReconnectEvent) => void): void;
358
+ }
359
+ interface WebSocketTransportOptions extends WebSocketOptions {
360
+ protocols?: string | string[];
361
+ connectTimeoutMs?: number;
362
+ reconnect?: boolean | WebSocketReconnectOptions;
363
+ }
364
+ interface WebSocketReconnectOptions {
365
+ enabled?: boolean;
366
+ initialDelayMs?: number;
367
+ maxDelayMs?: number;
368
+ factor?: number;
369
+ maxAttempts?: number;
370
+ jitterRatio?: number;
371
+ reconnectOnForeground?: boolean;
372
+ shouldReconnect?: (event?: any) => boolean;
373
+ }
374
+ interface WebSocketReconnectEvent {
375
+ attempt: number;
376
+ url: string;
377
+ reason: "close" | "error" | "foreground";
378
+ cause?: any;
360
379
  }
361
380
  declare class WebSocketTransport implements NetworkTransport {
362
- private static readonly OPEN;
363
- private static readonly CONNECTING;
381
+ private options;
364
382
  private opened;
365
383
  private socket;
384
+ private currentUrl;
385
+ private desiredUrl;
386
+ private offMessage;
366
387
  private msgHandlers;
367
388
  private openHandlers;
368
389
  private closeHandlers;
369
390
  private errorHandlers;
370
- private ids;
391
+ private reconnectHandlers;
392
+ private reconnectAttempts;
393
+ private reconnectTimerId;
394
+ private reconnectTimerPending;
395
+ private reconnectTimerToken;
396
+ private reconnecting;
397
+ private manuallyClosed;
398
+ private appBackgrounded;
399
+ private offForeground;
400
+ private offBackground;
401
+ private foregroundListenerPending;
402
+ private backgroundListenerPending;
403
+ constructor(options?: WebSocketTransportOptions);
371
404
  connect(url: string): Promise<void>;
405
+ private openSocket;
372
406
  send(data: string): Promise<void>;
373
407
  close(code?: number, reason?: string): Promise<void>;
374
408
  isConnected(): boolean;
@@ -379,6 +413,17 @@ declare class WebSocketTransport implements NetworkTransport {
379
413
  offClose(cb: (ev?: any) => void): void;
380
414
  onError(cb: (err: any) => void): void;
381
415
  offError(cb: (err: any) => void): void;
416
+ onReconnect(cb: (event: WebSocketReconnectEvent) => void): void;
417
+ offReconnect(cb: (event: WebSocketReconnectEvent) => void): void;
418
+ private getReconnectOptions;
419
+ private scheduleReconnect;
420
+ private reconnectNow;
421
+ private reconnectDelay;
422
+ private clearReconnectTimer;
423
+ private ensureAppStateListeners;
424
+ private ensureBackgroundListener;
425
+ private ensureForegroundListener;
426
+ private removeAppStateListeners;
382
427
  private cleanupSocket;
383
428
  }
384
429
  type Handler<T = any> = (msg: T) => void;
@@ -390,12 +435,23 @@ declare class NetworkClient {
390
435
  constructor(transport: NetworkTransport);
391
436
  connect(url: string): Promise<void>;
392
437
  isConnected(): boolean;
393
- send<TType extends string, TPayload>(type: TType, payload: TPayload): void;
438
+ onOpen(handler: () => void): void;
439
+ offOpen(handler: () => void): void;
440
+ onClose(handler: (event?: any) => void): void;
441
+ offClose(handler: (event?: any) => void): void;
442
+ onError(handler: (error: any) => void): void;
443
+ offError(handler: (error: any) => void): void;
444
+ onReconnect(handler: (event: WebSocketReconnectEvent) => void): void;
445
+ offReconnect(handler: (event: WebSocketReconnectEvent) => void): void;
446
+ send<TType extends string, TPayload>(type: TType, payload: TPayload): Promise<void>;
394
447
  on<TPayload = any>(type: string, handler: Handler<NetMessage<string, TPayload>>): void;
395
448
  off(type: string, handler: Handler): void;
396
449
  call<TReq = any, TRes = any>(type: string, payload: TReq): Promise<TRes>;
397
450
  private handleRawMessage;
398
451
  close(): Promise<void>;
452
+ private extractRpcId;
453
+ private extractRpcValue;
454
+ private rejectPendingRpc;
399
455
  }
400
456
  interface StatePatch<TState> {
401
457
  full?: TState;
@@ -409,11 +465,11 @@ declare class StateSyncClient<TState extends object> {
409
465
  constructor(net: NetworkClient, initialState: TState, channels?: {
410
466
  update: string;
411
467
  patch: string;
412
- });
468
+ } | undefined);
413
469
  getState(): TState;
414
470
  onChange(handler: (state: TState) => void): void;
415
471
  private emitChange;
416
- sendPatch(patch: Partial<TState>): void;
472
+ sendPatch(patch: Partial<TState>): Promise<void>;
417
473
  }
418
474
 
419
- export { type AABB, AssetManager, type AssetRegistry, AudioManager, Camera2D, type Collider, type CollisionHandler, CollisionSystem, Game, type GameConfig, type GameContext, InputManager, type NetMessage, NetworkClient, type NetworkTransport, PhysicsBody, type PhysicsBodySettings, Scene, SceneLayer, SceneNode, Sprite, SpriteAnimation, SpriteLayer, SpriteNode, type SpriteOptions, SpriteSheet, type SpriteSheetConfig, type StatePatch, StateSyncClient, type TileMap, TileRenderer, type TileRendererOptions, type TileSet, WebSocketTransport, aabbOverlap, createSpritePhysicsCollider, getTileSourceRect, objectSize, tulon, worldToTileX, worldToTileY };
475
+ export { type AABB, AssetManager, type AssetRegistry, AudioManager, Camera2D, type Collider, type CollisionHandler, CollisionSystem, Game, type GameConfig, type GameContext, InputManager, type NetMessage, NetworkClient, type NetworkTransport, PhysicsBody, type PhysicsBodySettings, Scene, SceneLayer, SceneNode, Sprite, SpriteAnimation, SpriteLayer, SpriteNode, type SpriteOptions, SpriteSheet, type SpriteSheetConfig, type StatePatch, StateSyncClient, type TileMap, TileRenderer, type TileRendererOptions, type TileSet, type WebSocketReconnectEvent, type WebSocketReconnectOptions, WebSocketTransport, type WebSocketTransportOptions, aabbOverlap, createSpritePhysicsCollider, getTileSourceRect, objectSize, tulon, worldToTileX, worldToTileY };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { ODOM } from '@ouidesigner/ouider';
2
+ import { WebAudioPlayOptions } from '@ouidesigner/ouider-device';
3
+ import { WebSocketOptions } from '@ouidesigner/ouider-network';
2
4
 
3
5
  interface SpriteSheetConfig {
4
6
  image: string;
@@ -81,23 +83,14 @@ declare class AssetManager implements AssetRegistry {
81
83
  getImage(key: string): ODOM.Image | undefined;
82
84
  getSpriteSheet(key: string): SpriteSheet | undefined;
83
85
  createAnimation(sheetKey: string, fps?: number, loop?: boolean): SpriteAnimation;
84
- getSound(key: string): ODOM.OObject | undefined;
86
+ getSound(key: string): string | undefined;
85
87
  clean(): void;
86
88
  }
87
89
 
88
- type VolumeOptions = {
89
- loop?: boolean;
90
- volume?: number;
91
- };
90
+ type VolumeOptions = WebAudioPlayOptions;
92
91
  declare class AudioManager {
93
92
  private assets;
94
- private audioContext;
95
- private audioDestination;
96
- private decodedBuffers;
97
93
  constructor(assets: AssetManager);
98
- private getContext;
99
- private getDestination;
100
- private getBuffer;
101
94
  clean(): void;
102
95
  play(key: string, options?: VolumeOptions): Promise<void>;
103
96
  }
@@ -357,18 +350,59 @@ interface NetworkTransport {
357
350
  onOpen(cb: () => void): void;
358
351
  onClose(cb: (ev?: any) => void): void;
359
352
  onError(cb: (err: any) => void): void;
353
+ onReconnect?(cb: (event: WebSocketReconnectEvent) => void): void;
354
+ offOpen?(cb: () => void): void;
355
+ offClose?(cb: (ev?: any) => void): void;
356
+ offError?(cb: (err: any) => void): void;
357
+ offReconnect?(cb: (event: WebSocketReconnectEvent) => void): void;
358
+ }
359
+ interface WebSocketTransportOptions extends WebSocketOptions {
360
+ protocols?: string | string[];
361
+ connectTimeoutMs?: number;
362
+ reconnect?: boolean | WebSocketReconnectOptions;
363
+ }
364
+ interface WebSocketReconnectOptions {
365
+ enabled?: boolean;
366
+ initialDelayMs?: number;
367
+ maxDelayMs?: number;
368
+ factor?: number;
369
+ maxAttempts?: number;
370
+ jitterRatio?: number;
371
+ reconnectOnForeground?: boolean;
372
+ shouldReconnect?: (event?: any) => boolean;
373
+ }
374
+ interface WebSocketReconnectEvent {
375
+ attempt: number;
376
+ url: string;
377
+ reason: "close" | "error" | "foreground";
378
+ cause?: any;
360
379
  }
361
380
  declare class WebSocketTransport implements NetworkTransport {
362
- private static readonly OPEN;
363
- private static readonly CONNECTING;
381
+ private options;
364
382
  private opened;
365
383
  private socket;
384
+ private currentUrl;
385
+ private desiredUrl;
386
+ private offMessage;
366
387
  private msgHandlers;
367
388
  private openHandlers;
368
389
  private closeHandlers;
369
390
  private errorHandlers;
370
- private ids;
391
+ private reconnectHandlers;
392
+ private reconnectAttempts;
393
+ private reconnectTimerId;
394
+ private reconnectTimerPending;
395
+ private reconnectTimerToken;
396
+ private reconnecting;
397
+ private manuallyClosed;
398
+ private appBackgrounded;
399
+ private offForeground;
400
+ private offBackground;
401
+ private foregroundListenerPending;
402
+ private backgroundListenerPending;
403
+ constructor(options?: WebSocketTransportOptions);
371
404
  connect(url: string): Promise<void>;
405
+ private openSocket;
372
406
  send(data: string): Promise<void>;
373
407
  close(code?: number, reason?: string): Promise<void>;
374
408
  isConnected(): boolean;
@@ -379,6 +413,17 @@ declare class WebSocketTransport implements NetworkTransport {
379
413
  offClose(cb: (ev?: any) => void): void;
380
414
  onError(cb: (err: any) => void): void;
381
415
  offError(cb: (err: any) => void): void;
416
+ onReconnect(cb: (event: WebSocketReconnectEvent) => void): void;
417
+ offReconnect(cb: (event: WebSocketReconnectEvent) => void): void;
418
+ private getReconnectOptions;
419
+ private scheduleReconnect;
420
+ private reconnectNow;
421
+ private reconnectDelay;
422
+ private clearReconnectTimer;
423
+ private ensureAppStateListeners;
424
+ private ensureBackgroundListener;
425
+ private ensureForegroundListener;
426
+ private removeAppStateListeners;
382
427
  private cleanupSocket;
383
428
  }
384
429
  type Handler<T = any> = (msg: T) => void;
@@ -390,12 +435,23 @@ declare class NetworkClient {
390
435
  constructor(transport: NetworkTransport);
391
436
  connect(url: string): Promise<void>;
392
437
  isConnected(): boolean;
393
- send<TType extends string, TPayload>(type: TType, payload: TPayload): void;
438
+ onOpen(handler: () => void): void;
439
+ offOpen(handler: () => void): void;
440
+ onClose(handler: (event?: any) => void): void;
441
+ offClose(handler: (event?: any) => void): void;
442
+ onError(handler: (error: any) => void): void;
443
+ offError(handler: (error: any) => void): void;
444
+ onReconnect(handler: (event: WebSocketReconnectEvent) => void): void;
445
+ offReconnect(handler: (event: WebSocketReconnectEvent) => void): void;
446
+ send<TType extends string, TPayload>(type: TType, payload: TPayload): Promise<void>;
394
447
  on<TPayload = any>(type: string, handler: Handler<NetMessage<string, TPayload>>): void;
395
448
  off(type: string, handler: Handler): void;
396
449
  call<TReq = any, TRes = any>(type: string, payload: TReq): Promise<TRes>;
397
450
  private handleRawMessage;
398
451
  close(): Promise<void>;
452
+ private extractRpcId;
453
+ private extractRpcValue;
454
+ private rejectPendingRpc;
399
455
  }
400
456
  interface StatePatch<TState> {
401
457
  full?: TState;
@@ -409,11 +465,11 @@ declare class StateSyncClient<TState extends object> {
409
465
  constructor(net: NetworkClient, initialState: TState, channels?: {
410
466
  update: string;
411
467
  patch: string;
412
- });
468
+ } | undefined);
413
469
  getState(): TState;
414
470
  onChange(handler: (state: TState) => void): void;
415
471
  private emitChange;
416
- sendPatch(patch: Partial<TState>): void;
472
+ sendPatch(patch: Partial<TState>): Promise<void>;
417
473
  }
418
474
 
419
- export { type AABB, AssetManager, type AssetRegistry, AudioManager, Camera2D, type Collider, type CollisionHandler, CollisionSystem, Game, type GameConfig, type GameContext, InputManager, type NetMessage, NetworkClient, type NetworkTransport, PhysicsBody, type PhysicsBodySettings, Scene, SceneLayer, SceneNode, Sprite, SpriteAnimation, SpriteLayer, SpriteNode, type SpriteOptions, SpriteSheet, type SpriteSheetConfig, type StatePatch, StateSyncClient, type TileMap, TileRenderer, type TileRendererOptions, type TileSet, WebSocketTransport, aabbOverlap, createSpritePhysicsCollider, getTileSourceRect, objectSize, tulon, worldToTileX, worldToTileY };
475
+ export { type AABB, AssetManager, type AssetRegistry, AudioManager, Camera2D, type Collider, type CollisionHandler, CollisionSystem, Game, type GameConfig, type GameContext, InputManager, type NetMessage, NetworkClient, type NetworkTransport, PhysicsBody, type PhysicsBodySettings, Scene, SceneLayer, SceneNode, Sprite, SpriteAnimation, SpriteLayer, SpriteNode, type SpriteOptions, SpriteSheet, type SpriteSheetConfig, type StatePatch, StateSyncClient, type TileMap, TileRenderer, type TileRendererOptions, type TileSet, type WebSocketReconnectEvent, type WebSocketReconnectOptions, WebSocketTransport, type WebSocketTransportOptions, aabbOverlap, createSpritePhysicsCollider, getTileSourceRect, objectSize, tulon, worldToTileX, worldToTileY };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- 'use strict';var ouider=require('@ouidesigner/ouider');var et=Object.defineProperty,bt=Object.defineProperties;var Dt=Object.getOwnPropertyDescriptors;var tt=Object.getOwnPropertySymbols;var Ct=Object.prototype.hasOwnProperty,Et=Object.prototype.propertyIsEnumerable;var Y=(d,t,e)=>t in d?et(d,t,{enumerable:true,configurable:true,writable:true,value:e}):d[t]=e,st=(d,t)=>{for(var e in t||(t={}))Ct.call(t,e)&&Y(d,e,t[e]);if(tt)for(var e of tt(t))Et.call(t,e)&&Y(d,e,t[e]);return d},it=(d,t)=>bt(d,Dt(t)),c=(d,t)=>et(d,"name",{value:t,configurable:true});var i=(d,t,e)=>Y(d,typeof t!="symbol"?t+"":t,e);var z=class z{constructor(t,e,s,o,n=0){i(this,"image");i(this,"frameWidth");i(this,"frameHeight");i(this,"frameCount");i(this,"from");i(this,"columns");this.image=t,this.frameWidth=e,this.frameHeight=s,this.frameCount=o,this.from=n;let r=I(t);this.columns=Math.max(1,Math.floor(r.width/e));}};c(z,"SpriteSheet");var E=z,S=class S{constructor(t,e=10,s=true){i(this,"sheet");i(this,"fps");i(this,"loop");i(this,"time",0);this.sheet=t,this.fps=e,this.loop=s;}update(t){this.time+=t;let e=this.sheet.frameCount/this.fps;this.loop?this.time=this.time%e:this.time>e&&(this.time=e);}draw(t,e,s,o=1,n=1){let r=Math.floor(this.time*this.fps)+this.sheet.from,a=Math.min(r,this.sheet.frameCount+this.sheet.from-1),h=this.sheet.columns,l=a%h*this.sheet.frameWidth,p=Math.floor(a/h)*this.sheet.frameHeight,u=this.sheet.frameWidth*o,f=this.sheet.frameHeight*n;t.drawImage(this.sheet.image,l,p,this.sheet.frameWidth,this.sheet.frameHeight,e,s,u,f);}reset(){this.time=0;}};c(S,"SpriteAnimation");var O=S,L=class L{constructor(t){i(this,"animation");i(this,"width");i(this,"height");i(this,"visible");i(this,"tag");i(this,"data");var e,s,o,n;this.animation=t.animation,this.visible=(e=t.visible)!=null?e:true,this.tag=t.tag,this.data=(s=t.data)!=null?s:{},this.width=(o=t.width)!=null?o:this.animation.sheet.frameWidth,this.height=(n=t.height)!=null?n:this.animation.sheet.frameHeight;}update(t){this.animation.update(t);}draw(t,e=1,s=1){this.visible&&this.animation.draw(t,0,0,e,s);}};c(L,"Sprite");var ot=L,W=class W{constructor(){i(this,"sprites",[]);}add(t){return this.sprites.push(t),t}remove(t){let e=this.sprites.indexOf(t);e>=0&&this.sprites.splice(e,1);}clear(){this.sprites.length=0;}update(t){for(let e of this.sprites)e.update(t);}draw(t){for(let e of this.sprites)e.draw(t);}withTag(t){return this.sprites.filter(e=>e.tag===t)}};c(W,"SpriteLayer");var nt=W;var B=class B{constructor(){i(this,"imageDefs",new Map);i(this,"soundDefs",new Map);i(this,"sheetDefs",new Map);i(this,"images",new Map);i(this,"sounds",new Map);i(this,"sheets",new Map);}image(t,e){this.imageDefs.set(t,e);}sound(t,e){this.soundDefs.set(t,e);}spriteSheet(t,e){this.sheetDefs.set(t,e);}async loadAll(){var s,o;let t=[],e=[];for(let[n,r]of this.imageDefs.entries())t.push(this.loadImage(r).then(a=>{this.images.set(n,a);}));for(let[n,r]of this.soundDefs.entries())e.push(this.loadSound(r).then(a=>{this.sounds.set(n,a);}));await Promise.all([...t,...e]);for(let[n,r]of this.sheetDefs.entries()){let a=(s=this.getImage(r.image))!=null?s:this.images.get(r.image);if(!a)throw new Error(`SpriteSheet "${n}" missing image "${r.image}"`);let h=new E(a,r.frameWidth,r.frameHeight,r.frameCount,(o=r.from)!=null?o:0);this.sheets.set(n,h);}}async loadImage(t){let e=await ouider.ODOM.Image.new();if(!e)throw new Error(`Unable to create image for "${t}"`);let s=null,o=null,n=false,r=c(async()=>{let h=[s,o].filter(Boolean);s=null,o=null,await Promise.all(h.map(l=>e.dettachEventListener(l).catch(()=>{})));},"detach"),a=c(async()=>{let[h,l,p,u]=await Promise.all([e.naturalWidth(),e.naturalHeight(),e.width(),e.height()]);e.__local_size__={width:h||p||0,height:l||u||0};},"cacheSize");return new Promise(async(h,l)=>{let p=c(async()=>{if(!n)try{await a(),n=!0,await r(),h(e);}catch(f){await u(f);}},"done"),u=c(async f=>{n||(n=true,await r(),await e.release().catch(()=>{}),l(new Error(`Unable to load image "${t}"`)));},"fail");try{s=await e.addEventListener("load",()=>{p();},{options:{once:!0}}),o=await e.addEventListener("error",()=>{u();},{options:{once:!0}}),await e.setSrc(t);let[f,m]=await Promise.all([e.getProperty("complete").catch(()=>!1),e.naturalWidth().catch(()=>0)]);f&&m>0&&await p();}catch(f){await u(f);}})}async loadSound(t){return await OUID.fetch(t,{method:"GET",headers:{},body:null,credentials:"omit"},"buffer")}getImage(t){return this.images.get(t)}getSpriteSheet(t){return this.sheets.get(t)}createAnimation(t,e,s){let o=this.getSpriteSheet(t);if(!o)throw new Error(`SpriteSheet "${t}" not found`);return new O(o,e!=null?e:10,s!=null?s:true)}getSound(t){return this.sounds.get(t)}clean(){this.images.forEach(t=>t.release()),this.sounds.forEach(t=>t.release()),this.images.clear(),this.sounds.clear(),this.sheets.clear();}};c(B,"AssetManager");var H=B;var A=class A{constructor(t){i(this,"assets");i(this,"audioContext",null);i(this,"audioDestination",null);i(this,"decodedBuffers",new Map);this.assets=t;}async getContext(){if(this.audioContext)return this.audioContext;let t=await OUID.newObject("AudioContext");return t?(this.audioContext=ouider.ODOM.OObject.toInstance(t),this.audioContext):null}async getDestination(t){if(this.audioDestination)return this.audioDestination;let e=await t.getPropertyAndHold("destination");return e?(this.audioDestination=e,this.audioDestination):null}async getBuffer(t){if(this.decodedBuffers.has(t))return this.decodedBuffers.get(t);let e=this.assets.getSound(t);if(!e)return null;let s=await this.getContext();if(!s)return null;let o=await s.invokeAndHold("decodeAudioData",e.ref);return o&&this.decodedBuffers.set(t,o),o}clean(){this.decodedBuffers.forEach(t=>t.release()),this.decodedBuffers.clear(),this.audioDestination&&(this.audioDestination.release(),this.audioDestination=null),this.audioContext&&(this.audioContext.invoke("close"),this.audioContext.release(),this.audioContext=null);}async play(t,e){let s=await this.getContext();if(!s)return;let o=await this.getBuffer(t);if(!o)return;await s.invoke("resume");let n=await s.invokeAndHold("createBufferSource");if(!n)return;await n.setProperty("buffer",o.ref),(e==null?void 0:e.loop)!=null&&await n.setProperty("loop",e.loop);let r=await this.getDestination(s);if(!r){n.release();return}if((e==null?void 0:e.volume)!=null){let a=await s.invokeAndHold("createGain");if(a){let h=await a.getPropertyAndHold("gain");h&&(await h.setProperty("value",e.volume),h.release()),await n.invoke("connect",a.ref),await a.invoke("connect",r.ref),await n.invoke("start",0),a.release(),n.release();return}}await n.invoke("connect",r.ref),await n.invoke("start",0),n.release();}};c(A,"AudioManager");var X=A;var R=class R{constructor(t){i(this,"keysDown",new Set);i(this,"keysPressed",new Set);i(this,"buttonsPressed",new Set);i(this,"buttonsDown",new Set);i(this,"mouseX",-1);i(this,"mouseY",-1);i(this,"offsetX",-1);i(this,"offsetY",-1);i(this,"ids",[]);i(this,"disposed",false);this.track(OUID.addEventListener("window","keydown",e=>{this.keysDown.has(e.code)||this.keysPressed.add(e.code),this.keysDown.add(e.code);})),this.track(OUID.addEventListener("window","keyup",e=>{this.keysDown.delete(e.code);})),this.track(t.addEventListener("mousedown",e=>{let s=e.button===0?"MouseLeft":"MouseRight";this.buttonsDown.has(s)||this.buttonsPressed.add(s),this.buttonsDown.add(s),this.updateMousePosition(e);})),this.track(t.addEventListener("mouseup",e=>{let s=e.button===0?"MouseLeft":"MouseRight";this.buttonsDown.delete(s),this.updateMousePosition(e);})),this.track(t.addEventListener("mousemove",e=>{this.updateMousePosition(e);})),this.track(t.addEventListener("mouseleave",()=>{this.buttonsDown.clear();}));}update(){this.keysPressed.clear(),this.buttonsPressed.clear();}isDown(t){return this.keysDown.has(t)||this.buttonsDown.has(t)}isPressed(t){return this.keysPressed.has(t)||this.buttonsPressed.has(t)}clean(){this.disposed=true,this.ids.forEach(t=>{OUID.dettachEventListener(t);}),this.ids=[],this.keysDown.clear(),this.keysPressed.clear(),this.buttonsDown.clear(),this.buttonsPressed.clear();}track(t){t.then(e=>{this.disposed?OUID.dettachEventListener(e):this.ids.push(e);}).catch(()=>{});}updateMousePosition(t){var e,s,o,n;this.mouseX=(e=t.x)!=null?e:this.mouseX,this.mouseY=(s=t.y)!=null?s:this.mouseY,this.offsetX=(o=t.offsetX)!=null?o:this.offsetX,this.offsetY=(n=t.offsetY)!=null?n:this.offsetY;}};c(R,"InputManager");var T=R;var $=class ${constructor(t){i(this,"config");i(this,"canvas");i(this,"ctx2d");i(this,"assets");i(this,"audio");i(this,"input");i(this,"world",{});i(this,"running",false);i(this,"initialized",false);i(this,"lastTime",0);i(this,"frameId",null);i(this,"resizeListenerId",null);this.config=t,this.canvas=t.canvas,this.assets=new H,this.audio=new X(this.assets),this.input=new T(this.canvas);}buildContext(t){return {dt:t,canvas:this.canvas,ctx2d:this.ctx2d,world:this.world,assets:this.assets,audio:this.audio,input:this.input}}async updateLocalSize(){var s,o;let t=(s=await this.canvas.width())!=null?s:0,e=(o=await this.canvas.height())!=null?o:0;this.canvas.__local_size__=this.ctx2d.__local_size__={width:t,height:e};}async syncSize(){var a,h;let[t,e,s,o]=await Promise.all([this.canvas.getProperty("clientWidth"),this.canvas.getProperty("clientHeight"),this.canvas.width(),this.canvas.height()]),n=(a=t!=null?t:s)!=null?a:0,r=(h=e!=null?e:o)!=null?h:0;await this.canvas.setProperty("width",n),await this.canvas.setProperty("height",r);}async init(){if(this.initialized)return;let t=await this.canvas.getContext("2d");if(!t)throw new Error("Unable to create a context");this.ctx2d=t,this.resizeListenerId=await this.canvas.addEventListener("resize",()=>{this.syncSize().then(()=>this.updateLocalSize());}),this.config.preload&&(this.config.preload(this.assets),await this.assets.loadAll()),await this.syncSize(),await this.updateLocalSize(),this.config.setup&&this.config.setup(this.buildContext(0)),this.initialized=true;}start(){this.running||(this.running=true,this.lastTime=performance.now(),this.scheduleFrame());}stop(){var t;!this.running&&!this.initialized||(this.running=false,this.frameId!=null&&(OUID.cancelAnimationFrame(this.frameId),this.frameId=null),this.resizeListenerId&&(OUID.dettachEventListener(this.resizeListenerId),this.resizeListenerId=null),this.audio.clean(),this.assets.clean(),this.input.clean(),(t=this.ctx2d)==null||t.release(),this.initialized=false);}scheduleFrame(){OUID.requestAnimationFrame(t=>{this.frameId=null,this.tick(t);}).then(t=>{this.running?this.frameId=t:OUID.cancelAnimationFrame(t);});}async tick(t){var o,n,r,a;if(!this.running)return;let s=Math.min((t-this.lastTime)/1e3,.1);this.lastTime=t;try{let h=this.buildContext(s);(n=(o=this.config).update)==null||n.call(o,h),this.input.update(),await((a=(r=this.config).draw)==null?void 0:a.call(r,h)),await this.ctx2d.commit();}catch(h){console.error("[Toubani] game loop error",h);}finally{this.running&&this.scheduleFrame();}}};c($,"Game");var U=$;function Kt(d){return new U(d)}c(Kt,"tulon");function I(d){var t;return (t=d.__local_size__)!=null?t:{width:0,height:0}}c(I,"objectSize");function v(d,t){return Math.floor(d/t.tileWidth)}c(v,"worldToTileX");function M(d,t){return Math.floor(d/t.tileHeight)}c(M,"worldToTileY");function Ht(d,t){let{image:e,tileWidth:s,tileHeight:o,margin:n=0,spacing:r=0}=t,a=I(e),h=Math.max(1,Math.floor((a.width-2*n+r)/(s+r))),l=d-1,p=l%h,u=Math.floor(l/h),f=n+p*(s+r),m=n+u*(o+r);return {sx:f,sy:m,sw:s,sh:o}}c(Ht,"getTileSourceRect");var N=class N{constructor(t){i(this,"tileset");i(this,"sourceRectCache",new Map);this.tileset=t;}draw(t,e,s,o,n){let{tileWidth:r,tileHeight:a}=e,h=o*s.focusX,l=n*s.focusY,p=s.zoom,u=s.x-h/p,f=s.y-l/p,m=s.x+(o-h)/p,w=s.y+(n-l)/p,g=Math.max(0,Math.floor(u/r)),y=Math.min(e.width-1,Math.floor(m/r)),b=Math.max(0,Math.floor(f/a)),mt=Math.min(e.height-1,Math.floor(w/a));for(let D=b;D<=mt;D++)for(let C=g;C<=y;C++){let _=e.getTile(C,D);if(_<=0)continue;let{sx:xt,sy:yt,sw:vt,sh:Mt}=this.getSourceRect(_),kt=C*r,Pt=D*a;t.drawImage(this.tileset.image,xt,yt,vt,Mt,kt,Pt,r,a);}}getSourceRect(t){let e=this.sourceRectCache.get(t);return e||(e=Ht(t,this.tileset),this.sourceRectCache.set(t,e)),e}};c(N,"TileRenderer");var rt=N;var j=class j{constructor(t,e){i(this,"node");i(this,"settings");i(this,"vx",0);i(this,"vy",0);i(this,"onGround",false);this.node=t,this.settings=e;}update(t){var f,m,w,g,y;let e=this.settings,s=(f=e.gravityX)!=null?f:0,o=(m=e.gravityY)!=null?m:1e3,n=(w=e.maxFallSpeed)!=null?w:2e3,r=(g=e.friction)!=null?g:.8,a=(y=e.airFriction)!=null?y:.98;this.vx+=s*t,this.vy+=o*t,this.vy>n&&(this.vy=n);let h=Math.max(1,Math.min(e.tileMap.tileWidth,e.tileMap.tileHeight)*.5),l=Math.max(Math.abs(this.vx*t),Math.abs(this.vy*t)),p=Math.max(1,Math.ceil(l/h)),u=t/p;for(let b=0;b<p;b++)this.moveAndCollideX(u),this.moveAndCollideY(u);this.onGround?this.vx*=r:this.vx*=a;}moveAndCollideX(t){let e=this.settings.tileMap,s=this.settings.width,o=this.settings.height,n=this.node.x+this.vx*t,r=this.node.y,a=Math.sign(this.vx);if(a===0){this.node.x=n;return}let h=n,l=n+s,p=r,u=r+o,f=Math.max(0,M(p,e)),m=Math.min(e.height-1,M(u-1,e));if(a>0){let w=v(l,e);for(let g=f;g<=m;g++)if(this.isSolid(w,g)){l=w*e.tileWidth,h=l-s,this.vx=0;break}}else {let w=v(h,e);for(let g=f;g<=m;g++)if(this.isSolid(w,g)){h=(w+1)*e.tileWidth,l=h+s,this.vx=0;break}}this.node.x=h;}moveAndCollideY(t){let e=this.settings.tileMap,s=this.settings.width,o=this.settings.height,n=this.node.x,r=this.node.y+this.vy*t,a=Math.sign(this.vy);if(this.onGround=false,a===0){this.node.y=r;return}let h=r,l=r+o,p=n,u=n+s,f=Math.max(0,v(p,e)),m=Math.min(e.width-1,v(u-1,e));if(a>0){let w=M(l,e);for(let g=f;g<=m;g++)if(this.isSolid(g,w)){l=w*e.tileHeight,h=l-o,this.vy=0,this.onGround=true;break}}else {let w=M(h,e);for(let g=f;g<=m;g++)if(this.isSolid(g,w)){h=(w+1)*e.tileHeight,l=h+o,this.vy=0;break}}this.node.y=h;}isSolid(t,e){let s=this.settings.tileMap;return t<0||e<0||t>=s.width||e>=s.height?false:s.isSolid(t,e)}};c(j,"PhysicsBody");var at=j;var F=class F{constructor(){i(this,"x",0);i(this,"y",0);i(this,"zoom",1);i(this,"rotation",0);i(this,"followTarget",null);i(this,"followLerp",.1);i(this,"focusX",.5);i(this,"focusY",.5);}follow(t,e=.1){this.followTarget=t,this.followLerp=e;}unfollow(){this.followTarget=null;}update(t){if(!this.followTarget)return;let e=this.followTarget.x,s=this.followTarget.y;this.x+=(e-this.x)*this.followLerp,this.y+=(s-this.y)*this.followLerp;}begin(t,e,s){t.save();let o=e*this.focusX,n=s*this.focusY;t.translate(o,n),this.rotation!==0&&t.rotate(-this.rotation),this.zoom!==1&&t.scale(this.zoom,this.zoom),t.translate(-this.x,-this.y);}end(t){t.restore();}};c(F,"Camera2D");var ht=F;var J=class J{constructor(){i(this,"x",0);i(this,"y",0);i(this,"rotation",0);i(this,"scaleX",1);i(this,"scaleY",1);i(this,"visible",true);i(this,"parent",null);i(this,"children",[]);}add(t){return t.parent=this,this.children.push(t),t}remove(t){let e=this.children.indexOf(t);e>=0&&(this.children.splice(e,1),t.parent=null);}clear(){for(let t of this.children)t.parent=null;this.children.length=0;}update(t){}drawSelf(t){}draw(t){if(this.visible){t.save(),t.translate(this.x,this.y),this.rotation!==0&&t.rotate(this.rotation),(this.scaleX!==1||this.scaleY!==1)&&t.scale(this.scaleX,this.scaleY),this.drawSelf(t);for(let e of this.children)e.draw(t);t.restore();}}updateTree(t){this.update(t);for(let e of this.children)e.updateTree(t);}getWorldPosition(){let t=this.x,e=this.y,s=this.parent;for(;s;)t+=s.x,e+=s.y,s=s.parent;return {x:t,y:e}}};c(J,"SceneNode");var P=J,q=class q extends P{constructor(e){super();i(this,"sprite");this.sprite=e;}update(e){this.sprite.update(e);}drawSelf(e){this.sprite.draw(e);}getWorldBounds(){let e=this.getWorldPosition();return {x:e.x,y:e.y,width:this.sprite.width*this.scaleX,height:this.sprite.height*this.scaleY}}};c(q,"SpriteNode");var ct=q,K=class K extends P{constructor(e=1,s=1){super();i(this,"parallaxX",1);i(this,"parallaxY",1);this.parallaxX=e,this.parallaxY=s;}};c(K,"SceneLayer");var G=K,k=class k{constructor(){i(this,"id");i(this,"root",new P);i(this,"layers",[]);this.id=k.id++;}addLayer(t=1,e=1){let s=new G(t,e);return this.layers.push(s),this.root.add(s),s}update(t){this.root.updateTree(t);}draw(t){this.root.draw(t);}};c(k,"Scene"),i(k,"id",0);var lt=k;function dt(d,t){return !(d.x+d.width<=t.x||d.x>=t.x+t.width||d.y+d.height<=t.y||d.y>=t.y+t.height)}c(dt,"aabbOverlap");var Q=class Q{constructor(){i(this,"colliders",[]);i(this,"rules",[]);i(this,"rulesByPair",new Map);}add(t){this.colliders.push(t);}remove(t){let e=this.colliders.indexOf(t);e>=0&&this.colliders.splice(e,1);}clear(){this.colliders.length=0;}onCollision(t,e,s){var a;let o={tagA:t,tagB:e,handler:s};this.rules.push(o);let n=this.ruleKey(t,e),r=(a=this.rulesByPair.get(n))!=null?a:[];r.push(o),this.rulesByPair.set(n,r);}update(){var o;let t=new Map,e=new Map;for(let n of this.colliders){if(!n.isActive)continue;let r=(o=t.get(n.tag))!=null?o:[];r.push(n),t.set(n.tag,r);}let s=c(n=>{let r=e.get(n);return r||(r=n.getBounds(),e.set(n,r)),r},"getBounds");for(let[n,r]of this.rulesByPair.entries()){let[a,h]=n.split("\0"),l=t.get(a),p=t.get(h);if(!(!l||!p))if(a===h)for(let u=0;u<l.length;u++){let f=l[u],m=s(f);for(let w=u+1;w<l.length;w++){let g=l[w];dt(m,s(g))&&this.triggerRules(f,g,r);}}else for(let u of l){let f=s(u);for(let m of p)dt(f,s(m))&&this.triggerRules(u,m,r);}}}ruleKey(t,e){return t<=e?`${t}\0${e}`:`${e}\0${t}`}triggerRules(t,e,s){for(let o of s){let n=o.tagA===t.tag&&o.tagB===e.tag,r=o.tagA===e.tag&&o.tagB===t.tag;n?o.handler(t,e):r&&o.handler(e,t);}}};c(Q,"CollisionSystem");var ut=Q,Xt=0;function he(d,t,e,s){return {id:`col-${Xt++}`,tag:d,isActive:true,data:{node:t,body:e,sprite:s},getBounds(){let n=t.getWorldPosition(),r=e.settings.width,a=e.settings.height;return {x:n.x,y:n.y,width:r,height:a}}}}c(he,"createSpritePhysicsCollider");var x=class x{constructor(){i(this,"opened",false);i(this,"socket",null);i(this,"msgHandlers",[]);i(this,"openHandlers",[]);i(this,"closeHandlers",[]);i(this,"errorHandlers",[]);i(this,"ids",[]);}async connect(t){if(this.socket){let s=await this.socket.getProperty("readyState");if(s===x.OPEN||s===x.CONNECTING)return;await this.cleanupSocket(false);}let e=await ouider.OUID.newObject("WebSocket",t);if(!e)throw new Error(`Unable to create WebSocket for ${t}`);this.socket=ouider.ODOM.OObject.toInstance(e),this.ids.push(await this.socket.addEventListener("open",()=>{this.opened=true,this.openHandlers.forEach(s=>s());})),this.ids.push(await this.socket.addEventListener("message",s=>{let o=typeof s.data=="string"?s.data:"";this.msgHandlers.forEach(n=>n(o));})),this.ids.push(await this.socket.addEventListener("close",s=>{this.opened=false,this.closeHandlers.forEach(o=>o(s));})),this.ids.push(await this.socket.addEventListener("error",s=>{this.errorHandlers.forEach(o=>o(s));}));try{await new Promise((s,o)=>{let n=c(()=>{h(),s();},"onOpen"),r=c(l=>{h(),o(l);},"onError"),a=c(l=>{h(),o(l);},"onClose"),h=c(()=>{this.offOpen(n),this.offError(r),this.offClose(a);},"cleanup");this.onOpen(n),this.onError(r),this.onClose(a);});}catch(s){throw await this.cleanupSocket(false),s}}async send(t){this.socket&&await this.socket.getProperty("readyState")===x.OPEN&&await this.socket.invoke("send",t);}async close(t,e){await this.cleanupSocket(true,t,e);}isConnected(){return this.opened}onMessage(t){this.msgHandlers.push(t);}onOpen(t){this.openHandlers.push(t);}offOpen(t){this.openHandlers=this.openHandlers.filter(e=>e!==t);}onClose(t){this.closeHandlers.push(t);}offClose(t){this.closeHandlers=this.closeHandlers.filter(e=>e!==t);}onError(t){this.errorHandlers.push(t);}offError(t){this.errorHandlers=this.errorHandlers.filter(e=>e!==t);}async cleanupSocket(t,e,s){let o=this.socket;this.socket=null,this.opened=false;let n=this.ids.splice(0);if(await Promise.all(n.map(r=>ouider.OUID.dettachEventListener(r).catch(()=>{}))),!!o)try{if(t){let r=await o.getProperty("readyState").catch(()=>null);(r===x.OPEN||r===x.CONNECTING)&&await o.invoke("close",e,s);}}finally{await o.release().catch(()=>{});}}};c(x,"WebSocketTransport"),i(x,"OPEN",1),i(x,"CONNECTING",0);var pt=x,V=class V{constructor(t){i(this,"transport");i(this,"handlers",new Map);i(this,"rpcResolvers",new Map);i(this,"rpcCounter",0);i(this,"handleRawMessage",c(t=>{if(!t)return;let e;try{e=JSON.parse(t);}catch(o){return}if(e.payload&&typeof e.payload=="object"&&"rpcId"in e.payload){let o=e.payload.rpcId,n=this.rpcResolvers.get(o);n&&(n.resolve(e.payload),this.rpcResolvers.delete(o));}let s=this.handlers.get(e.type);if(s)for(let o of s)o(e);},"handleRawMessage"));this.transport=t,this.transport.onMessage(this.handleRawMessage);}async connect(t){await this.transport.connect(t);}isConnected(){return this.transport.isConnected()}send(t,e){let s={type:t,payload:e};this.transport.send(JSON.stringify(s));}on(t,e){var o;let s=(o=this.handlers.get(t))!=null?o:[];s.push(e),this.handlers.set(t,s);}off(t,e){let s=this.handlers.get(t);s&&this.handlers.set(t,s.filter(o=>o!==e));}call(t,e){let s=`rpc_${++this.rpcCounter}_${Date.now()}`,o=e&&typeof e=="object"?it(st({},e),{rpcId:s}):{value:e,rpcId:s},n={type:t,payload:o};return new Promise((r,a)=>{this.rpcResolvers.set(s,{resolve:r,reject:a}),this.transport.send(JSON.stringify(n)).catch(h=>{this.rpcResolvers.delete(s),a(h);});})}close(){return this.rpcResolvers.forEach(({reject:t})=>t(new Error("Network client closed"))),this.rpcResolvers.clear(),this.transport.close()}};c(V,"NetworkClient");var gt=V,Z=class Z{constructor(t,e,s){i(this,"net");i(this,"channels");i(this,"state");i(this,"onChangeHandlers",[]);var o;this.net=t,this.channels=s,this.state=e,this.net.on((o=s==null?void 0:s.update)!=null?o:"state_update",n=>{let{payload:r}=n;r.full?this.state=r.full:r.patch&&(this.state=Object.assign({},this.state,r.patch)),this.emitChange();});}getState(){return this.state}onChange(t){this.onChangeHandlers.push(t);}emitChange(){for(let t of this.onChangeHandlers)t(this.state);}sendPatch(t){var e,s;this.net.send((s=(e=this.channels)==null?void 0:e.patch)!=null?s:"state_patch",t);}};c(Z,"StateSyncClient");var wt=Z;exports.AssetManager=H;exports.AudioManager=X;exports.Camera2D=ht;exports.CollisionSystem=ut;exports.Game=U;exports.InputManager=T;exports.NetworkClient=gt;exports.PhysicsBody=at;exports.Scene=lt;exports.SceneLayer=G;exports.SceneNode=P;exports.Sprite=ot;exports.SpriteAnimation=O;exports.SpriteLayer=nt;exports.SpriteNode=ct;exports.SpriteSheet=E;exports.StateSyncClient=wt;exports.TileRenderer=rt;exports.WebSocketTransport=pt;exports.aabbOverlap=dt;exports.createSpritePhysicsCollider=he;exports.getTileSourceRect=Ht;exports.objectSize=I;exports.tulon=Kt;exports.worldToTileX=v;exports.worldToTileY=M;
1
+ 'use strict';var ouider=require('@ouidesigner/ouider');require('@ouidesigner/ouider-device');var ouiderNetwork=require('@ouidesigner/ouider-network');var st=Object.defineProperty,Rt=Object.defineProperties;var It=Object.getOwnPropertyDescriptors;var et=Object.getOwnPropertySymbols;var Dt=Object.prototype.hasOwnProperty,Ct=Object.prototype.propertyIsEnumerable;var S=(d,t,e)=>t in d?st(d,t,{enumerable:true,configurable:true,writable:true,value:e}):d[t]=e,it=(d,t)=>{for(var e in t||(t={}))Dt.call(t,e)&&S(d,e,t[e]);if(et)for(var e of et(t))Ct.call(t,e)&&S(d,e,t[e]);return d},nt=(d,t)=>Rt(d,It(t)),c=(d,t)=>st(d,"name",{value:t,configurable:true});var i=(d,t,e)=>S(d,typeof t!="symbol"?t+"":t,e);var E=class E{constructor(t,e,s,o,n=0){i(this,"image");i(this,"frameWidth");i(this,"frameHeight");i(this,"frameCount");i(this,"from");i(this,"columns");this.image=t,this.frameWidth=e,this.frameHeight=s,this.frameCount=o,this.from=n;let r=b(t);this.columns=Math.max(1,Math.floor(r.width/e));}};c(E,"SpriteSheet");var D=E,X=class X{constructor(t,e=10,s=true){i(this,"sheet");i(this,"fps");i(this,"loop");i(this,"time",0);this.sheet=t,this.fps=e,this.loop=s;}update(t){this.time+=t;let e=this.sheet.frameCount/this.fps;this.loop?this.time=this.time%e:this.time>e&&(this.time=e);}draw(t,e,s,o=1,n=1){let r=Math.floor(this.time*this.fps)+this.sheet.from,h=Math.min(r,this.sheet.frameCount+this.sheet.from-1),a=this.sheet.columns,l=h%a*this.sheet.frameWidth,p=Math.floor(h/a)*this.sheet.frameHeight,u=this.sheet.frameWidth*o,f=this.sheet.frameHeight*n;t.drawImage(this.sheet.image,l,p,this.sheet.frameWidth,this.sheet.frameHeight,e,s,u,f);}reset(){this.time=0;}};c(X,"SpriteAnimation");var C=X,Y=class Y{constructor(t){i(this,"animation");i(this,"width");i(this,"height");i(this,"visible");i(this,"tag");i(this,"data");var e,s,o,n;this.animation=t.animation,this.visible=(e=t.visible)!=null?e:true,this.tag=t.tag,this.data=(s=t.data)!=null?s:{},this.width=(o=t.width)!=null?o:this.animation.sheet.frameWidth,this.height=(n=t.height)!=null?n:this.animation.sheet.frameHeight;}update(t){this.animation.update(t);}draw(t,e=1,s=1){this.visible&&this.animation.draw(t,0,0,e,s);}};c(Y,"Sprite");var ot=Y,z=class z{constructor(){i(this,"sprites",[]);}add(t){return this.sprites.push(t),t}remove(t){let e=this.sprites.indexOf(t);e>=0&&this.sprites.splice(e,1);}clear(){this.sprites.length=0;}update(t){for(let e of this.sprites)e.update(t);}draw(t){for(let e of this.sprites)e.draw(t);}withTag(t){return this.sprites.filter(e=>e.tag===t)}};c(z,"SpriteLayer");var rt=z;var A=class A{constructor(){i(this,"imageDefs",new Map);i(this,"soundDefs",new Map);i(this,"sheetDefs",new Map);i(this,"images",new Map);i(this,"sounds",new Map);i(this,"sheets",new Map);}image(t,e){this.imageDefs.set(t,e);}sound(t,e){this.soundDefs.set(t,e);}spriteSheet(t,e){this.sheetDefs.set(t,e);}async loadAll(){var s,o;let t=[],e=[];for(let[n,r]of this.imageDefs.entries())t.push(this.loadImage(r).then(h=>{this.images.set(n,h);}));for(let[n,r]of this.soundDefs.entries())e.push(this.loadSound(r).then(h=>{this.sounds.set(n,h);}));await Promise.all([...t,...e]);for(let[n,r]of this.sheetDefs.entries()){let h=(s=this.getImage(r.image))!=null?s:this.images.get(r.image);if(!h)throw new Error(`SpriteSheet "${n}" missing image "${r.image}"`);let a=new D(h,r.frameWidth,r.frameHeight,r.frameCount,(o=r.from)!=null?o:0);this.sheets.set(n,a);}}async loadImage(t){let e=await ouider.ODOM.Image.new();if(!e)throw new Error(`Unable to create image for "${t}"`);let s=null,o=null,n=false,r=c(async()=>{let a=[s,o].filter(Boolean);s=null,o=null,await Promise.all(a.map(l=>e.dettachEventListener(l).catch(()=>{})));},"detach"),h=c(async()=>{let[a,l,p,u]=await Promise.all([e.naturalWidth(),e.naturalHeight(),e.width(),e.height()]);e.__local_size__={width:a||p||0,height:l||u||0};},"cacheSize");return new Promise(async(a,l)=>{let p=c(async()=>{if(!n)try{await h(),n=!0,await r(),a(e);}catch(f){await u(f);}},"done"),u=c(async f=>{n||(n=true,await r(),await e.release().catch(()=>{}),l(new Error(`Unable to load image "${t}"`)));},"fail");try{s=await e.addEventListener("load",()=>{p();},{options:{once:!0}}),o=await e.addEventListener("error",()=>{u();},{options:{once:!0}}),await e.setSrc(t);let[f,w]=await Promise.all([e.getProperty("complete").catch(()=>!1),e.naturalWidth().catch(()=>0)]);f&&w>0&&await p();}catch(f){await u(f);}})}async loadSound(t){return await ouider.OUID.media.audio.decode(t),t}getImage(t){return this.images.get(t)}getSpriteSheet(t){return this.sheets.get(t)}createAnimation(t,e,s){let o=this.getSpriteSheet(t);if(!o)throw new Error(`SpriteSheet "${t}" not found`);return new C(o,e!=null?e:10,s!=null?s:true)}getSound(t){return this.sounds.get(t)}clean(){this.images.forEach(t=>t.release()),this.images.clear(),this.sounds.clear(),this.sheets.clear();}};c(A,"AssetManager");var L=A;var U=class U{constructor(t){i(this,"assets");this.assets=t;}clean(){ouider.OUID.media.audio.close();}async play(t,e){let s=this.assets.getSound(t);s&&await ouider.OUID.media.audio.play(s,e);}};c(U,"AudioManager");var H=U;var W=class W{constructor(t){i(this,"keysDown",new Set);i(this,"keysPressed",new Set);i(this,"buttonsPressed",new Set);i(this,"buttonsDown",new Set);i(this,"mouseX",-1);i(this,"mouseY",-1);i(this,"offsetX",-1);i(this,"offsetY",-1);i(this,"ids",[]);i(this,"disposed",false);this.track(OUID.addEventListener("window","keydown",e=>{this.keysDown.has(e.code)||this.keysPressed.add(e.code),this.keysDown.add(e.code);})),this.track(OUID.addEventListener("window","keyup",e=>{this.keysDown.delete(e.code);})),this.track(t.addEventListener("mousedown",e=>{let s=e.button===0?"MouseLeft":"MouseRight";this.buttonsDown.has(s)||this.buttonsPressed.add(s),this.buttonsDown.add(s),this.updateMousePosition(e);})),this.track(t.addEventListener("mouseup",e=>{let s=e.button===0?"MouseLeft":"MouseRight";this.buttonsDown.delete(s),this.updateMousePosition(e);})),this.track(t.addEventListener("mousemove",e=>{this.updateMousePosition(e);})),this.track(t.addEventListener("mouseleave",()=>{this.buttonsDown.clear();}));}update(){this.keysPressed.clear(),this.buttonsPressed.clear();}isDown(t){return this.keysDown.has(t)||this.buttonsDown.has(t)}isPressed(t){return this.keysPressed.has(t)||this.buttonsPressed.has(t)}clean(){this.disposed=true,this.ids.forEach(t=>{OUID.dettachEventListener(t);}),this.ids=[],this.keysDown.clear(),this.keysPressed.clear(),this.buttonsDown.clear(),this.buttonsPressed.clear();}track(t){t.then(e=>{this.disposed?OUID.dettachEventListener(e):this.ids.push(e);}).catch(()=>{});}updateMousePosition(t){var e,s,o,n;this.mouseX=(e=t.x)!=null?e:this.mouseX,this.mouseY=(s=t.y)!=null?s:this.mouseY,this.offsetX=(o=t.offsetX)!=null?o:this.offsetX,this.offsetY=(n=t.offsetY)!=null?n:this.offsetY;}};c(W,"InputManager");var O=W;var F=class F{constructor(t){i(this,"config");i(this,"canvas");i(this,"ctx2d");i(this,"assets");i(this,"audio");i(this,"input");i(this,"world",{});i(this,"running",false);i(this,"initialized",false);i(this,"lastTime",0);i(this,"frameId",null);i(this,"resizeListenerId",null);this.config=t,this.canvas=t.canvas,this.assets=new L,this.audio=new H(this.assets),this.input=new O(this.canvas);}buildContext(t){return {dt:t,canvas:this.canvas,ctx2d:this.ctx2d,world:this.world,assets:this.assets,audio:this.audio,input:this.input}}async updateLocalSize(){var s,o;let t=(s=await this.canvas.width())!=null?s:0,e=(o=await this.canvas.height())!=null?o:0;this.canvas.__local_size__=this.ctx2d.__local_size__={width:t,height:e};}async syncSize(){var h,a;let[t,e,s,o]=await Promise.all([this.canvas.getProperty("clientWidth"),this.canvas.getProperty("clientHeight"),this.canvas.width(),this.canvas.height()]),n=(h=t!=null?t:s)!=null?h:0,r=(a=e!=null?e:o)!=null?a:0;await this.canvas.setProperty("width",n),await this.canvas.setProperty("height",r);}async init(){if(this.initialized)return;let t=await this.canvas.getContext("2d");if(!t)throw new Error("Unable to create a context");this.ctx2d=t,this.resizeListenerId=await this.canvas.addEventListener("resize",()=>{this.syncSize().then(()=>this.updateLocalSize());}),this.config.preload&&(this.config.preload(this.assets),await this.assets.loadAll()),await this.syncSize(),await this.updateLocalSize(),this.config.setup&&this.config.setup(this.buildContext(0)),this.initialized=true;}start(){this.running||(this.running=true,this.lastTime=performance.now(),this.scheduleFrame());}stop(){var t;!this.running&&!this.initialized||(this.running=false,this.frameId!=null&&(OUID.cancelAnimationFrame(this.frameId),this.frameId=null),this.resizeListenerId&&(OUID.dettachEventListener(this.resizeListenerId),this.resizeListenerId=null),this.audio.clean(),this.assets.clean(),this.input.clean(),(t=this.ctx2d)==null||t.release(),this.initialized=false);}scheduleFrame(){OUID.requestAnimationFrame(t=>{this.frameId=null,this.tick(t);}).then(t=>{this.running?this.frameId=t:OUID.cancelAnimationFrame(t);});}async tick(t){var o,n,r,h;if(!this.running)return;let s=Math.min((t-this.lastTime)/1e3,.1);this.lastTime=t;try{let a=this.buildContext(s);(n=(o=this.config).update)==null||n.call(o,a),this.input.update(),await((h=(r=this.config).draw)==null?void 0:h.call(r,a)),await this.ctx2d.commit();}catch(a){console.error("[Toubani] game loop error",a);}finally{this.running&&this.scheduleFrame();}}};c(F,"Game");var B=F;function Qt(d){return new B(d)}c(Qt,"tulon");function b(d){var t;return (t=d.__local_size__)!=null?t:{width:0,height:0}}c(b,"objectSize");function x(d,t){return Math.floor(d/t.tileWidth)}c(x,"worldToTileX");function v(d,t){return Math.floor(d/t.tileHeight)}c(v,"worldToTileY");function Ht(d,t){let{image:e,tileWidth:s,tileHeight:o,margin:n=0,spacing:r=0}=t,h=b(e),a=Math.max(1,Math.floor((h.width-2*n+r)/(s+r))),l=d-1,p=l%a,u=Math.floor(l/a),f=n+p*(s+r),w=n+u*(o+r);return {sx:f,sy:w,sw:s,sh:o}}c(Ht,"getTileSourceRect");var j=class j{constructor(t){i(this,"tileset");i(this,"sourceRectCache",new Map);this.tileset=t;}draw(t,e,s,o,n){let{tileWidth:r,tileHeight:h}=e,a=o*s.focusX,l=n*s.focusY,p=s.zoom,u=s.x-a/p,f=s.y-l/p,w=s.x+(o-a)/p,m=s.y+(n-l)/p,g=Math.max(0,Math.floor(u/r)),y=Math.min(e.width-1,Math.floor(w/r)),P=Math.max(0,Math.floor(f/h)),yt=Math.min(e.height-1,Math.floor(m/h));for(let R=P;R<=yt;R++)for(let I=g;I<=y;I++){let tt=e.getTile(I,R);if(tt<=0)continue;let{sx:xt,sy:vt,sw:Mt,sh:kt}=this.getSourceRect(tt),Tt=I*r,Pt=R*h;t.drawImage(this.tileset.image,xt,vt,Mt,kt,Tt,Pt,r,h);}}getSourceRect(t){let e=this.sourceRectCache.get(t);return e||(e=Ht(t,this.tileset),this.sourceRectCache.set(t,e)),e}};c(j,"TileRenderer");var ht=j;var $=class ${constructor(t,e){i(this,"node");i(this,"settings");i(this,"vx",0);i(this,"vy",0);i(this,"onGround",false);this.node=t,this.settings=e;}update(t){var f,w,m,g,y;let e=this.settings,s=(f=e.gravityX)!=null?f:0,o=(w=e.gravityY)!=null?w:1e3,n=(m=e.maxFallSpeed)!=null?m:2e3,r=(g=e.friction)!=null?g:.8,h=(y=e.airFriction)!=null?y:.98;this.vx+=s*t,this.vy+=o*t,this.vy>n&&(this.vy=n);let a=Math.max(1,Math.min(e.tileMap.tileWidth,e.tileMap.tileHeight)*.5),l=Math.max(Math.abs(this.vx*t),Math.abs(this.vy*t)),p=Math.max(1,Math.ceil(l/a)),u=t/p;this.onGround=false;for(let P=0;P<p;P++)this.moveAndCollideX(u),this.moveAndCollideY(u);this.onGround?this.vx*=r:this.vx*=h;}moveAndCollideX(t){let e=this.settings.tileMap,s=this.settings.width,o=this.settings.height,n=this.node.x+this.vx*t,r=this.node.y,h=Math.sign(this.vx);if(h===0){this.node.x=n;return}let a=n,l=n+s,p=r,u=r+o,f=Math.max(0,v(p,e)),w=Math.min(e.height-1,v(u-1,e));if(h>0){let m=x(l,e);for(let g=f;g<=w;g++)if(this.isSolid(m,g)){l=m*e.tileWidth,a=l-s,this.vx=0;break}}else {let m=x(a,e);for(let g=f;g<=w;g++)if(this.isSolid(m,g)){a=(m+1)*e.tileWidth,l=a+s,this.vx=0;break}}this.node.x=a;}moveAndCollideY(t){let e=this.settings.tileMap,s=this.settings.width,o=this.settings.height,n=this.node.x,r=this.node.y+this.vy*t,h=Math.sign(this.vy);if(h===0){this.node.y=r;return}let a=r,l=r+o,p=n,u=n+s,f=Math.max(0,x(p,e)),w=Math.min(e.width-1,x(u-1,e));if(h>0){let m=v(l,e);for(let g=f;g<=w;g++)if(this.isSolid(g,m)){l=m*e.tileHeight,a=l-o,this.vy=0,this.onGround=true;break}}else {let m=v(a,e);for(let g=f;g<=w;g++)if(this.isSolid(g,m)){a=(m+1)*e.tileHeight,l=a+o,this.vy=0;break}}this.node.y=a;}isSolid(t,e){let s=this.settings.tileMap;return t<0||e<0||t>=s.width||e>=s.height?false:s.isSolid(t,e)}};c($,"PhysicsBody");var ct=$;var N=class N{constructor(){i(this,"x",0);i(this,"y",0);i(this,"zoom",1);i(this,"rotation",0);i(this,"followTarget",null);i(this,"followLerp",.1);i(this,"focusX",.5);i(this,"focusY",.5);}follow(t,e=.1){this.followTarget=t,this.followLerp=e;}unfollow(){this.followTarget=null;}update(t){if(!this.followTarget)return;let e=this.followTarget.x,s=this.followTarget.y;this.x+=(e-this.x)*this.followLerp,this.y+=(s-this.y)*this.followLerp;}begin(t,e,s){t.save();let o=e*this.focusX,n=s*this.focusY;t.translate(o,n),this.rotation!==0&&t.rotate(-this.rotation),this.zoom!==1&&t.scale(this.zoom,this.zoom),t.translate(-this.x,-this.y);}end(t){t.restore();}};c(N,"Camera2D");var lt=N;var J=class J{constructor(){i(this,"x",0);i(this,"y",0);i(this,"rotation",0);i(this,"scaleX",1);i(this,"scaleY",1);i(this,"visible",true);i(this,"parent",null);i(this,"children",[]);}add(t){return t.parent=this,this.children.push(t),t}remove(t){let e=this.children.indexOf(t);e>=0&&(this.children.splice(e,1),t.parent=null);}clear(){for(let t of this.children)t.parent=null;this.children.length=0;}update(t){}drawSelf(t){}draw(t){if(this.visible){t.save(),t.translate(this.x,this.y),this.rotation!==0&&t.rotate(this.rotation),(this.scaleX!==1||this.scaleY!==1)&&t.scale(this.scaleX,this.scaleY),this.drawSelf(t);for(let e of this.children)e.draw(t);t.restore();}}updateTree(t){this.update(t);for(let e of this.children)e.updateTree(t);}getWorldPosition(){let t=this.x,e=this.y,s=this.parent;for(;s;)t+=s.x,e+=s.y,s=s.parent;return {x:t,y:e}}};c(J,"SceneNode");var k=J,V=class V extends k{constructor(e){super();i(this,"sprite");this.sprite=e;}update(e){this.sprite.update(e);}drawSelf(e){this.sprite.draw(e);}getWorldBounds(){let e=this.getWorldPosition();return {x:e.x,y:e.y,width:this.sprite.width*this.scaleX,height:this.sprite.height*this.scaleY}}};c(V,"SpriteNode");var dt=V,G=class G extends k{constructor(e=1,s=1){super();i(this,"parallaxX",1);i(this,"parallaxY",1);this.parallaxX=e,this.parallaxY=s;}};c(G,"SceneLayer");var _=G,M=class M{constructor(){i(this,"id");i(this,"root",new k);i(this,"layers",[]);this.id=M.id++;}addLayer(t=1,e=1){let s=new _(t,e);return this.layers.push(s),this.root.add(s),s}update(t){this.root.updateTree(t);}draw(t){this.root.draw(t);}};c(M,"Scene"),i(M,"id",0);var ut=M;function ft(d,t){return !(d.x+d.width<=t.x||d.x>=t.x+t.width||d.y+d.height<=t.y||d.y>=t.y+t.height)}c(ft,"aabbOverlap");var q=class q{constructor(){i(this,"colliders",[]);i(this,"rules",[]);i(this,"rulesByPair",new Map);}add(t){this.colliders.push(t);}remove(t){let e=this.colliders.indexOf(t);e>=0&&this.colliders.splice(e,1);}clear(){this.colliders.length=0;}onCollision(t,e,s){var h;let o={tagA:t,tagB:e,handler:s};this.rules.push(o);let n=this.ruleKey(t,e),r=(h=this.rulesByPair.get(n))!=null?h:[];r.push(o),this.rulesByPair.set(n,r);}update(){var o;let t=new Map,e=new Map;for(let n of this.colliders){if(!n.isActive)continue;let r=(o=t.get(n.tag))!=null?o:[];r.push(n),t.set(n.tag,r);}let s=c(n=>{let r=e.get(n);return r||(r=n.getBounds(),e.set(n,r)),r},"getBounds");for(let[n,r]of this.rulesByPair.entries()){let[h,a]=n.split("\0"),l=t.get(h),p=t.get(a);if(!(!l||!p))if(h===a)for(let u=0;u<l.length;u++){let f=l[u],w=s(f);for(let m=u+1;m<l.length;m++){let g=l[m];ft(w,s(g))&&this.triggerRules(f,g,r);}}else for(let u of l){let f=s(u);for(let w of p)ft(f,s(w))&&this.triggerRules(u,w,r);}}}ruleKey(t,e){return t<=e?`${t}\0${e}`:`${e}\0${t}`}triggerRules(t,e,s){for(let o of s){let n=o.tagA===t.tag&&o.tagB===e.tag,r=o.tagA===e.tag&&o.tagB===t.tag;n?o.handler(t,e):r&&o.handler(e,t);}}};c(q,"CollisionSystem");var pt=q,Ot=0;function de(d,t,e,s){return {id:`col-${Ot++}`,tag:d,isActive:true,data:{node:t,body:e,sprite:s},getBounds(){let n=t.getWorldPosition(),r=e.settings.width,h=e.settings.height;return {x:n.x,y:n.y,width:r,height:h}}}}c(de,"createSpritePhysicsCollider");var K=class K{constructor(t={}){i(this,"options");i(this,"opened",false);i(this,"socket",null);i(this,"currentUrl",null);i(this,"desiredUrl",null);i(this,"offMessage",null);i(this,"msgHandlers",[]);i(this,"openHandlers",[]);i(this,"closeHandlers",[]);i(this,"errorHandlers",[]);i(this,"reconnectHandlers",[]);i(this,"reconnectAttempts",0);i(this,"reconnectTimerId",null);i(this,"reconnectTimerPending",false);i(this,"reconnectTimerToken",0);i(this,"reconnecting",null);i(this,"manuallyClosed",true);i(this,"appBackgrounded",false);i(this,"offForeground",null);i(this,"offBackground",null);i(this,"foregroundListenerPending",false);i(this,"backgroundListenerPending",false);this.options=t;}async connect(t){this.desiredUrl=t,this.manuallyClosed=false,this.reconnectAttempts=0,await this.clearReconnectTimer(),this.ensureAppStateListeners(),await this.openSocket(t);}async openSocket(t){if(this.socket){if(this.currentUrl===t&&this.socket.isConnected())return;if(this.currentUrl===t&&!this.opened){let n=this.socket;try{await n.connect();}catch(r){throw this.socket===n&&await this.cleanupSocket(false),this.manuallyClosed||this.scheduleReconnect(r,"error"),r}return}await this.cleanupSocket(true);}let{protocols:e,connectTimeoutMs:s}=this.options,o=ouiderNetwork.OUIDNetwork.websocket(t,e,{connectTimeoutMs:s});this.socket=o,this.currentUrl=t,o.onOpen(()=>{this.socket!==o||this.manuallyClosed||(this.opened=true,this.reconnectAttempts=0,this.openHandlers.forEach(n=>n()));}),this.offMessage=await o.onMessage(n=>{var h;let r=typeof n=="string"?n:(h=JSON.stringify(n))!=null?h:"";this.msgHandlers.forEach(a=>a(r));}),o.onClose(n=>{this.socket!==o||this.manuallyClosed||(this.opened=false,this.closeHandlers.forEach(r=>r(n)),this.scheduleReconnect(n,"close"));}),o.onError(n=>{this.socket!==o||this.manuallyClosed||this.errorHandlers.forEach(r=>r(n));});try{await o.connect();}catch(n){throw this.socket===o&&await this.cleanupSocket(false),this.manuallyClosed||this.scheduleReconnect(n,"error"),n}}async send(t){var e;await((e=this.socket)==null?void 0:e.send(t));}async close(t,e){this.manuallyClosed=true,this.desiredUrl=null,this.reconnectAttempts=0,await this.clearReconnectTimer(),await this.removeAppStateListeners(),await this.cleanupSocket(true,t,e,true);}isConnected(){return this.opened}onMessage(t){this.msgHandlers.push(t);}onOpen(t){this.openHandlers.push(t);}offOpen(t){this.openHandlers=this.openHandlers.filter(e=>e!==t);}onClose(t){this.closeHandlers.push(t);}offClose(t){this.closeHandlers=this.closeHandlers.filter(e=>e!==t);}onError(t){this.errorHandlers.push(t);}offError(t){this.errorHandlers=this.errorHandlers.filter(e=>e!==t);}onReconnect(t){this.reconnectHandlers.push(t);}offReconnect(t){this.reconnectHandlers=this.reconnectHandlers.filter(e=>e!==t);}getReconnectOptions(){var s,o,n,r,h,a;let t=this.options.reconnect;if(t===false)return null;let e=t===true||t==null?{}:t;return e.enabled===false?null:{initialDelayMs:Math.max(0,(s=e.initialDelayMs)!=null?s:500),maxDelayMs:Math.max(0,(o=e.maxDelayMs)!=null?o:1e4),factor:Math.max(1,(n=e.factor)!=null?n:2),maxAttempts:(r=e.maxAttempts)!=null?r:Number.POSITIVE_INFINITY,jitterRatio:Math.max(0,Math.min(1,(h=e.jitterRatio)!=null?h:.2)),reconnectOnForeground:(a=e.reconnectOnForeground)!=null?a:true,shouldReconnect:e.shouldReconnect}}scheduleReconnect(t,e="close"){let s=this.getReconnectOptions(),o=this.desiredUrl;if(!s||!o||this.manuallyClosed||this.opened||this.reconnectTimerId!=null||this.reconnectTimerPending||s.shouldReconnect&&!s.shouldReconnect(t)||this.reconnectAttempts>=s.maxAttempts)return;let n=this.reconnectDelay(s,this.reconnectAttempts+1),r=++this.reconnectTimerToken,h=false;this.reconnectTimerPending=true,ouider.OUID.setTimeout(()=>{r===this.reconnectTimerToken&&(h=true,this.reconnectTimerId=null,this.reconnectTimerPending=false,this.reconnectNow(e,t));},n).then(a=>{if(h||r!==this.reconnectTimerToken||this.manuallyClosed||this.opened){this.reconnectTimerPending=false,ouider.OUID.clearTimeout(a);return}this.reconnectTimerId=a,this.reconnectTimerPending=false;}).catch(a=>{this.reconnectTimerPending=false,this.errorHandlers.forEach(l=>l(a));});}async reconnectNow(t,e,s=false){let o=this.getReconnectOptions(),n=this.desiredUrl;if(!o||!n||this.manuallyClosed||o.shouldReconnect&&!o.shouldReconnect(e)||this.opened&&!s)return;if(this.reconnecting)return this.reconnecting;if(this.reconnectAttempts>=o.maxAttempts)return;await this.clearReconnectTimer();let r=this.reconnectAttempts+1;this.reconnectAttempts=r;let h={attempt:r,url:n,reason:t,cause:e};this.reconnectHandlers.forEach(l=>l(h));let a=(async()=>{try{s&&this.socket&&await this.cleanupSocket(!0).catch(()=>{}),this.manuallyClosed||await this.openSocket(n);}catch(l){this.manuallyClosed||this.scheduleReconnect(l,"error");}})();this.reconnecting=a;try{await a;}finally{this.reconnecting===a&&(this.reconnecting=null);}}reconnectDelay(t,e){let s=Math.min(t.maxDelayMs,t.initialDelayMs*Math.pow(t.factor,Math.max(0,e-1)));if(s<=0||t.jitterRatio<=0)return Math.round(s);let o=s*t.jitterRatio;return Math.max(0,Math.round(s-o+Math.random()*o*2))}async clearReconnectTimer(){this.reconnectTimerToken++,this.reconnectTimerPending=false;let t=this.reconnectTimerId;this.reconnectTimerId=null,t!=null&&await ouider.OUID.clearTimeout(t).catch(()=>{});}ensureAppStateListeners(){let t=this.getReconnectOptions();t!=null&&t.reconnectOnForeground&&(this.ensureBackgroundListener(),this.ensureForegroundListener());}ensureBackgroundListener(){this.offBackground||this.backgroundListenerPending||(this.backgroundListenerPending=true,ouider.OUID.system.onBackground(()=>{this.appBackgrounded=true;}).then(t=>{var e;this.backgroundListenerPending=false,this.manuallyClosed||!((e=this.getReconnectOptions())!=null&&e.reconnectOnForeground)?t():this.offBackground=t;}).catch(()=>{this.backgroundListenerPending=false;}));}ensureForegroundListener(){this.offForeground||this.foregroundListenerPending||(this.foregroundListenerPending=true,ouider.OUID.system.onForeground(()=>{let t=this.appBackgrounded;this.appBackgrounded=false,!(this.manuallyClosed||!this.desiredUrl)&&this.reconnectNow("foreground",void 0,t);}).then(t=>{var e;this.foregroundListenerPending=false,this.manuallyClosed||!((e=this.getReconnectOptions())!=null&&e.reconnectOnForeground)?t():this.offForeground=t;}).catch(()=>{this.foregroundListenerPending=false;}));}async removeAppStateListeners(){let t=this.offForeground,e=this.offBackground;this.offForeground=null,this.offBackground=null,this.appBackgrounded=false,await Promise.all([t==null?void 0:t().catch(()=>{}),e==null?void 0:e().catch(()=>{})]);}async cleanupSocket(t,e,s,o=false){let n=this.socket;this.socket=null,this.currentUrl=null,o&&(this.desiredUrl=null),this.opened=false;let r=this.offMessage;this.offMessage=null,await(r==null?void 0:r().catch(()=>{})),t&&await(n==null?void 0:n.close(e,s));}};c(K,"WebSocketTransport");var gt=K,Q=class Q{constructor(t){i(this,"transport");i(this,"handlers",new Map);i(this,"rpcResolvers",new Map);i(this,"rpcCounter",0);i(this,"handleRawMessage",c(t=>{if(!t)return;let e;try{e=JSON.parse(t);}catch(n){return}let s=this.extractRpcId(e);if(s){let n=this.rpcResolvers.get(s);n&&(n.resolve(this.extractRpcValue(e)),this.rpcResolvers.delete(s));}let o=this.handlers.get(e.type);if(o)for(let n of o)n(e);},"handleRawMessage"));this.transport=t,this.transport.onMessage(this.handleRawMessage),this.transport.onClose(()=>this.rejectPendingRpc(new Error("Network connection closed"))),this.transport.onError(e=>{this.rejectPendingRpc(e instanceof Error?e:new Error("Network connection error"));});}async connect(t){await this.transport.connect(t);}isConnected(){return this.transport.isConnected()}onOpen(t){this.transport.onOpen(t);}offOpen(t){var e,s;(s=(e=this.transport).offOpen)==null||s.call(e,t);}onClose(t){this.transport.onClose(t);}offClose(t){var e,s;(s=(e=this.transport).offClose)==null||s.call(e,t);}onError(t){this.transport.onError(t);}offError(t){var e,s;(s=(e=this.transport).offError)==null||s.call(e,t);}onReconnect(t){var e,s;(s=(e=this.transport).onReconnect)==null||s.call(e,t);}offReconnect(t){var e,s;(s=(e=this.transport).offReconnect)==null||s.call(e,t);}send(t,e){let s={type:t,payload:e};return this.transport.send(JSON.stringify(s))}on(t,e){var o;let s=(o=this.handlers.get(t))!=null?o:[];s.push(e),this.handlers.set(t,s);}off(t,e){let s=this.handlers.get(t);s&&this.handlers.set(t,s.filter(o=>o!==e));}call(t,e){let s=`rpc_${++this.rpcCounter}_${Date.now()}`,o=e&&typeof e=="object"?nt(it({},e),{rpcId:s}):{value:e,rpcId:s},n={type:t,payload:o};return new Promise((r,h)=>{this.rpcResolvers.set(s,{resolve:r,reject:h}),this.transport.send(JSON.stringify(n)).catch(a=>{this.rpcResolvers.delete(s),h(a);});})}close(){return this.rejectPendingRpc(new Error("Network client closed")),this.transport.close()}extractRpcId(t){if(typeof t.rpcId=="string")return t.rpcId;if(t.payload&&typeof t.payload=="object"&&"rpcId"in t.payload){let e=t.payload.rpcId;return typeof e=="string"?e:void 0}}extractRpcValue(t){return "result"in t?t.result:t.payload}rejectPendingRpc(t){this.rpcResolvers.forEach(({reject:e})=>e(t)),this.rpcResolvers.clear();}};c(Q,"NetworkClient");var mt=Q,Z=class Z{constructor(t,e,s){i(this,"net");i(this,"channels");i(this,"state");i(this,"onChangeHandlers",[]);var o;this.net=t,this.channels=s,this.state=e,this.net.on((o=s==null?void 0:s.update)!=null?o:"state_update",n=>{let{payload:r}=n;r.full?this.state=r.full:r.patch&&(this.state=Object.assign({},this.state,r.patch)),this.emitChange();});}getState(){return this.state}onChange(t){this.onChangeHandlers.push(t);}emitChange(){for(let t of this.onChangeHandlers)t(this.state);}sendPatch(t){var e,s;return this.net.send((s=(e=this.channels)==null?void 0:e.patch)!=null?s:"state_patch",t)}};c(Z,"StateSyncClient");var wt=Z;exports.AssetManager=L;exports.AudioManager=H;exports.Camera2D=lt;exports.CollisionSystem=pt;exports.Game=B;exports.InputManager=O;exports.NetworkClient=mt;exports.PhysicsBody=ct;exports.Scene=ut;exports.SceneLayer=_;exports.SceneNode=k;exports.Sprite=ot;exports.SpriteAnimation=C;exports.SpriteLayer=rt;exports.SpriteNode=dt;exports.SpriteSheet=D;exports.StateSyncClient=wt;exports.TileRenderer=ht;exports.WebSocketTransport=gt;exports.aabbOverlap=ft;exports.createSpritePhysicsCollider=de;exports.getTileSourceRect=Ht;exports.objectSize=b;exports.tulon=Qt;exports.worldToTileX=x;exports.worldToTileY=v;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import {ODOM,OUID as OUID$1}from'@ouidesigner/ouider';var et=Object.defineProperty,bt=Object.defineProperties;var Dt=Object.getOwnPropertyDescriptors;var tt=Object.getOwnPropertySymbols;var Ct=Object.prototype.hasOwnProperty,Et=Object.prototype.propertyIsEnumerable;var Y=(d,t,e)=>t in d?et(d,t,{enumerable:true,configurable:true,writable:true,value:e}):d[t]=e,st=(d,t)=>{for(var e in t||(t={}))Ct.call(t,e)&&Y(d,e,t[e]);if(tt)for(var e of tt(t))Et.call(t,e)&&Y(d,e,t[e]);return d},it=(d,t)=>bt(d,Dt(t)),c=(d,t)=>et(d,"name",{value:t,configurable:true});var i=(d,t,e)=>Y(d,typeof t!="symbol"?t+"":t,e);var z=class z{constructor(t,e,s,o,n=0){i(this,"image");i(this,"frameWidth");i(this,"frameHeight");i(this,"frameCount");i(this,"from");i(this,"columns");this.image=t,this.frameWidth=e,this.frameHeight=s,this.frameCount=o,this.from=n;let r=I(t);this.columns=Math.max(1,Math.floor(r.width/e));}};c(z,"SpriteSheet");var E=z,S=class S{constructor(t,e=10,s=true){i(this,"sheet");i(this,"fps");i(this,"loop");i(this,"time",0);this.sheet=t,this.fps=e,this.loop=s;}update(t){this.time+=t;let e=this.sheet.frameCount/this.fps;this.loop?this.time=this.time%e:this.time>e&&(this.time=e);}draw(t,e,s,o=1,n=1){let r=Math.floor(this.time*this.fps)+this.sheet.from,a=Math.min(r,this.sheet.frameCount+this.sheet.from-1),h=this.sheet.columns,l=a%h*this.sheet.frameWidth,p=Math.floor(a/h)*this.sheet.frameHeight,u=this.sheet.frameWidth*o,f=this.sheet.frameHeight*n;t.drawImage(this.sheet.image,l,p,this.sheet.frameWidth,this.sheet.frameHeight,e,s,u,f);}reset(){this.time=0;}};c(S,"SpriteAnimation");var O=S,L=class L{constructor(t){i(this,"animation");i(this,"width");i(this,"height");i(this,"visible");i(this,"tag");i(this,"data");var e,s,o,n;this.animation=t.animation,this.visible=(e=t.visible)!=null?e:true,this.tag=t.tag,this.data=(s=t.data)!=null?s:{},this.width=(o=t.width)!=null?o:this.animation.sheet.frameWidth,this.height=(n=t.height)!=null?n:this.animation.sheet.frameHeight;}update(t){this.animation.update(t);}draw(t,e=1,s=1){this.visible&&this.animation.draw(t,0,0,e,s);}};c(L,"Sprite");var ot=L,W=class W{constructor(){i(this,"sprites",[]);}add(t){return this.sprites.push(t),t}remove(t){let e=this.sprites.indexOf(t);e>=0&&this.sprites.splice(e,1);}clear(){this.sprites.length=0;}update(t){for(let e of this.sprites)e.update(t);}draw(t){for(let e of this.sprites)e.draw(t);}withTag(t){return this.sprites.filter(e=>e.tag===t)}};c(W,"SpriteLayer");var nt=W;var B=class B{constructor(){i(this,"imageDefs",new Map);i(this,"soundDefs",new Map);i(this,"sheetDefs",new Map);i(this,"images",new Map);i(this,"sounds",new Map);i(this,"sheets",new Map);}image(t,e){this.imageDefs.set(t,e);}sound(t,e){this.soundDefs.set(t,e);}spriteSheet(t,e){this.sheetDefs.set(t,e);}async loadAll(){var s,o;let t=[],e=[];for(let[n,r]of this.imageDefs.entries())t.push(this.loadImage(r).then(a=>{this.images.set(n,a);}));for(let[n,r]of this.soundDefs.entries())e.push(this.loadSound(r).then(a=>{this.sounds.set(n,a);}));await Promise.all([...t,...e]);for(let[n,r]of this.sheetDefs.entries()){let a=(s=this.getImage(r.image))!=null?s:this.images.get(r.image);if(!a)throw new Error(`SpriteSheet "${n}" missing image "${r.image}"`);let h=new E(a,r.frameWidth,r.frameHeight,r.frameCount,(o=r.from)!=null?o:0);this.sheets.set(n,h);}}async loadImage(t){let e=await ODOM.Image.new();if(!e)throw new Error(`Unable to create image for "${t}"`);let s=null,o=null,n=false,r=c(async()=>{let h=[s,o].filter(Boolean);s=null,o=null,await Promise.all(h.map(l=>e.dettachEventListener(l).catch(()=>{})));},"detach"),a=c(async()=>{let[h,l,p,u]=await Promise.all([e.naturalWidth(),e.naturalHeight(),e.width(),e.height()]);e.__local_size__={width:h||p||0,height:l||u||0};},"cacheSize");return new Promise(async(h,l)=>{let p=c(async()=>{if(!n)try{await a(),n=!0,await r(),h(e);}catch(f){await u(f);}},"done"),u=c(async f=>{n||(n=true,await r(),await e.release().catch(()=>{}),l(new Error(`Unable to load image "${t}"`)));},"fail");try{s=await e.addEventListener("load",()=>{p();},{options:{once:!0}}),o=await e.addEventListener("error",()=>{u();},{options:{once:!0}}),await e.setSrc(t);let[f,m]=await Promise.all([e.getProperty("complete").catch(()=>!1),e.naturalWidth().catch(()=>0)]);f&&m>0&&await p();}catch(f){await u(f);}})}async loadSound(t){return await OUID.fetch(t,{method:"GET",headers:{},body:null,credentials:"omit"},"buffer")}getImage(t){return this.images.get(t)}getSpriteSheet(t){return this.sheets.get(t)}createAnimation(t,e,s){let o=this.getSpriteSheet(t);if(!o)throw new Error(`SpriteSheet "${t}" not found`);return new O(o,e!=null?e:10,s!=null?s:true)}getSound(t){return this.sounds.get(t)}clean(){this.images.forEach(t=>t.release()),this.sounds.forEach(t=>t.release()),this.images.clear(),this.sounds.clear(),this.sheets.clear();}};c(B,"AssetManager");var H=B;var A=class A{constructor(t){i(this,"assets");i(this,"audioContext",null);i(this,"audioDestination",null);i(this,"decodedBuffers",new Map);this.assets=t;}async getContext(){if(this.audioContext)return this.audioContext;let t=await OUID.newObject("AudioContext");return t?(this.audioContext=ODOM.OObject.toInstance(t),this.audioContext):null}async getDestination(t){if(this.audioDestination)return this.audioDestination;let e=await t.getPropertyAndHold("destination");return e?(this.audioDestination=e,this.audioDestination):null}async getBuffer(t){if(this.decodedBuffers.has(t))return this.decodedBuffers.get(t);let e=this.assets.getSound(t);if(!e)return null;let s=await this.getContext();if(!s)return null;let o=await s.invokeAndHold("decodeAudioData",e.ref);return o&&this.decodedBuffers.set(t,o),o}clean(){this.decodedBuffers.forEach(t=>t.release()),this.decodedBuffers.clear(),this.audioDestination&&(this.audioDestination.release(),this.audioDestination=null),this.audioContext&&(this.audioContext.invoke("close"),this.audioContext.release(),this.audioContext=null);}async play(t,e){let s=await this.getContext();if(!s)return;let o=await this.getBuffer(t);if(!o)return;await s.invoke("resume");let n=await s.invokeAndHold("createBufferSource");if(!n)return;await n.setProperty("buffer",o.ref),(e==null?void 0:e.loop)!=null&&await n.setProperty("loop",e.loop);let r=await this.getDestination(s);if(!r){n.release();return}if((e==null?void 0:e.volume)!=null){let a=await s.invokeAndHold("createGain");if(a){let h=await a.getPropertyAndHold("gain");h&&(await h.setProperty("value",e.volume),h.release()),await n.invoke("connect",a.ref),await a.invoke("connect",r.ref),await n.invoke("start",0),a.release(),n.release();return}}await n.invoke("connect",r.ref),await n.invoke("start",0),n.release();}};c(A,"AudioManager");var X=A;var R=class R{constructor(t){i(this,"keysDown",new Set);i(this,"keysPressed",new Set);i(this,"buttonsPressed",new Set);i(this,"buttonsDown",new Set);i(this,"mouseX",-1);i(this,"mouseY",-1);i(this,"offsetX",-1);i(this,"offsetY",-1);i(this,"ids",[]);i(this,"disposed",false);this.track(OUID.addEventListener("window","keydown",e=>{this.keysDown.has(e.code)||this.keysPressed.add(e.code),this.keysDown.add(e.code);})),this.track(OUID.addEventListener("window","keyup",e=>{this.keysDown.delete(e.code);})),this.track(t.addEventListener("mousedown",e=>{let s=e.button===0?"MouseLeft":"MouseRight";this.buttonsDown.has(s)||this.buttonsPressed.add(s),this.buttonsDown.add(s),this.updateMousePosition(e);})),this.track(t.addEventListener("mouseup",e=>{let s=e.button===0?"MouseLeft":"MouseRight";this.buttonsDown.delete(s),this.updateMousePosition(e);})),this.track(t.addEventListener("mousemove",e=>{this.updateMousePosition(e);})),this.track(t.addEventListener("mouseleave",()=>{this.buttonsDown.clear();}));}update(){this.keysPressed.clear(),this.buttonsPressed.clear();}isDown(t){return this.keysDown.has(t)||this.buttonsDown.has(t)}isPressed(t){return this.keysPressed.has(t)||this.buttonsPressed.has(t)}clean(){this.disposed=true,this.ids.forEach(t=>{OUID.dettachEventListener(t);}),this.ids=[],this.keysDown.clear(),this.keysPressed.clear(),this.buttonsDown.clear(),this.buttonsPressed.clear();}track(t){t.then(e=>{this.disposed?OUID.dettachEventListener(e):this.ids.push(e);}).catch(()=>{});}updateMousePosition(t){var e,s,o,n;this.mouseX=(e=t.x)!=null?e:this.mouseX,this.mouseY=(s=t.y)!=null?s:this.mouseY,this.offsetX=(o=t.offsetX)!=null?o:this.offsetX,this.offsetY=(n=t.offsetY)!=null?n:this.offsetY;}};c(R,"InputManager");var T=R;var $=class ${constructor(t){i(this,"config");i(this,"canvas");i(this,"ctx2d");i(this,"assets");i(this,"audio");i(this,"input");i(this,"world",{});i(this,"running",false);i(this,"initialized",false);i(this,"lastTime",0);i(this,"frameId",null);i(this,"resizeListenerId",null);this.config=t,this.canvas=t.canvas,this.assets=new H,this.audio=new X(this.assets),this.input=new T(this.canvas);}buildContext(t){return {dt:t,canvas:this.canvas,ctx2d:this.ctx2d,world:this.world,assets:this.assets,audio:this.audio,input:this.input}}async updateLocalSize(){var s,o;let t=(s=await this.canvas.width())!=null?s:0,e=(o=await this.canvas.height())!=null?o:0;this.canvas.__local_size__=this.ctx2d.__local_size__={width:t,height:e};}async syncSize(){var a,h;let[t,e,s,o]=await Promise.all([this.canvas.getProperty("clientWidth"),this.canvas.getProperty("clientHeight"),this.canvas.width(),this.canvas.height()]),n=(a=t!=null?t:s)!=null?a:0,r=(h=e!=null?e:o)!=null?h:0;await this.canvas.setProperty("width",n),await this.canvas.setProperty("height",r);}async init(){if(this.initialized)return;let t=await this.canvas.getContext("2d");if(!t)throw new Error("Unable to create a context");this.ctx2d=t,this.resizeListenerId=await this.canvas.addEventListener("resize",()=>{this.syncSize().then(()=>this.updateLocalSize());}),this.config.preload&&(this.config.preload(this.assets),await this.assets.loadAll()),await this.syncSize(),await this.updateLocalSize(),this.config.setup&&this.config.setup(this.buildContext(0)),this.initialized=true;}start(){this.running||(this.running=true,this.lastTime=performance.now(),this.scheduleFrame());}stop(){var t;!this.running&&!this.initialized||(this.running=false,this.frameId!=null&&(OUID.cancelAnimationFrame(this.frameId),this.frameId=null),this.resizeListenerId&&(OUID.dettachEventListener(this.resizeListenerId),this.resizeListenerId=null),this.audio.clean(),this.assets.clean(),this.input.clean(),(t=this.ctx2d)==null||t.release(),this.initialized=false);}scheduleFrame(){OUID.requestAnimationFrame(t=>{this.frameId=null,this.tick(t);}).then(t=>{this.running?this.frameId=t:OUID.cancelAnimationFrame(t);});}async tick(t){var o,n,r,a;if(!this.running)return;let s=Math.min((t-this.lastTime)/1e3,.1);this.lastTime=t;try{let h=this.buildContext(s);(n=(o=this.config).update)==null||n.call(o,h),this.input.update(),await((a=(r=this.config).draw)==null?void 0:a.call(r,h)),await this.ctx2d.commit();}catch(h){console.error("[Toubani] game loop error",h);}finally{this.running&&this.scheduleFrame();}}};c($,"Game");var U=$;function Kt(d){return new U(d)}c(Kt,"tulon");function I(d){var t;return (t=d.__local_size__)!=null?t:{width:0,height:0}}c(I,"objectSize");function v(d,t){return Math.floor(d/t.tileWidth)}c(v,"worldToTileX");function M(d,t){return Math.floor(d/t.tileHeight)}c(M,"worldToTileY");function Ht(d,t){let{image:e,tileWidth:s,tileHeight:o,margin:n=0,spacing:r=0}=t,a=I(e),h=Math.max(1,Math.floor((a.width-2*n+r)/(s+r))),l=d-1,p=l%h,u=Math.floor(l/h),f=n+p*(s+r),m=n+u*(o+r);return {sx:f,sy:m,sw:s,sh:o}}c(Ht,"getTileSourceRect");var N=class N{constructor(t){i(this,"tileset");i(this,"sourceRectCache",new Map);this.tileset=t;}draw(t,e,s,o,n){let{tileWidth:r,tileHeight:a}=e,h=o*s.focusX,l=n*s.focusY,p=s.zoom,u=s.x-h/p,f=s.y-l/p,m=s.x+(o-h)/p,w=s.y+(n-l)/p,g=Math.max(0,Math.floor(u/r)),y=Math.min(e.width-1,Math.floor(m/r)),b=Math.max(0,Math.floor(f/a)),mt=Math.min(e.height-1,Math.floor(w/a));for(let D=b;D<=mt;D++)for(let C=g;C<=y;C++){let _=e.getTile(C,D);if(_<=0)continue;let{sx:xt,sy:yt,sw:vt,sh:Mt}=this.getSourceRect(_),kt=C*r,Pt=D*a;t.drawImage(this.tileset.image,xt,yt,vt,Mt,kt,Pt,r,a);}}getSourceRect(t){let e=this.sourceRectCache.get(t);return e||(e=Ht(t,this.tileset),this.sourceRectCache.set(t,e)),e}};c(N,"TileRenderer");var rt=N;var j=class j{constructor(t,e){i(this,"node");i(this,"settings");i(this,"vx",0);i(this,"vy",0);i(this,"onGround",false);this.node=t,this.settings=e;}update(t){var f,m,w,g,y;let e=this.settings,s=(f=e.gravityX)!=null?f:0,o=(m=e.gravityY)!=null?m:1e3,n=(w=e.maxFallSpeed)!=null?w:2e3,r=(g=e.friction)!=null?g:.8,a=(y=e.airFriction)!=null?y:.98;this.vx+=s*t,this.vy+=o*t,this.vy>n&&(this.vy=n);let h=Math.max(1,Math.min(e.tileMap.tileWidth,e.tileMap.tileHeight)*.5),l=Math.max(Math.abs(this.vx*t),Math.abs(this.vy*t)),p=Math.max(1,Math.ceil(l/h)),u=t/p;for(let b=0;b<p;b++)this.moveAndCollideX(u),this.moveAndCollideY(u);this.onGround?this.vx*=r:this.vx*=a;}moveAndCollideX(t){let e=this.settings.tileMap,s=this.settings.width,o=this.settings.height,n=this.node.x+this.vx*t,r=this.node.y,a=Math.sign(this.vx);if(a===0){this.node.x=n;return}let h=n,l=n+s,p=r,u=r+o,f=Math.max(0,M(p,e)),m=Math.min(e.height-1,M(u-1,e));if(a>0){let w=v(l,e);for(let g=f;g<=m;g++)if(this.isSolid(w,g)){l=w*e.tileWidth,h=l-s,this.vx=0;break}}else {let w=v(h,e);for(let g=f;g<=m;g++)if(this.isSolid(w,g)){h=(w+1)*e.tileWidth,l=h+s,this.vx=0;break}}this.node.x=h;}moveAndCollideY(t){let e=this.settings.tileMap,s=this.settings.width,o=this.settings.height,n=this.node.x,r=this.node.y+this.vy*t,a=Math.sign(this.vy);if(this.onGround=false,a===0){this.node.y=r;return}let h=r,l=r+o,p=n,u=n+s,f=Math.max(0,v(p,e)),m=Math.min(e.width-1,v(u-1,e));if(a>0){let w=M(l,e);for(let g=f;g<=m;g++)if(this.isSolid(g,w)){l=w*e.tileHeight,h=l-o,this.vy=0,this.onGround=true;break}}else {let w=M(h,e);for(let g=f;g<=m;g++)if(this.isSolid(g,w)){h=(w+1)*e.tileHeight,l=h+o,this.vy=0;break}}this.node.y=h;}isSolid(t,e){let s=this.settings.tileMap;return t<0||e<0||t>=s.width||e>=s.height?false:s.isSolid(t,e)}};c(j,"PhysicsBody");var at=j;var F=class F{constructor(){i(this,"x",0);i(this,"y",0);i(this,"zoom",1);i(this,"rotation",0);i(this,"followTarget",null);i(this,"followLerp",.1);i(this,"focusX",.5);i(this,"focusY",.5);}follow(t,e=.1){this.followTarget=t,this.followLerp=e;}unfollow(){this.followTarget=null;}update(t){if(!this.followTarget)return;let e=this.followTarget.x,s=this.followTarget.y;this.x+=(e-this.x)*this.followLerp,this.y+=(s-this.y)*this.followLerp;}begin(t,e,s){t.save();let o=e*this.focusX,n=s*this.focusY;t.translate(o,n),this.rotation!==0&&t.rotate(-this.rotation),this.zoom!==1&&t.scale(this.zoom,this.zoom),t.translate(-this.x,-this.y);}end(t){t.restore();}};c(F,"Camera2D");var ht=F;var J=class J{constructor(){i(this,"x",0);i(this,"y",0);i(this,"rotation",0);i(this,"scaleX",1);i(this,"scaleY",1);i(this,"visible",true);i(this,"parent",null);i(this,"children",[]);}add(t){return t.parent=this,this.children.push(t),t}remove(t){let e=this.children.indexOf(t);e>=0&&(this.children.splice(e,1),t.parent=null);}clear(){for(let t of this.children)t.parent=null;this.children.length=0;}update(t){}drawSelf(t){}draw(t){if(this.visible){t.save(),t.translate(this.x,this.y),this.rotation!==0&&t.rotate(this.rotation),(this.scaleX!==1||this.scaleY!==1)&&t.scale(this.scaleX,this.scaleY),this.drawSelf(t);for(let e of this.children)e.draw(t);t.restore();}}updateTree(t){this.update(t);for(let e of this.children)e.updateTree(t);}getWorldPosition(){let t=this.x,e=this.y,s=this.parent;for(;s;)t+=s.x,e+=s.y,s=s.parent;return {x:t,y:e}}};c(J,"SceneNode");var P=J,q=class q extends P{constructor(e){super();i(this,"sprite");this.sprite=e;}update(e){this.sprite.update(e);}drawSelf(e){this.sprite.draw(e);}getWorldBounds(){let e=this.getWorldPosition();return {x:e.x,y:e.y,width:this.sprite.width*this.scaleX,height:this.sprite.height*this.scaleY}}};c(q,"SpriteNode");var ct=q,K=class K extends P{constructor(e=1,s=1){super();i(this,"parallaxX",1);i(this,"parallaxY",1);this.parallaxX=e,this.parallaxY=s;}};c(K,"SceneLayer");var G=K,k=class k{constructor(){i(this,"id");i(this,"root",new P);i(this,"layers",[]);this.id=k.id++;}addLayer(t=1,e=1){let s=new G(t,e);return this.layers.push(s),this.root.add(s),s}update(t){this.root.updateTree(t);}draw(t){this.root.draw(t);}};c(k,"Scene"),i(k,"id",0);var lt=k;function dt(d,t){return !(d.x+d.width<=t.x||d.x>=t.x+t.width||d.y+d.height<=t.y||d.y>=t.y+t.height)}c(dt,"aabbOverlap");var Q=class Q{constructor(){i(this,"colliders",[]);i(this,"rules",[]);i(this,"rulesByPair",new Map);}add(t){this.colliders.push(t);}remove(t){let e=this.colliders.indexOf(t);e>=0&&this.colliders.splice(e,1);}clear(){this.colliders.length=0;}onCollision(t,e,s){var a;let o={tagA:t,tagB:e,handler:s};this.rules.push(o);let n=this.ruleKey(t,e),r=(a=this.rulesByPair.get(n))!=null?a:[];r.push(o),this.rulesByPair.set(n,r);}update(){var o;let t=new Map,e=new Map;for(let n of this.colliders){if(!n.isActive)continue;let r=(o=t.get(n.tag))!=null?o:[];r.push(n),t.set(n.tag,r);}let s=c(n=>{let r=e.get(n);return r||(r=n.getBounds(),e.set(n,r)),r},"getBounds");for(let[n,r]of this.rulesByPair.entries()){let[a,h]=n.split("\0"),l=t.get(a),p=t.get(h);if(!(!l||!p))if(a===h)for(let u=0;u<l.length;u++){let f=l[u],m=s(f);for(let w=u+1;w<l.length;w++){let g=l[w];dt(m,s(g))&&this.triggerRules(f,g,r);}}else for(let u of l){let f=s(u);for(let m of p)dt(f,s(m))&&this.triggerRules(u,m,r);}}}ruleKey(t,e){return t<=e?`${t}\0${e}`:`${e}\0${t}`}triggerRules(t,e,s){for(let o of s){let n=o.tagA===t.tag&&o.tagB===e.tag,r=o.tagA===e.tag&&o.tagB===t.tag;n?o.handler(t,e):r&&o.handler(e,t);}}};c(Q,"CollisionSystem");var ut=Q,Xt=0;function he(d,t,e,s){return {id:`col-${Xt++}`,tag:d,isActive:true,data:{node:t,body:e,sprite:s},getBounds(){let n=t.getWorldPosition(),r=e.settings.width,a=e.settings.height;return {x:n.x,y:n.y,width:r,height:a}}}}c(he,"createSpritePhysicsCollider");var x=class x{constructor(){i(this,"opened",false);i(this,"socket",null);i(this,"msgHandlers",[]);i(this,"openHandlers",[]);i(this,"closeHandlers",[]);i(this,"errorHandlers",[]);i(this,"ids",[]);}async connect(t){if(this.socket){let s=await this.socket.getProperty("readyState");if(s===x.OPEN||s===x.CONNECTING)return;await this.cleanupSocket(false);}let e=await OUID$1.newObject("WebSocket",t);if(!e)throw new Error(`Unable to create WebSocket for ${t}`);this.socket=ODOM.OObject.toInstance(e),this.ids.push(await this.socket.addEventListener("open",()=>{this.opened=true,this.openHandlers.forEach(s=>s());})),this.ids.push(await this.socket.addEventListener("message",s=>{let o=typeof s.data=="string"?s.data:"";this.msgHandlers.forEach(n=>n(o));})),this.ids.push(await this.socket.addEventListener("close",s=>{this.opened=false,this.closeHandlers.forEach(o=>o(s));})),this.ids.push(await this.socket.addEventListener("error",s=>{this.errorHandlers.forEach(o=>o(s));}));try{await new Promise((s,o)=>{let n=c(()=>{h(),s();},"onOpen"),r=c(l=>{h(),o(l);},"onError"),a=c(l=>{h(),o(l);},"onClose"),h=c(()=>{this.offOpen(n),this.offError(r),this.offClose(a);},"cleanup");this.onOpen(n),this.onError(r),this.onClose(a);});}catch(s){throw await this.cleanupSocket(false),s}}async send(t){this.socket&&await this.socket.getProperty("readyState")===x.OPEN&&await this.socket.invoke("send",t);}async close(t,e){await this.cleanupSocket(true,t,e);}isConnected(){return this.opened}onMessage(t){this.msgHandlers.push(t);}onOpen(t){this.openHandlers.push(t);}offOpen(t){this.openHandlers=this.openHandlers.filter(e=>e!==t);}onClose(t){this.closeHandlers.push(t);}offClose(t){this.closeHandlers=this.closeHandlers.filter(e=>e!==t);}onError(t){this.errorHandlers.push(t);}offError(t){this.errorHandlers=this.errorHandlers.filter(e=>e!==t);}async cleanupSocket(t,e,s){let o=this.socket;this.socket=null,this.opened=false;let n=this.ids.splice(0);if(await Promise.all(n.map(r=>OUID$1.dettachEventListener(r).catch(()=>{}))),!!o)try{if(t){let r=await o.getProperty("readyState").catch(()=>null);(r===x.OPEN||r===x.CONNECTING)&&await o.invoke("close",e,s);}}finally{await o.release().catch(()=>{});}}};c(x,"WebSocketTransport"),i(x,"OPEN",1),i(x,"CONNECTING",0);var pt=x,V=class V{constructor(t){i(this,"transport");i(this,"handlers",new Map);i(this,"rpcResolvers",new Map);i(this,"rpcCounter",0);i(this,"handleRawMessage",c(t=>{if(!t)return;let e;try{e=JSON.parse(t);}catch(o){return}if(e.payload&&typeof e.payload=="object"&&"rpcId"in e.payload){let o=e.payload.rpcId,n=this.rpcResolvers.get(o);n&&(n.resolve(e.payload),this.rpcResolvers.delete(o));}let s=this.handlers.get(e.type);if(s)for(let o of s)o(e);},"handleRawMessage"));this.transport=t,this.transport.onMessage(this.handleRawMessage);}async connect(t){await this.transport.connect(t);}isConnected(){return this.transport.isConnected()}send(t,e){let s={type:t,payload:e};this.transport.send(JSON.stringify(s));}on(t,e){var o;let s=(o=this.handlers.get(t))!=null?o:[];s.push(e),this.handlers.set(t,s);}off(t,e){let s=this.handlers.get(t);s&&this.handlers.set(t,s.filter(o=>o!==e));}call(t,e){let s=`rpc_${++this.rpcCounter}_${Date.now()}`,o=e&&typeof e=="object"?it(st({},e),{rpcId:s}):{value:e,rpcId:s},n={type:t,payload:o};return new Promise((r,a)=>{this.rpcResolvers.set(s,{resolve:r,reject:a}),this.transport.send(JSON.stringify(n)).catch(h=>{this.rpcResolvers.delete(s),a(h);});})}close(){return this.rpcResolvers.forEach(({reject:t})=>t(new Error("Network client closed"))),this.rpcResolvers.clear(),this.transport.close()}};c(V,"NetworkClient");var gt=V,Z=class Z{constructor(t,e,s){i(this,"net");i(this,"channels");i(this,"state");i(this,"onChangeHandlers",[]);var o;this.net=t,this.channels=s,this.state=e,this.net.on((o=s==null?void 0:s.update)!=null?o:"state_update",n=>{let{payload:r}=n;r.full?this.state=r.full:r.patch&&(this.state=Object.assign({},this.state,r.patch)),this.emitChange();});}getState(){return this.state}onChange(t){this.onChangeHandlers.push(t);}emitChange(){for(let t of this.onChangeHandlers)t(this.state);}sendPatch(t){var e,s;this.net.send((s=(e=this.channels)==null?void 0:e.patch)!=null?s:"state_patch",t);}};c(Z,"StateSyncClient");var wt=Z;export{H as AssetManager,X as AudioManager,ht as Camera2D,ut as CollisionSystem,U as Game,T as InputManager,gt as NetworkClient,at as PhysicsBody,lt as Scene,G as SceneLayer,P as SceneNode,ot as Sprite,O as SpriteAnimation,nt as SpriteLayer,ct as SpriteNode,E as SpriteSheet,wt as StateSyncClient,rt as TileRenderer,pt as WebSocketTransport,dt as aabbOverlap,he as createSpritePhysicsCollider,Ht as getTileSourceRect,I as objectSize,Kt as tulon,v as worldToTileX,M as worldToTileY};
1
+ import {ODOM,OUID as OUID$1}from'@ouidesigner/ouider';import'@ouidesigner/ouider-device';import {OUIDNetwork}from'@ouidesigner/ouider-network';var st=Object.defineProperty,Rt=Object.defineProperties;var It=Object.getOwnPropertyDescriptors;var et=Object.getOwnPropertySymbols;var Dt=Object.prototype.hasOwnProperty,Ct=Object.prototype.propertyIsEnumerable;var S=(d,t,e)=>t in d?st(d,t,{enumerable:true,configurable:true,writable:true,value:e}):d[t]=e,it=(d,t)=>{for(var e in t||(t={}))Dt.call(t,e)&&S(d,e,t[e]);if(et)for(var e of et(t))Ct.call(t,e)&&S(d,e,t[e]);return d},nt=(d,t)=>Rt(d,It(t)),c=(d,t)=>st(d,"name",{value:t,configurable:true});var i=(d,t,e)=>S(d,typeof t!="symbol"?t+"":t,e);var E=class E{constructor(t,e,s,o,n=0){i(this,"image");i(this,"frameWidth");i(this,"frameHeight");i(this,"frameCount");i(this,"from");i(this,"columns");this.image=t,this.frameWidth=e,this.frameHeight=s,this.frameCount=o,this.from=n;let r=b(t);this.columns=Math.max(1,Math.floor(r.width/e));}};c(E,"SpriteSheet");var D=E,X=class X{constructor(t,e=10,s=true){i(this,"sheet");i(this,"fps");i(this,"loop");i(this,"time",0);this.sheet=t,this.fps=e,this.loop=s;}update(t){this.time+=t;let e=this.sheet.frameCount/this.fps;this.loop?this.time=this.time%e:this.time>e&&(this.time=e);}draw(t,e,s,o=1,n=1){let r=Math.floor(this.time*this.fps)+this.sheet.from,h=Math.min(r,this.sheet.frameCount+this.sheet.from-1),a=this.sheet.columns,l=h%a*this.sheet.frameWidth,p=Math.floor(h/a)*this.sheet.frameHeight,u=this.sheet.frameWidth*o,f=this.sheet.frameHeight*n;t.drawImage(this.sheet.image,l,p,this.sheet.frameWidth,this.sheet.frameHeight,e,s,u,f);}reset(){this.time=0;}};c(X,"SpriteAnimation");var C=X,Y=class Y{constructor(t){i(this,"animation");i(this,"width");i(this,"height");i(this,"visible");i(this,"tag");i(this,"data");var e,s,o,n;this.animation=t.animation,this.visible=(e=t.visible)!=null?e:true,this.tag=t.tag,this.data=(s=t.data)!=null?s:{},this.width=(o=t.width)!=null?o:this.animation.sheet.frameWidth,this.height=(n=t.height)!=null?n:this.animation.sheet.frameHeight;}update(t){this.animation.update(t);}draw(t,e=1,s=1){this.visible&&this.animation.draw(t,0,0,e,s);}};c(Y,"Sprite");var ot=Y,z=class z{constructor(){i(this,"sprites",[]);}add(t){return this.sprites.push(t),t}remove(t){let e=this.sprites.indexOf(t);e>=0&&this.sprites.splice(e,1);}clear(){this.sprites.length=0;}update(t){for(let e of this.sprites)e.update(t);}draw(t){for(let e of this.sprites)e.draw(t);}withTag(t){return this.sprites.filter(e=>e.tag===t)}};c(z,"SpriteLayer");var rt=z;var A=class A{constructor(){i(this,"imageDefs",new Map);i(this,"soundDefs",new Map);i(this,"sheetDefs",new Map);i(this,"images",new Map);i(this,"sounds",new Map);i(this,"sheets",new Map);}image(t,e){this.imageDefs.set(t,e);}sound(t,e){this.soundDefs.set(t,e);}spriteSheet(t,e){this.sheetDefs.set(t,e);}async loadAll(){var s,o;let t=[],e=[];for(let[n,r]of this.imageDefs.entries())t.push(this.loadImage(r).then(h=>{this.images.set(n,h);}));for(let[n,r]of this.soundDefs.entries())e.push(this.loadSound(r).then(h=>{this.sounds.set(n,h);}));await Promise.all([...t,...e]);for(let[n,r]of this.sheetDefs.entries()){let h=(s=this.getImage(r.image))!=null?s:this.images.get(r.image);if(!h)throw new Error(`SpriteSheet "${n}" missing image "${r.image}"`);let a=new D(h,r.frameWidth,r.frameHeight,r.frameCount,(o=r.from)!=null?o:0);this.sheets.set(n,a);}}async loadImage(t){let e=await ODOM.Image.new();if(!e)throw new Error(`Unable to create image for "${t}"`);let s=null,o=null,n=false,r=c(async()=>{let a=[s,o].filter(Boolean);s=null,o=null,await Promise.all(a.map(l=>e.dettachEventListener(l).catch(()=>{})));},"detach"),h=c(async()=>{let[a,l,p,u]=await Promise.all([e.naturalWidth(),e.naturalHeight(),e.width(),e.height()]);e.__local_size__={width:a||p||0,height:l||u||0};},"cacheSize");return new Promise(async(a,l)=>{let p=c(async()=>{if(!n)try{await h(),n=!0,await r(),a(e);}catch(f){await u(f);}},"done"),u=c(async f=>{n||(n=true,await r(),await e.release().catch(()=>{}),l(new Error(`Unable to load image "${t}"`)));},"fail");try{s=await e.addEventListener("load",()=>{p();},{options:{once:!0}}),o=await e.addEventListener("error",()=>{u();},{options:{once:!0}}),await e.setSrc(t);let[f,w]=await Promise.all([e.getProperty("complete").catch(()=>!1),e.naturalWidth().catch(()=>0)]);f&&w>0&&await p();}catch(f){await u(f);}})}async loadSound(t){return await OUID$1.media.audio.decode(t),t}getImage(t){return this.images.get(t)}getSpriteSheet(t){return this.sheets.get(t)}createAnimation(t,e,s){let o=this.getSpriteSheet(t);if(!o)throw new Error(`SpriteSheet "${t}" not found`);return new C(o,e!=null?e:10,s!=null?s:true)}getSound(t){return this.sounds.get(t)}clean(){this.images.forEach(t=>t.release()),this.images.clear(),this.sounds.clear(),this.sheets.clear();}};c(A,"AssetManager");var L=A;var U=class U{constructor(t){i(this,"assets");this.assets=t;}clean(){OUID$1.media.audio.close();}async play(t,e){let s=this.assets.getSound(t);s&&await OUID$1.media.audio.play(s,e);}};c(U,"AudioManager");var H=U;var W=class W{constructor(t){i(this,"keysDown",new Set);i(this,"keysPressed",new Set);i(this,"buttonsPressed",new Set);i(this,"buttonsDown",new Set);i(this,"mouseX",-1);i(this,"mouseY",-1);i(this,"offsetX",-1);i(this,"offsetY",-1);i(this,"ids",[]);i(this,"disposed",false);this.track(OUID.addEventListener("window","keydown",e=>{this.keysDown.has(e.code)||this.keysPressed.add(e.code),this.keysDown.add(e.code);})),this.track(OUID.addEventListener("window","keyup",e=>{this.keysDown.delete(e.code);})),this.track(t.addEventListener("mousedown",e=>{let s=e.button===0?"MouseLeft":"MouseRight";this.buttonsDown.has(s)||this.buttonsPressed.add(s),this.buttonsDown.add(s),this.updateMousePosition(e);})),this.track(t.addEventListener("mouseup",e=>{let s=e.button===0?"MouseLeft":"MouseRight";this.buttonsDown.delete(s),this.updateMousePosition(e);})),this.track(t.addEventListener("mousemove",e=>{this.updateMousePosition(e);})),this.track(t.addEventListener("mouseleave",()=>{this.buttonsDown.clear();}));}update(){this.keysPressed.clear(),this.buttonsPressed.clear();}isDown(t){return this.keysDown.has(t)||this.buttonsDown.has(t)}isPressed(t){return this.keysPressed.has(t)||this.buttonsPressed.has(t)}clean(){this.disposed=true,this.ids.forEach(t=>{OUID.dettachEventListener(t);}),this.ids=[],this.keysDown.clear(),this.keysPressed.clear(),this.buttonsDown.clear(),this.buttonsPressed.clear();}track(t){t.then(e=>{this.disposed?OUID.dettachEventListener(e):this.ids.push(e);}).catch(()=>{});}updateMousePosition(t){var e,s,o,n;this.mouseX=(e=t.x)!=null?e:this.mouseX,this.mouseY=(s=t.y)!=null?s:this.mouseY,this.offsetX=(o=t.offsetX)!=null?o:this.offsetX,this.offsetY=(n=t.offsetY)!=null?n:this.offsetY;}};c(W,"InputManager");var O=W;var F=class F{constructor(t){i(this,"config");i(this,"canvas");i(this,"ctx2d");i(this,"assets");i(this,"audio");i(this,"input");i(this,"world",{});i(this,"running",false);i(this,"initialized",false);i(this,"lastTime",0);i(this,"frameId",null);i(this,"resizeListenerId",null);this.config=t,this.canvas=t.canvas,this.assets=new L,this.audio=new H(this.assets),this.input=new O(this.canvas);}buildContext(t){return {dt:t,canvas:this.canvas,ctx2d:this.ctx2d,world:this.world,assets:this.assets,audio:this.audio,input:this.input}}async updateLocalSize(){var s,o;let t=(s=await this.canvas.width())!=null?s:0,e=(o=await this.canvas.height())!=null?o:0;this.canvas.__local_size__=this.ctx2d.__local_size__={width:t,height:e};}async syncSize(){var h,a;let[t,e,s,o]=await Promise.all([this.canvas.getProperty("clientWidth"),this.canvas.getProperty("clientHeight"),this.canvas.width(),this.canvas.height()]),n=(h=t!=null?t:s)!=null?h:0,r=(a=e!=null?e:o)!=null?a:0;await this.canvas.setProperty("width",n),await this.canvas.setProperty("height",r);}async init(){if(this.initialized)return;let t=await this.canvas.getContext("2d");if(!t)throw new Error("Unable to create a context");this.ctx2d=t,this.resizeListenerId=await this.canvas.addEventListener("resize",()=>{this.syncSize().then(()=>this.updateLocalSize());}),this.config.preload&&(this.config.preload(this.assets),await this.assets.loadAll()),await this.syncSize(),await this.updateLocalSize(),this.config.setup&&this.config.setup(this.buildContext(0)),this.initialized=true;}start(){this.running||(this.running=true,this.lastTime=performance.now(),this.scheduleFrame());}stop(){var t;!this.running&&!this.initialized||(this.running=false,this.frameId!=null&&(OUID.cancelAnimationFrame(this.frameId),this.frameId=null),this.resizeListenerId&&(OUID.dettachEventListener(this.resizeListenerId),this.resizeListenerId=null),this.audio.clean(),this.assets.clean(),this.input.clean(),(t=this.ctx2d)==null||t.release(),this.initialized=false);}scheduleFrame(){OUID.requestAnimationFrame(t=>{this.frameId=null,this.tick(t);}).then(t=>{this.running?this.frameId=t:OUID.cancelAnimationFrame(t);});}async tick(t){var o,n,r,h;if(!this.running)return;let s=Math.min((t-this.lastTime)/1e3,.1);this.lastTime=t;try{let a=this.buildContext(s);(n=(o=this.config).update)==null||n.call(o,a),this.input.update(),await((h=(r=this.config).draw)==null?void 0:h.call(r,a)),await this.ctx2d.commit();}catch(a){console.error("[Toubani] game loop error",a);}finally{this.running&&this.scheduleFrame();}}};c(F,"Game");var B=F;function Qt(d){return new B(d)}c(Qt,"tulon");function b(d){var t;return (t=d.__local_size__)!=null?t:{width:0,height:0}}c(b,"objectSize");function x(d,t){return Math.floor(d/t.tileWidth)}c(x,"worldToTileX");function v(d,t){return Math.floor(d/t.tileHeight)}c(v,"worldToTileY");function Ht(d,t){let{image:e,tileWidth:s,tileHeight:o,margin:n=0,spacing:r=0}=t,h=b(e),a=Math.max(1,Math.floor((h.width-2*n+r)/(s+r))),l=d-1,p=l%a,u=Math.floor(l/a),f=n+p*(s+r),w=n+u*(o+r);return {sx:f,sy:w,sw:s,sh:o}}c(Ht,"getTileSourceRect");var j=class j{constructor(t){i(this,"tileset");i(this,"sourceRectCache",new Map);this.tileset=t;}draw(t,e,s,o,n){let{tileWidth:r,tileHeight:h}=e,a=o*s.focusX,l=n*s.focusY,p=s.zoom,u=s.x-a/p,f=s.y-l/p,w=s.x+(o-a)/p,m=s.y+(n-l)/p,g=Math.max(0,Math.floor(u/r)),y=Math.min(e.width-1,Math.floor(w/r)),P=Math.max(0,Math.floor(f/h)),yt=Math.min(e.height-1,Math.floor(m/h));for(let R=P;R<=yt;R++)for(let I=g;I<=y;I++){let tt=e.getTile(I,R);if(tt<=0)continue;let{sx:xt,sy:vt,sw:Mt,sh:kt}=this.getSourceRect(tt),Tt=I*r,Pt=R*h;t.drawImage(this.tileset.image,xt,vt,Mt,kt,Tt,Pt,r,h);}}getSourceRect(t){let e=this.sourceRectCache.get(t);return e||(e=Ht(t,this.tileset),this.sourceRectCache.set(t,e)),e}};c(j,"TileRenderer");var ht=j;var $=class ${constructor(t,e){i(this,"node");i(this,"settings");i(this,"vx",0);i(this,"vy",0);i(this,"onGround",false);this.node=t,this.settings=e;}update(t){var f,w,m,g,y;let e=this.settings,s=(f=e.gravityX)!=null?f:0,o=(w=e.gravityY)!=null?w:1e3,n=(m=e.maxFallSpeed)!=null?m:2e3,r=(g=e.friction)!=null?g:.8,h=(y=e.airFriction)!=null?y:.98;this.vx+=s*t,this.vy+=o*t,this.vy>n&&(this.vy=n);let a=Math.max(1,Math.min(e.tileMap.tileWidth,e.tileMap.tileHeight)*.5),l=Math.max(Math.abs(this.vx*t),Math.abs(this.vy*t)),p=Math.max(1,Math.ceil(l/a)),u=t/p;this.onGround=false;for(let P=0;P<p;P++)this.moveAndCollideX(u),this.moveAndCollideY(u);this.onGround?this.vx*=r:this.vx*=h;}moveAndCollideX(t){let e=this.settings.tileMap,s=this.settings.width,o=this.settings.height,n=this.node.x+this.vx*t,r=this.node.y,h=Math.sign(this.vx);if(h===0){this.node.x=n;return}let a=n,l=n+s,p=r,u=r+o,f=Math.max(0,v(p,e)),w=Math.min(e.height-1,v(u-1,e));if(h>0){let m=x(l,e);for(let g=f;g<=w;g++)if(this.isSolid(m,g)){l=m*e.tileWidth,a=l-s,this.vx=0;break}}else {let m=x(a,e);for(let g=f;g<=w;g++)if(this.isSolid(m,g)){a=(m+1)*e.tileWidth,l=a+s,this.vx=0;break}}this.node.x=a;}moveAndCollideY(t){let e=this.settings.tileMap,s=this.settings.width,o=this.settings.height,n=this.node.x,r=this.node.y+this.vy*t,h=Math.sign(this.vy);if(h===0){this.node.y=r;return}let a=r,l=r+o,p=n,u=n+s,f=Math.max(0,x(p,e)),w=Math.min(e.width-1,x(u-1,e));if(h>0){let m=v(l,e);for(let g=f;g<=w;g++)if(this.isSolid(g,m)){l=m*e.tileHeight,a=l-o,this.vy=0,this.onGround=true;break}}else {let m=v(a,e);for(let g=f;g<=w;g++)if(this.isSolid(g,m)){a=(m+1)*e.tileHeight,l=a+o,this.vy=0;break}}this.node.y=a;}isSolid(t,e){let s=this.settings.tileMap;return t<0||e<0||t>=s.width||e>=s.height?false:s.isSolid(t,e)}};c($,"PhysicsBody");var ct=$;var N=class N{constructor(){i(this,"x",0);i(this,"y",0);i(this,"zoom",1);i(this,"rotation",0);i(this,"followTarget",null);i(this,"followLerp",.1);i(this,"focusX",.5);i(this,"focusY",.5);}follow(t,e=.1){this.followTarget=t,this.followLerp=e;}unfollow(){this.followTarget=null;}update(t){if(!this.followTarget)return;let e=this.followTarget.x,s=this.followTarget.y;this.x+=(e-this.x)*this.followLerp,this.y+=(s-this.y)*this.followLerp;}begin(t,e,s){t.save();let o=e*this.focusX,n=s*this.focusY;t.translate(o,n),this.rotation!==0&&t.rotate(-this.rotation),this.zoom!==1&&t.scale(this.zoom,this.zoom),t.translate(-this.x,-this.y);}end(t){t.restore();}};c(N,"Camera2D");var lt=N;var J=class J{constructor(){i(this,"x",0);i(this,"y",0);i(this,"rotation",0);i(this,"scaleX",1);i(this,"scaleY",1);i(this,"visible",true);i(this,"parent",null);i(this,"children",[]);}add(t){return t.parent=this,this.children.push(t),t}remove(t){let e=this.children.indexOf(t);e>=0&&(this.children.splice(e,1),t.parent=null);}clear(){for(let t of this.children)t.parent=null;this.children.length=0;}update(t){}drawSelf(t){}draw(t){if(this.visible){t.save(),t.translate(this.x,this.y),this.rotation!==0&&t.rotate(this.rotation),(this.scaleX!==1||this.scaleY!==1)&&t.scale(this.scaleX,this.scaleY),this.drawSelf(t);for(let e of this.children)e.draw(t);t.restore();}}updateTree(t){this.update(t);for(let e of this.children)e.updateTree(t);}getWorldPosition(){let t=this.x,e=this.y,s=this.parent;for(;s;)t+=s.x,e+=s.y,s=s.parent;return {x:t,y:e}}};c(J,"SceneNode");var k=J,V=class V extends k{constructor(e){super();i(this,"sprite");this.sprite=e;}update(e){this.sprite.update(e);}drawSelf(e){this.sprite.draw(e);}getWorldBounds(){let e=this.getWorldPosition();return {x:e.x,y:e.y,width:this.sprite.width*this.scaleX,height:this.sprite.height*this.scaleY}}};c(V,"SpriteNode");var dt=V,G=class G extends k{constructor(e=1,s=1){super();i(this,"parallaxX",1);i(this,"parallaxY",1);this.parallaxX=e,this.parallaxY=s;}};c(G,"SceneLayer");var _=G,M=class M{constructor(){i(this,"id");i(this,"root",new k);i(this,"layers",[]);this.id=M.id++;}addLayer(t=1,e=1){let s=new _(t,e);return this.layers.push(s),this.root.add(s),s}update(t){this.root.updateTree(t);}draw(t){this.root.draw(t);}};c(M,"Scene"),i(M,"id",0);var ut=M;function ft(d,t){return !(d.x+d.width<=t.x||d.x>=t.x+t.width||d.y+d.height<=t.y||d.y>=t.y+t.height)}c(ft,"aabbOverlap");var q=class q{constructor(){i(this,"colliders",[]);i(this,"rules",[]);i(this,"rulesByPair",new Map);}add(t){this.colliders.push(t);}remove(t){let e=this.colliders.indexOf(t);e>=0&&this.colliders.splice(e,1);}clear(){this.colliders.length=0;}onCollision(t,e,s){var h;let o={tagA:t,tagB:e,handler:s};this.rules.push(o);let n=this.ruleKey(t,e),r=(h=this.rulesByPair.get(n))!=null?h:[];r.push(o),this.rulesByPair.set(n,r);}update(){var o;let t=new Map,e=new Map;for(let n of this.colliders){if(!n.isActive)continue;let r=(o=t.get(n.tag))!=null?o:[];r.push(n),t.set(n.tag,r);}let s=c(n=>{let r=e.get(n);return r||(r=n.getBounds(),e.set(n,r)),r},"getBounds");for(let[n,r]of this.rulesByPair.entries()){let[h,a]=n.split("\0"),l=t.get(h),p=t.get(a);if(!(!l||!p))if(h===a)for(let u=0;u<l.length;u++){let f=l[u],w=s(f);for(let m=u+1;m<l.length;m++){let g=l[m];ft(w,s(g))&&this.triggerRules(f,g,r);}}else for(let u of l){let f=s(u);for(let w of p)ft(f,s(w))&&this.triggerRules(u,w,r);}}}ruleKey(t,e){return t<=e?`${t}\0${e}`:`${e}\0${t}`}triggerRules(t,e,s){for(let o of s){let n=o.tagA===t.tag&&o.tagB===e.tag,r=o.tagA===e.tag&&o.tagB===t.tag;n?o.handler(t,e):r&&o.handler(e,t);}}};c(q,"CollisionSystem");var pt=q,Ot=0;function de(d,t,e,s){return {id:`col-${Ot++}`,tag:d,isActive:true,data:{node:t,body:e,sprite:s},getBounds(){let n=t.getWorldPosition(),r=e.settings.width,h=e.settings.height;return {x:n.x,y:n.y,width:r,height:h}}}}c(de,"createSpritePhysicsCollider");var K=class K{constructor(t={}){i(this,"options");i(this,"opened",false);i(this,"socket",null);i(this,"currentUrl",null);i(this,"desiredUrl",null);i(this,"offMessage",null);i(this,"msgHandlers",[]);i(this,"openHandlers",[]);i(this,"closeHandlers",[]);i(this,"errorHandlers",[]);i(this,"reconnectHandlers",[]);i(this,"reconnectAttempts",0);i(this,"reconnectTimerId",null);i(this,"reconnectTimerPending",false);i(this,"reconnectTimerToken",0);i(this,"reconnecting",null);i(this,"manuallyClosed",true);i(this,"appBackgrounded",false);i(this,"offForeground",null);i(this,"offBackground",null);i(this,"foregroundListenerPending",false);i(this,"backgroundListenerPending",false);this.options=t;}async connect(t){this.desiredUrl=t,this.manuallyClosed=false,this.reconnectAttempts=0,await this.clearReconnectTimer(),this.ensureAppStateListeners(),await this.openSocket(t);}async openSocket(t){if(this.socket){if(this.currentUrl===t&&this.socket.isConnected())return;if(this.currentUrl===t&&!this.opened){let n=this.socket;try{await n.connect();}catch(r){throw this.socket===n&&await this.cleanupSocket(false),this.manuallyClosed||this.scheduleReconnect(r,"error"),r}return}await this.cleanupSocket(true);}let{protocols:e,connectTimeoutMs:s}=this.options,o=OUIDNetwork.websocket(t,e,{connectTimeoutMs:s});this.socket=o,this.currentUrl=t,o.onOpen(()=>{this.socket!==o||this.manuallyClosed||(this.opened=true,this.reconnectAttempts=0,this.openHandlers.forEach(n=>n()));}),this.offMessage=await o.onMessage(n=>{var h;let r=typeof n=="string"?n:(h=JSON.stringify(n))!=null?h:"";this.msgHandlers.forEach(a=>a(r));}),o.onClose(n=>{this.socket!==o||this.manuallyClosed||(this.opened=false,this.closeHandlers.forEach(r=>r(n)),this.scheduleReconnect(n,"close"));}),o.onError(n=>{this.socket!==o||this.manuallyClosed||this.errorHandlers.forEach(r=>r(n));});try{await o.connect();}catch(n){throw this.socket===o&&await this.cleanupSocket(false),this.manuallyClosed||this.scheduleReconnect(n,"error"),n}}async send(t){var e;await((e=this.socket)==null?void 0:e.send(t));}async close(t,e){this.manuallyClosed=true,this.desiredUrl=null,this.reconnectAttempts=0,await this.clearReconnectTimer(),await this.removeAppStateListeners(),await this.cleanupSocket(true,t,e,true);}isConnected(){return this.opened}onMessage(t){this.msgHandlers.push(t);}onOpen(t){this.openHandlers.push(t);}offOpen(t){this.openHandlers=this.openHandlers.filter(e=>e!==t);}onClose(t){this.closeHandlers.push(t);}offClose(t){this.closeHandlers=this.closeHandlers.filter(e=>e!==t);}onError(t){this.errorHandlers.push(t);}offError(t){this.errorHandlers=this.errorHandlers.filter(e=>e!==t);}onReconnect(t){this.reconnectHandlers.push(t);}offReconnect(t){this.reconnectHandlers=this.reconnectHandlers.filter(e=>e!==t);}getReconnectOptions(){var s,o,n,r,h,a;let t=this.options.reconnect;if(t===false)return null;let e=t===true||t==null?{}:t;return e.enabled===false?null:{initialDelayMs:Math.max(0,(s=e.initialDelayMs)!=null?s:500),maxDelayMs:Math.max(0,(o=e.maxDelayMs)!=null?o:1e4),factor:Math.max(1,(n=e.factor)!=null?n:2),maxAttempts:(r=e.maxAttempts)!=null?r:Number.POSITIVE_INFINITY,jitterRatio:Math.max(0,Math.min(1,(h=e.jitterRatio)!=null?h:.2)),reconnectOnForeground:(a=e.reconnectOnForeground)!=null?a:true,shouldReconnect:e.shouldReconnect}}scheduleReconnect(t,e="close"){let s=this.getReconnectOptions(),o=this.desiredUrl;if(!s||!o||this.manuallyClosed||this.opened||this.reconnectTimerId!=null||this.reconnectTimerPending||s.shouldReconnect&&!s.shouldReconnect(t)||this.reconnectAttempts>=s.maxAttempts)return;let n=this.reconnectDelay(s,this.reconnectAttempts+1),r=++this.reconnectTimerToken,h=false;this.reconnectTimerPending=true,OUID$1.setTimeout(()=>{r===this.reconnectTimerToken&&(h=true,this.reconnectTimerId=null,this.reconnectTimerPending=false,this.reconnectNow(e,t));},n).then(a=>{if(h||r!==this.reconnectTimerToken||this.manuallyClosed||this.opened){this.reconnectTimerPending=false,OUID$1.clearTimeout(a);return}this.reconnectTimerId=a,this.reconnectTimerPending=false;}).catch(a=>{this.reconnectTimerPending=false,this.errorHandlers.forEach(l=>l(a));});}async reconnectNow(t,e,s=false){let o=this.getReconnectOptions(),n=this.desiredUrl;if(!o||!n||this.manuallyClosed||o.shouldReconnect&&!o.shouldReconnect(e)||this.opened&&!s)return;if(this.reconnecting)return this.reconnecting;if(this.reconnectAttempts>=o.maxAttempts)return;await this.clearReconnectTimer();let r=this.reconnectAttempts+1;this.reconnectAttempts=r;let h={attempt:r,url:n,reason:t,cause:e};this.reconnectHandlers.forEach(l=>l(h));let a=(async()=>{try{s&&this.socket&&await this.cleanupSocket(!0).catch(()=>{}),this.manuallyClosed||await this.openSocket(n);}catch(l){this.manuallyClosed||this.scheduleReconnect(l,"error");}})();this.reconnecting=a;try{await a;}finally{this.reconnecting===a&&(this.reconnecting=null);}}reconnectDelay(t,e){let s=Math.min(t.maxDelayMs,t.initialDelayMs*Math.pow(t.factor,Math.max(0,e-1)));if(s<=0||t.jitterRatio<=0)return Math.round(s);let o=s*t.jitterRatio;return Math.max(0,Math.round(s-o+Math.random()*o*2))}async clearReconnectTimer(){this.reconnectTimerToken++,this.reconnectTimerPending=false;let t=this.reconnectTimerId;this.reconnectTimerId=null,t!=null&&await OUID$1.clearTimeout(t).catch(()=>{});}ensureAppStateListeners(){let t=this.getReconnectOptions();t!=null&&t.reconnectOnForeground&&(this.ensureBackgroundListener(),this.ensureForegroundListener());}ensureBackgroundListener(){this.offBackground||this.backgroundListenerPending||(this.backgroundListenerPending=true,OUID$1.system.onBackground(()=>{this.appBackgrounded=true;}).then(t=>{var e;this.backgroundListenerPending=false,this.manuallyClosed||!((e=this.getReconnectOptions())!=null&&e.reconnectOnForeground)?t():this.offBackground=t;}).catch(()=>{this.backgroundListenerPending=false;}));}ensureForegroundListener(){this.offForeground||this.foregroundListenerPending||(this.foregroundListenerPending=true,OUID$1.system.onForeground(()=>{let t=this.appBackgrounded;this.appBackgrounded=false,!(this.manuallyClosed||!this.desiredUrl)&&this.reconnectNow("foreground",void 0,t);}).then(t=>{var e;this.foregroundListenerPending=false,this.manuallyClosed||!((e=this.getReconnectOptions())!=null&&e.reconnectOnForeground)?t():this.offForeground=t;}).catch(()=>{this.foregroundListenerPending=false;}));}async removeAppStateListeners(){let t=this.offForeground,e=this.offBackground;this.offForeground=null,this.offBackground=null,this.appBackgrounded=false,await Promise.all([t==null?void 0:t().catch(()=>{}),e==null?void 0:e().catch(()=>{})]);}async cleanupSocket(t,e,s,o=false){let n=this.socket;this.socket=null,this.currentUrl=null,o&&(this.desiredUrl=null),this.opened=false;let r=this.offMessage;this.offMessage=null,await(r==null?void 0:r().catch(()=>{})),t&&await(n==null?void 0:n.close(e,s));}};c(K,"WebSocketTransport");var gt=K,Q=class Q{constructor(t){i(this,"transport");i(this,"handlers",new Map);i(this,"rpcResolvers",new Map);i(this,"rpcCounter",0);i(this,"handleRawMessage",c(t=>{if(!t)return;let e;try{e=JSON.parse(t);}catch(n){return}let s=this.extractRpcId(e);if(s){let n=this.rpcResolvers.get(s);n&&(n.resolve(this.extractRpcValue(e)),this.rpcResolvers.delete(s));}let o=this.handlers.get(e.type);if(o)for(let n of o)n(e);},"handleRawMessage"));this.transport=t,this.transport.onMessage(this.handleRawMessage),this.transport.onClose(()=>this.rejectPendingRpc(new Error("Network connection closed"))),this.transport.onError(e=>{this.rejectPendingRpc(e instanceof Error?e:new Error("Network connection error"));});}async connect(t){await this.transport.connect(t);}isConnected(){return this.transport.isConnected()}onOpen(t){this.transport.onOpen(t);}offOpen(t){var e,s;(s=(e=this.transport).offOpen)==null||s.call(e,t);}onClose(t){this.transport.onClose(t);}offClose(t){var e,s;(s=(e=this.transport).offClose)==null||s.call(e,t);}onError(t){this.transport.onError(t);}offError(t){var e,s;(s=(e=this.transport).offError)==null||s.call(e,t);}onReconnect(t){var e,s;(s=(e=this.transport).onReconnect)==null||s.call(e,t);}offReconnect(t){var e,s;(s=(e=this.transport).offReconnect)==null||s.call(e,t);}send(t,e){let s={type:t,payload:e};return this.transport.send(JSON.stringify(s))}on(t,e){var o;let s=(o=this.handlers.get(t))!=null?o:[];s.push(e),this.handlers.set(t,s);}off(t,e){let s=this.handlers.get(t);s&&this.handlers.set(t,s.filter(o=>o!==e));}call(t,e){let s=`rpc_${++this.rpcCounter}_${Date.now()}`,o=e&&typeof e=="object"?nt(it({},e),{rpcId:s}):{value:e,rpcId:s},n={type:t,payload:o};return new Promise((r,h)=>{this.rpcResolvers.set(s,{resolve:r,reject:h}),this.transport.send(JSON.stringify(n)).catch(a=>{this.rpcResolvers.delete(s),h(a);});})}close(){return this.rejectPendingRpc(new Error("Network client closed")),this.transport.close()}extractRpcId(t){if(typeof t.rpcId=="string")return t.rpcId;if(t.payload&&typeof t.payload=="object"&&"rpcId"in t.payload){let e=t.payload.rpcId;return typeof e=="string"?e:void 0}}extractRpcValue(t){return "result"in t?t.result:t.payload}rejectPendingRpc(t){this.rpcResolvers.forEach(({reject:e})=>e(t)),this.rpcResolvers.clear();}};c(Q,"NetworkClient");var mt=Q,Z=class Z{constructor(t,e,s){i(this,"net");i(this,"channels");i(this,"state");i(this,"onChangeHandlers",[]);var o;this.net=t,this.channels=s,this.state=e,this.net.on((o=s==null?void 0:s.update)!=null?o:"state_update",n=>{let{payload:r}=n;r.full?this.state=r.full:r.patch&&(this.state=Object.assign({},this.state,r.patch)),this.emitChange();});}getState(){return this.state}onChange(t){this.onChangeHandlers.push(t);}emitChange(){for(let t of this.onChangeHandlers)t(this.state);}sendPatch(t){var e,s;return this.net.send((s=(e=this.channels)==null?void 0:e.patch)!=null?s:"state_patch",t)}};c(Z,"StateSyncClient");var wt=Z;export{L as AssetManager,H as AudioManager,lt as Camera2D,pt as CollisionSystem,B as Game,O as InputManager,mt as NetworkClient,ct as PhysicsBody,ut as Scene,_ as SceneLayer,k as SceneNode,ot as Sprite,C as SpriteAnimation,rt as SpriteLayer,dt as SpriteNode,D as SpriteSheet,wt as StateSyncClient,ht as TileRenderer,gt as WebSocketTransport,ft as aabbOverlap,de as createSpritePhysicsCollider,Ht as getTileSourceRect,b as objectSize,Qt as tulon,x as worldToTileX,v as worldToTileY};
@@ -0,0 +1,26 @@
1
+ # AI Guide for @ouidesigner/toubani
2
+
3
+ Use this file when an AI coding agent adds Toubani game or interactive-scene behavior to an Ouider mini app.
4
+
5
+ ## Package Role
6
+
7
+ `@ouidesigner/toubani` provides game-oriented helpers for Ouider projects, including:
8
+
9
+ - game loop and scene structure;
10
+ - input handling;
11
+ - sprites, tiles, camera, and collision helpers;
12
+ - audio and asset helpers;
13
+ - optional network helpers through `@ouidesigner/ouider-network`.
14
+
15
+ ## Rules
16
+
17
+ - Keep the Ouider mini-app bundle contract intact: main app code stays separate from WebView preload code.
18
+ - Do not use direct DOM APIs in main app code unless the project explicitly runs only in unified WebView mode.
19
+ - If network helpers are used, read `node_modules/@ouidesigner/ouider-network/docs/ai/index.md`.
20
+ - If audio, media, or other native host capabilities are used, update `manifest.json` with the matching permission.
21
+ - Keep game loops disposable. Stop timers, animation loops, listeners, and network transports when the route or scene is destroyed.
22
+
23
+ ## Verification
24
+
25
+ Run the project typecheck/build after changes and test on the intended host if the feature depends on input, audio, network, or frame timing.
26
+
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@ouidesigner/toubani",
3
- "version": "0.0.9",
3
+ "version": "0.1.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
7
7
  "clean": "rm -rf dist",
8
- "build": "npm run clean;tsup",
8
+ "build": "npm run clean && tsup",
9
9
  "test": "echo \"Error: no test specified\" && exit 1"
10
10
  },
11
11
  "keywords": [],
@@ -13,7 +13,9 @@
13
13
  "license": "ISC",
14
14
  "type": "commonjs",
15
15
  "peerDependencies": {
16
- "@ouidesigner/ouider": "^0.3.0"
16
+ "@ouidesigner/ouider": "^0.3.2",
17
+ "@ouidesigner/ouider-network": "^0.1.3",
18
+ "@ouidesigner/ouider-device": "^0.0.7"
17
19
  },
18
20
  "devDependencies": {
19
21
  "@types/node": "^24.10.1",
@@ -27,7 +29,8 @@
27
29
  },
28
30
  "types": "dist/index.d.ts",
29
31
  "files": [
30
- "/dist"
32
+ "/dist",
33
+ "/docs/ai"
31
34
  ],
32
35
  "publishConfig": {
33
36
  "access": "public"