incanto 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/3d.d.ts CHANGED
@@ -377,6 +377,9 @@ interface CreateGame3DOptions {
377
377
  seed?: number;
378
378
  fixedHz?: number;
379
379
  pixelRatio?: number;
380
+ /** Keep the frame rate by rendering fewer pixels while frames run slow
381
+ * (default ON). `false` pins the resolution — screenshots, capture, tests. */
382
+ adaptiveResolution?: boolean;
380
383
  resolveScene?: LoadSceneOptions["resolveScene"];
381
384
  /** @internal Test seam — replaces the Renderer3D construction. */
382
385
  _rendererFactory?: (engine: Engine, canvas: HTMLCanvasElement) => GameRenderer;
@@ -2466,6 +2469,14 @@ interface Renderer3DOptions {
2466
2469
  assets?: AssetStore3D;
2467
2470
  /** Defaults to `window.devicePixelRatio` capped at 2. */
2468
2471
  pixelRatio?: number;
2472
+ /**
2473
+ * Keep the frame rate by rendering at fewer pixels when frames run slow, and
2474
+ * hand them back when they recover (default ON). A retina display asks for
2475
+ * 4× the pixels; shaded water is fragment-heavy; the honest answer is fewer
2476
+ * pixels of the same world, only while it is actually needed. `false` pins
2477
+ * the resolution — for screenshots, video capture and pixel-diff tests.
2478
+ */
2479
+ adaptiveResolution?: boolean;
2469
2480
  }
2470
2481
  /**
2471
2482
  * WebGL presentation layer: subscribes to `engine.updated`, mirrors the active
@@ -2533,10 +2544,22 @@ declare class Renderer3D {
2533
2544
  private bloomCompositeScene;
2534
2545
  private bloomCompositeUniforms;
2535
2546
  constructor(opts: Renderer3DOptions);
2547
+ /** The pixel ratio the scene ASKED for — the governor scales this, never replaces it. */
2548
+ private readonly basePixelRatio;
2549
+ private readonly adaptive;
2550
+ private lastFrameAt;
2536
2551
  private readonly debugLines;
2537
2552
  private readonly selectionLines;
2538
2553
  private syncSelectionOutline;
2539
2554
  private syncDebugLines;
2555
+ /**
2556
+ * Frame-rate governor: measure the frame, and when the median of a window is
2557
+ * slow, render the same world at fewer pixels (and hand them back when it
2558
+ * recovers). Resizing the drawing buffer is all it takes — every render
2559
+ * target in the pipeline is keyed off `getDrawingBufferSize`, so the water
2560
+ * grab, the mirror and the bloom chain follow automatically.
2561
+ */
2562
+ private governResolution;
2540
2563
  private render;
2541
2564
  /**
2542
2565
  * Bloom post pass (only when `environment.bloom` is declared). LDR and safe:
package/dist/3d.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { t as IncantoError } from "./errors-BpWbnbb_.js";
2
2
  import { A as Water3D, F as StaticBody3D, I as Node3D, M as CharacterBody3D, N as PhysicsBody3D, P as RigidBody3D, j as Area3D, z as WATER_MAX_RIPPLES } from "./gameplay-BJ5gXIks.js";
3
- import { a as Environment3D, c as sunDirectionFromElevationAzimuth, i as syncTree, l as sunDirectionFromSky, o as horizonColorFromSky, r as Renderer3D, s as parseEnvironment3D, t as createGame3D, u as AssetStore3D } from "./create-game-OyjhYUL3.js";
3
+ import { a as Environment3D, c as sunDirectionFromElevationAzimuth, i as syncTree, l as sunDirectionFromSky, o as horizonColorFromSky, r as Renderer3D, s as parseEnvironment3D, t as createGame3D, u as AssetStore3D } from "./create-game-DjWIgtQ_.js";
4
4
  import { A as QUARTER_PITCH, C as BoneAttachment3D, D as TERRAIN_THEMES, E as DEFAULT_TERRAIN_TEXTURE_BASE, M as keyboardIntensity, N as movementState, O as terrainThemeLayers, P as rigPose, S as BoneLookAt3D, T as Terrain3D, _ as Flowers3D, a as Trail3D, b as CharacterController3D, c as Particles3D, d as DirectionalLight3D, f as OmniLight3D, g as DENSITY_PRESETS, h as Foliage3D, i as Tree3D, j as cameraRelative, k as Joint3D, l as ModelInstance3D, m as MeshInstance3D, n as VOXEL_PALETTE, o as River3D, p as InstancedMesh3D, r as VoxelGrid3D, s as traceDownhillPath, t as registerNodes3D, u as LoftMesh3D, v as resolveFlowerDensity, w as Billboard3D, x as Camera3D, y as FLOWER_VARIETIES } from "./register-CpJORtm4.js";
5
5
  import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
6
6
  import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-CpIB9C-F.js";
@@ -126,6 +126,50 @@ var AssetStore3D = class {
126
126
  }
127
127
  };
128
128
  //#endregion
129
+ //#region src/3d/adaptive-resolution.ts
130
+ const DEFAULTS = {
131
+ slowMs: 20,
132
+ fastMs: 13,
133
+ minScale: .6,
134
+ step: .15,
135
+ window: 45
136
+ };
137
+ var AdaptiveResolution = class {
138
+ opts;
139
+ samples = [];
140
+ scale = 1;
141
+ constructor(options = {}) {
142
+ this.opts = {
143
+ ...DEFAULTS,
144
+ ...options
145
+ };
146
+ }
147
+ /** The fraction of the requested pixel ratio to render at right now. */
148
+ current() {
149
+ return this.scale;
150
+ }
151
+ /**
152
+ * Feed one frame's duration. Returns the new scale when it CHANGED (the
153
+ * caller then resizes), else null — so a steady frame rate costs nothing.
154
+ */
155
+ push(frameMs) {
156
+ if (!(frameMs > 0) || !Number.isFinite(frameMs)) return null;
157
+ this.samples.push(frameMs);
158
+ if (this.samples.length < this.opts.window) return null;
159
+ const sorted = [...this.samples].sort((a, b) => a - b);
160
+ const median = sorted[Math.floor(sorted.length / 2)];
161
+ this.samples = [];
162
+ const before = this.scale;
163
+ if (median > this.opts.slowMs) this.scale = Math.max(this.opts.minScale, this.scale - this.opts.step);
164
+ else if (median < this.opts.fastMs && this.scale < 1) this.scale = Math.min(1, this.scale + this.opts.step);
165
+ return this.scale === before ? null : this.scale;
166
+ }
167
+ /** Forget the window (a paused or backgrounded game is not "slow"). */
168
+ reset() {
169
+ this.samples = [];
170
+ }
171
+ };
172
+ //#endregion
129
173
  //#region src/3d/bloom/bloom-composite.ts
130
174
  /**
131
175
  * HDR bloom post pass:
@@ -1421,7 +1465,9 @@ var Renderer3D = class {
1421
1465
  canvas: opts.canvas,
1422
1466
  antialias: rendering.antialias
1423
1467
  });
1468
+ this.basePixelRatio = rendering.pixelRatio;
1424
1469
  this.webgl.setPixelRatio(rendering.pixelRatio);
1470
+ this.adaptive = opts.adaptiveResolution === false ? null : new AdaptiveResolution();
1425
1471
  this.webgl.shadowMap.enabled = true;
1426
1472
  this.webgl.shadowMap.type = PCFShadowMap;
1427
1473
  this.webgl.toneMapping = ACESFilmicToneMapping;
@@ -1445,6 +1491,10 @@ var Renderer3D = class {
1445
1491
  this.threeScene.add(this.selectionLines);
1446
1492
  this.disconnect = this.engine.updated.connect(() => this.render());
1447
1493
  }
1494
+ /** The pixel ratio the scene ASKED for — the governor scales this, never replaces it. */
1495
+ basePixelRatio;
1496
+ adaptive;
1497
+ lastFrameAt = 0;
1448
1498
  debugLines;
1449
1499
  selectionLines;
1450
1500
  syncSelectionOutline() {
@@ -1468,9 +1518,35 @@ var Renderer3D = class {
1468
1518
  this.debugLines.visible = vertices !== null;
1469
1519
  if (vertices) this.debugLines.geometry.setAttribute("position", new BufferAttribute(vertices, 3));
1470
1520
  }
1521
+ /**
1522
+ * Frame-rate governor: measure the frame, and when the median of a window is
1523
+ * slow, render the same world at fewer pixels (and hand them back when it
1524
+ * recovers). Resizing the drawing buffer is all it takes — every render
1525
+ * target in the pipeline is keyed off `getDrawingBufferSize`, so the water
1526
+ * grab, the mirror and the bloom chain follow automatically.
1527
+ */
1528
+ governResolution() {
1529
+ const gov = this.adaptive;
1530
+ if (!gov) return;
1531
+ const now = globalThis.performance?.now?.() ?? 0;
1532
+ const prev = this.lastFrameAt;
1533
+ this.lastFrameAt = now;
1534
+ if (prev === 0) return;
1535
+ const frameMs = now - prev;
1536
+ if (frameMs > 500) {
1537
+ gov.reset();
1538
+ return;
1539
+ }
1540
+ const scale = gov.push(frameMs);
1541
+ if (scale === null) return;
1542
+ this.webgl.setPixelRatio(this.basePixelRatio * scale);
1543
+ const size = this.lastSize;
1544
+ if (size.w > 0 && size.h > 0) this.webgl.setSize(size.w, size.h, false);
1545
+ }
1471
1546
  render() {
1472
1547
  const scene = this.engine.scene;
1473
1548
  if (!scene) return;
1549
+ this.governResolution();
1474
1550
  this.syncDebugLines();
1475
1551
  this.syncSelectionOutline();
1476
1552
  if (scene.assets && !this.loadedAssetScenes.has(scene)) {
@@ -1937,7 +2013,8 @@ async function createGame3D(opts) {
1937
2013
  const renderer = opts._rendererFactory ? opts._rendererFactory(engine, opts.canvas) : new Renderer3D({
1938
2014
  canvas: opts.canvas,
1939
2015
  engine,
1940
- ...opts.pixelRatio !== void 0 ? { pixelRatio: opts.pixelRatio } : {}
2016
+ ...opts.pixelRatio !== void 0 ? { pixelRatio: opts.pixelRatio } : {},
2017
+ ...opts.adaptiveResolution !== void 0 ? { adaptiveResolution: opts.adaptiveResolution } : {}
1941
2018
  });
1942
2019
  if (opts.debug ?? false) {
1943
2020
  const host = opts.touchContainer ?? opts.canvas.parentElement ?? (typeof document !== "undefined" ? document.body : null);
package/dist/index.js CHANGED
@@ -295,6 +295,6 @@ function newUid() {
295
295
  //#endregion
296
296
  //#region src/index.ts
297
297
  /** Engine version. Kept in sync with package.json by the release pipeline. */
298
- const VERSION = "0.23.0";
298
+ const VERSION = "0.24.0";
299
299
  //#endregion
300
300
  export { AudioBuses, AudioPlayer, Behavior, CONST_REF_KEY, Engine, HudLayer, IncantoError, InputMap, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, Scene, SceneTree, SfxEngine, Signal, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiText, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, startRecording, synthSfx };
package/dist/react.js CHANGED
@@ -156,7 +156,7 @@ function IncantoCanvas(props) {
156
156
  pointer: latest.pointer,
157
157
  ...keyboard !== void 0 ? { keyboard } : {}
158
158
  };
159
- const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-OyjhYUL3.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-DrcEgRup.js").then((n) => n.n)).createGame2D(o)))(opts);
159
+ const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-DjWIgtQ_.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-DrcEgRup.js").then((n) => n.n)).createGame2D(o)))(opts);
160
160
  if (disposed) {
161
161
  next.dispose();
162
162
  return;
@@ -1 +1 @@
1
- import{t as e}from"./index-D-bWhSDj.js";async function t(t){return new n((await e(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,t)}var n=class{raw;active=new Map;reconnecting=!1;disposed=!1;constructor(e,t){this.raw=new e({...t})}get account(){return this.raw.account}get connected(){return this.raw.connected}connect(){return this.raw.connected?Promise.resolve(!0):(this.disposed=!1,this.rawConnect())}rawConnect(){return this.raw.connect({onDisconnect:()=>void this.reconnect()})}disconnect(){this.disposed=!0;for(let e of this.active.values())e.off();return this.active.clear(),this.raw.disconnect()}remoteFunction(e,t,n){return this.raw.remoteFunction(e,t,n)}track(e){let t=Symbol(`sub`),n={make:e,off:e()};return this.active.set(t,n),()=>{n.off(),this.active.delete(t)}}async reconnect(){if(!this.disposed&&!this.reconnecting){this.reconnecting=!0;try{await this.rawConnect();for(let e of this.active.values())e.off(),e.off=e.make()}finally{this.reconnecting=!1}}}subscribeRoomState(e,t){return this.track(()=>this.raw.subscribeRoomState(e,t))}subscribeRoomMyState(e,t){return this.track(()=>this.raw.subscribeRoomMyState(e,t))}subscribeRoomAllUserStates(e,t){return this.track(()=>this.raw.subscribeRoomAllUserStates(e,e=>{let n={};for(let t of e??[]){if(!t||typeof t.account!=`string`||t.__leaved)continue;let{account:e,__updated:r,__leaved:i,...a}=t;n[e]=a}t(n)}))}subscribeRoomCollection(e,t,n){return this.track(()=>this.raw.subscribeRoomCollection(e,t,({items:e})=>{let t={};for(let n of e??[])n&&typeof n.__id==`string`&&(t[n.__id]=n);n(t)}))}onRoomMessage(e,t,n){return this.track(()=>this.raw.onRoomMessage(e,t,n))}onRoomUserJoin(e,t){return this.track(()=>this.raw.onRoomUserJoin(e,t))}onRoomUserLeave(e,t){return this.track(()=>this.raw.onRoomUserLeave(e,t))}subscribeGlobalState(e){return this.track(()=>this.raw.subscribeGlobalState(e))}subscribeGlobalMyState(e){return this.track(()=>this.raw.subscribeGlobalMyState(e))}subscribeGlobalUserState(e,t){return this.track(()=>this.raw.subscribeGlobalUserState(e,t))}subscribeGlobalCollection(e,t){return this.track(()=>this.raw.subscribeGlobalCollection(e,({items:e})=>{let n={};for(let t of e??[])t&&typeof t.__id==`string`&&(n[t.__id]=t);t(n)}))}subscribeAsset(e,t){return this.track(()=>this.raw.subscribeAsset(e,t))}onGlobalMessage(e,t){return this.track(()=>this.raw.onGlobalMessage(e,t))}};export{t as createAgent8Server};
1
+ import{t as e}from"./index-B5y_MdYb.js";async function t(t){return new n((await e(()=>import(`./GameServer-C56iOUgF.js`),[],import.meta.url)).GameServer,t)}var n=class{raw;active=new Map;reconnecting=!1;disposed=!1;constructor(e,t){this.raw=new e({...t})}get account(){return this.raw.account}get connected(){return this.raw.connected}connect(){return this.raw.connected?Promise.resolve(!0):(this.disposed=!1,this.rawConnect())}rawConnect(){return this.raw.connect({onDisconnect:()=>void this.reconnect()})}disconnect(){this.disposed=!0;for(let e of this.active.values())e.off();return this.active.clear(),this.raw.disconnect()}remoteFunction(e,t,n){return this.raw.remoteFunction(e,t,n)}track(e){let t=Symbol(`sub`),n={make:e,off:e()};return this.active.set(t,n),()=>{n.off(),this.active.delete(t)}}async reconnect(){if(!this.disposed&&!this.reconnecting){this.reconnecting=!0;try{await this.rawConnect();for(let e of this.active.values())e.off(),e.off=e.make()}finally{this.reconnecting=!1}}}subscribeRoomState(e,t){return this.track(()=>this.raw.subscribeRoomState(e,t))}subscribeRoomMyState(e,t){return this.track(()=>this.raw.subscribeRoomMyState(e,t))}subscribeRoomAllUserStates(e,t){return this.track(()=>this.raw.subscribeRoomAllUserStates(e,e=>{let n={};for(let t of e??[]){if(!t||typeof t.account!=`string`||t.__leaved)continue;let{account:e,__updated:r,__leaved:i,...a}=t;n[e]=a}t(n)}))}subscribeRoomCollection(e,t,n){return this.track(()=>this.raw.subscribeRoomCollection(e,t,({items:e})=>{let t={};for(let n of e??[])n&&typeof n.__id==`string`&&(t[n.__id]=n);n(t)}))}onRoomMessage(e,t,n){return this.track(()=>this.raw.onRoomMessage(e,t,n))}onRoomUserJoin(e,t){return this.track(()=>this.raw.onRoomUserJoin(e,t))}onRoomUserLeave(e,t){return this.track(()=>this.raw.onRoomUserLeave(e,t))}subscribeGlobalState(e){return this.track(()=>this.raw.subscribeGlobalState(e))}subscribeGlobalMyState(e){return this.track(()=>this.raw.subscribeGlobalMyState(e))}subscribeGlobalUserState(e,t){return this.track(()=>this.raw.subscribeGlobalUserState(e,t))}subscribeGlobalCollection(e,t){return this.track(()=>this.raw.subscribeGlobalCollection(e,({items:e})=>{let n={};for(let t of e??[])t&&typeof t.__id==`string`&&(n[t.__id]=t);t(n)}))}subscribeAsset(e,t){return this.track(()=>this.raw.subscribeAsset(e,t))}onGlobalMessage(e,t){return this.track(()=>this.raw.onGlobalMessage(e,t))}};export{t as createAgent8Server};