@spawnite/schema 0.1.0-beta.16

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/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import{n as e}from"./3cnTQfiz.js";import*as t from"./zod.js";import*as n from"./z.js";import{describe as r}from"zod/v4/core";var i=/* @__PURE__ */ function(e){return e.TreeRound=`treeRound`,e.TreeTall=`treeTall`,e.BushRound=`bushRound`,e.RockBoulder=`rockBoulder`,e.RockFlat=`rockFlat`,e.GrassTuft=`grassTuft`,e}({}),a=/* @__PURE__ */ function(e){return e.Tree=`tree`,e.Bush=`bush`,e.Rock=`rock`,e.Tuft=`tuft`,e}({}),o=/* @__PURE__ */ function(e){return e.Mesh=`mesh`,e.Trunk=`trunk`,e}({}),s={treeRound:{family:`tree`,minimumHeight:4,maximumHeight:6,tilt:.05,collision:`trunk`},treeTall:{family:`tree`,minimumHeight:5,maximumHeight:7.5,tilt:.05,collision:`trunk`},bushRound:{family:`bush`,minimumHeight:.8,maximumHeight:1.3,tilt:.08},rockBoulder:{family:`rock`,minimumHeight:.8,maximumHeight:1.8,tilt:.35,collision:`mesh`},rockFlat:{family:`rock`,minimumHeight:.4,maximumHeight:.75,tilt:.3,collision:`mesh`},grassTuft:{family:`tuft`,minimumHeight:.35,maximumHeight:.6,tilt:.15}},c=t.string().check(t.regex(/^[a-z][a-z0-9-]*$/,`a material's name is lowercase, such as dry-grass`),r(`A ground material: a built-in such as grass, or one the game's materials add.`)),l=[`stylised`,`photographed`,`flat`],u=t.string().check(t.regex(/^#[0-9a-fA-F]{6}$/,`a colour is written #rrggbb`)),d=/* @__PURE__ */ function(e){return e.Grass=`grass`,e}({}),f=t.object({kind:t._default(t.enum(d),`grass`).check(r(`What grows: grass blades.`)),density:t.optional(t.number().check(t.gte(0),t.lte(2))).check(r(`How thick it grows, 0 none to 2 double; 1 when left out. The scatter brush's grass multiplier scales it per cell, up to 2.`)),height:t.optional(t.number().check(t.positive(),t.lte(2))).check(r(`Metres a blade stands at its tallest; 0.32 when left out.`)),colour:t.optional(u).check(r(`The blades' colour, #rrggbb; the material's swatch, tinted as the material is, when left out.`)),sway:t.optional(t.boolean()).check(r(`Whether the blades sway in the wind; true when left out. The player's Grass sway setting and the quality level can still hold them still.`))}).check(r(`What the material grows on its own, as Unreal's Landscape Grass Type grows from a layer: paint the material and the cover grows.`)),p=t.object({from:t.optional(c).check(r(`The material a new one starts from; its textures and settings, then this entry's.`)),texture:t.optional(t.string()).check(r(`A colour texture from the game's public folder, in place of the material's own.`)),tint:t.optional(u).check(r(`Multiplies the texture's colour, #rrggbb.`)),tintAmount:t.optional(t.number().check(t.gte(0),t.lte(1))).check(r(`How much of the tint applies, 0 to 1: 0 leaves the texture as is; 1 when left out.`)),tile:t.optional(t.number().check(t.positive())).check(r(`Metres one repeat of the texture covers.`)),bumps:t.optional(t.number().check(t.gte(0),t.lte(2))).check(r(`How strongly the texture's height and normal shade the ground, 0 to 2; 1 as drawn.`)),sharpness:t.optional(t.number().check(t.gte(4),t.lte(64))).check(r(`How sharp the seam is where the material meets another, 4 soft to 64 crisp.`)),hexTiling:t.optional(t.boolean()).check(r(`Breaks up the texture's repeat with hexagonal tiles, for a texture that repeats badly; costs three samples.`)),rotate:t.optional(t.boolean()).check(r(`Turns the texture a random way in each cell, which hides its repeat; false for a texture with a direction, such as sand's ripples.`)),wet:t.optional(t.boolean()).check(r(`Rain ripples play on the material.`)),cover:t.optional(f).check(r(`What grows on the material: grass on the built-in grass and dry grass. A density of 0 grows none.`)),animate:t.optional(t.object({scroll:t.optional(t.array(t.number()).check(t.length(2))).check(r(`Repeats a second the texture moves along x and z, as a flowing lava.`)),frames:t.optional(t.int().check(t.gte(2),t.lte(64))).check(r(`Frames stacked down the texture, played in turn.`)),frameTime:t.optional(t.number().check(t.positive())).check(r(`Seconds each frame shows; 0.1 without it.`))})).check(r(`How the material moves: a scroll, frames, or both. It fades out with distance from the camera.`))}).check(r(`A change to a material's look, or a new material made from one.`)),m=t.readonly(t.record(c,p)).check(r(`Changes to a material's look by its name, as Roblox's MaterialVariant does: a built-in's name changes it, a new name with from adds one.`)),ee=`materials`,te=t.object({set:t._default(t.enum(l),`stylised`).check(r(`The engine's stylised textures, its photographed ones, or flat: each material its plain colour and no textures, for a game that draws its own ground surface.`)),materials:t._default(m,{})}).check(r(`The materials every map of the game shares.`)),ne=t.object({material:t.optional(c).check(r(`The cell's material where the rule matches.`)),overlay:t.optional(c).check(r(`A second material mixed over the cell's, by blend.`)),blend:t.optional(t.number().check(t.gte(0),t.lte(1))).check(r(`How much of the overlay shows, 0 to 1.`)),above:t.optional(t.number()).check(r(`Matches ground higher than this, in metres.`)),below:t.optional(t.number()).check(r(`Matches ground lower than this, in metres.`)),steeperThan:t.optional(t.number().check(t.gte(0),t.lte(90))).check(r(`Matches ground steeper than this, in degrees.`)),flatterThan:t.optional(t.number().check(t.gte(0),t.lte(90))).check(r(`Matches ground flatter than this, in degrees.`)),region:t.optional(t.string()).check(r(`Matches inside the named region, where its weight is over one half.`)),noise:t.optional(t.object({scale:t.number().check(t.positive(),r(`Metres across one patch.`)),above:t.number().check(t.gte(-1),t.lte(1),r(`Matches where the noise, -1 to 1, is higher; 0 covers about half.`))})).check(r(`Matches in seeded patches, to break up an area.`)),deeperThan:t.optional(t.number().check(t.nonnegative())).check(r(`Matches under water deeper than this, in metres.`)),shallowerThan:t.optional(t.number().check(t.nonnegative())).check(r(`Matches under water shallower than this, in metres.`)),shoreWithin:t.optional(t.number().check(t.nonnegative())).check(r(`Matches dry ground within this many metres of water.`))}).check(t.superRefine((e,t)=>{e.material===void 0==(e.overlay===void 0)?t.addIssue({code:`custom`,message:`sets one of material or overlay`}):e.overlay!==void 0&&e.blend===void 0&&t.addIssue({code:`custom`,message:`an overlay rule needs its blend, 0 to 1`})}),r(`One rule: every condition it gives must hold, and the first rule that matches sets the cell's material, or its overlay.`)),re=t.object({base:t._default(c,`grass`).check(r(`The material where nothing else applies.`)),steep:t._default(c,`rock`).check(r(`The material of ground steeper than steepAbove.`)),steepAbove:t._default(t.number().check(t.gte(0),t.lte(90)),35).check(r(`Degrees above which ground takes the steep material.`)),rules:t._default(t.readonly(t.array(ne)),[]).check(r(`Rules checked in order before steep, the first match winning, as Minecraft's surface rules are.`))}).check(r(`How each cell picks its material: its paint, a path's surface, the first matching rule, steep, a region's material, then base.`)),ie=t.pipe(t.array(t.number()).check(t.length(3)),t.tuple([t.number(),t.number(),t.number()])),ae=t.readonly(t.object({amplitude:t.number().check(t.nonnegative(),r(`Metres the hills reach up from the base height; they never dip below it.`)),wavelength:t.number().check(t.positive(),r(`Metres from one hill to the next.`)),detail:t.number().check(t.gte(0),t.lte(1),r(`The second octave's share of the first.`))})).check(r(`One set of hills: the noise's height, spacing and detail.`)),oe=1e6,h=t.number().check(t.gte(0),t.lte(2),r(`How dense the kind stands, 0 to 2: 0 leaves it out, and a family's kinds sum to its density, where 1 is realm's meadow and above 1 the grid is the ceiling, so only a rock's slope favour still grows.`)),se=t.object({treeRound:h,treeTall:h,bushRound:h,rockBoulder:h,rockFlat:h,grassTuft:h,seedOffset:t.int().check(t.gte(-1e6),t.lte(oe),r(`Hashed into this section's draws beside the map seed, so two sections at one density do not stand the same way; bounded as the seed is.`))}),ce=t.readonly(se).check(r(`What stands on the ground here: a density per kind.`)),le=t.readonly(t.extend(se,{clearing:t.number().check(t.nonnegative(),r(`Metres around the region's centre kept bare of everything; 0 for none.`))})).check(r(`The region's own scatter, and the circle it keeps bare.`)),ue=t.readonly(t.tuple([t.number(),t.number()])).check(r(`A point on the ground, [x, z] in metres.`)),g=t.readonly(ie).check(r(`A point in the world, [x, y, z] in metres.`)),de=t.readonly(t.object({eye:g.check(r(`Where the camera stands.`)),target:g.check(r(`What it looks at.`)),fov:t._default(t.number().check(t.positive(),t.lte(170)),50).check(r(`Vertical field of view in degrees; ignored when ortho is set.`)),ortho:t.optional(t.number().check(t.positive())).check(r(`Metres the view spans top to bottom when it is orthographic; left out for a perspective view.`))})).check(r(`A bookmarked camera: a shot an agent retakes after each edit.`)),fe=/* @__PURE__ */ function(e){return e.Box=`box`,e.Sphere=`sphere`,e.Cylinder=`cylinder`,e.Plane=`plane`,e}({}),pe={position:t.union([ue,g]).check(r(`[x, z] stands it on the ground; [x, y, z] holds it at that height.`)),yaw:t._default(t.number(),0).check(r(`Degrees about the vertical axis.`)),scale:t._default(t.union([t.number().check(t.positive()),g]),1).check(r(`Times its size: one number, or one per axis.`))},me=t.readonly(t.union([t.strictObject({kind:t.enum(fe),size:t._default(g,[1,1,1]).check(r(`Metres across, tall and deep.`)),color:t._default(t.string(),`#9aa0a6`),...pe}),t.strictObject({kind:t.enum(i),...pe,scale:t._default(t.number().check(t.positive()),1).check(r(`Times its height; the scatter scales a kind evenly.`))}),t.strictObject({model:t.string().check(t.minLength(1),r(`A registered model's name, or a path the game serves from the page's folder, such as assets/statue.glb.`)),...pe})])).check(r(`An object at a written spot: a shape, a scatter kind or a model.`)),_=t.string().check(t.minLength(1),t.refine(e=>!/^\d+$/.test(e),`a name is not a number`),t.refine(e=>e!==`__proto__`,`a name is not __proto__, which a JSON import reads as the prototype`),r(`The name an entry is known by.`)),he=t.readonly(t.record(_,me)),ge=t.number(),v={seed:t.int().check(t.gte(-1e6),t.lte(oe),r(`Seeds the noise the hills are generated from and every scatter draw; within a million either way, inside the 32 bits the scatter's hash reads a key in.`)),size:t.number().check(t.positive(),r(`Metres across, centred on the origin.`)),cell:t.number().check(t.positive(),r(`Metres between height samples: the vertex spacing.`)),hills:ae.check(r(`The hills outside every region.`)),scatter:ce.check(r(`The scatter outside every region.`)),regions:t.readonly(t.record(_,t.readonly(t.object({center:ue,radius:t.number().check(t.positive(),r(`Metres of ground with its own hills.`)),fade:t.number().check(t.positive(),r(`Metres beyond the radius over which the region blends into its neighbours.`)),hills:ae,scatter:le,material:t.optional(c).check(r(`The ground's material where the region's weight is over one half.`))})))).check(r(`Discs with their own hills and scatter, by name, blended in table order: a later region paints over an earlier one where they overlap.`)),paths:t.readonly(t.record(_,t.readonly(t.object({halfWidth:t.number().check(t.positive(),r(`Metres either side of the curve that are held flat.`)),fade:t.number().check(t.positive(),r(`Metres beyond the half width over which the hills return.`)),points:t.readonly(t.array(ue).check(t.minLength(2))).check(r(`Waypoints joined by a Catmull-Rom curve.`)),surface:t.optional(t.enum([`dirt`,`stone`,`sand`])).check(r(`What the ground shows inside the half width, blended out over the fade; none leaves the ground as it is.`))})))).check(r(`Flat lanes through the hills, by name, each following the broad octave along its length.`)),views:t._default(t.readonly(t.record(_,de)),{}).check(r(`Named cameras, by name: where a screenshot looks from when asked for the view by name. A concept-art match is one of these.`)),props:t._default(he,{}).check(r(`Objects that stand at a written spot, by name: a shape, a scatter kind or a model.`)),rim:t.number().check(t.nonnegative(),r(`Metres from the edge over which the ground rises into a bank; 0 for none.`)),ground:t._default(re,{base:`grass`,steep:`rock`,steepAbove:35,rules:[]}),materials:t._default(m,{}).check(r(`This map's changes to a material's look, over the game's src/maps/materials.json.`)),revision:t.optional(t.int().check(t.nonnegative())).check(r(`Counts the writes the map editor and spawnite map make to this file, so a write made against an older file is refused rather than lost. Left out before the first.`)),sea:t.optional(t.number()).check(r(`The height in metres of the map's one sea: the ground below it is under water, and the coast is where the hills rise out of it. Left out, the sea stands 1 m below the lowest point of the map's base, so the map is dry until the ground is dug below it.`))},_e=t.readonly(t.object({seed:v.seed,hills:v.hills,regions:v.regions,paths:v.paths,rim:v.rim})).check(r(`A copy of the map's seed, hills, regions, paths and rim, recorded at each write of its terrain block.`)),ve=`.`,ye=/^(\.|-?\d+(\.\d+)?(e[+-]?\d+)?)$/,be=t.object({size:t.number().check(t.positive(),r(`The map's size when the block was written, so a resample finds the old grid.`)),cell:t.number().check(t.positive(),r(`The map's cell when the block was written.`)),base:_e,height:t.optional(t.array(t.string())).check(r(`Metres added to the base at each point: size / cell + 1 rows, north to south, of as many values separated by spaces, a dot where no brush touched.`)),palette:t.optional(t.array(c)).check(r(`The materials the paint and overlay grids index.`)),paint:t.optional(t.array(t.string())).check(r(`Each cell's painted material, an index into palette: size / cell rows of as many values, a dot where the rules decide.`)),overlay:t.optional(t.array(t.string())).check(r(`Each cell's painted overlay, an index into palette, mixed over its material by blend.`)),blend:t.optional(t.array(t.string())).check(r(`How much of each cell's overlay shows, 0 to 1.`)),scatter:t.optional(t.partialRecord(t.enum(a),t.array(t.string()))).check(r(`Each scatter family's multiplier of the map's density, per cell, from 0, none, to 2, double: size / cell rows of as many values, a dot where no brush touched.`)),erased:t.optional(t.partialRecord(t.enum(a),t.array(t.tuple([t.number(),t.number()])))).check(r(`Each scatter family's instances the Erase brush or Remove this took out, each by its spot, its exact [x, z] in metres, which names it; a More stroke over the spot grows it back.`))}).check(t.superRefine((e,t)=>{let n=Math.round(e.size/e.cell),r=e.palette?.length??0,i=[[`height`,n+1,`numbers`,Number.isFinite],[`paint`,n,`whole numbers below ${r}, the palette's length,`,e=>Number.isInteger(e)&&e>=0&&e<r],[`overlay`,n,`whole numbers below ${r}, the palette's length,`,e=>Number.isInteger(e)&&e>=0&&e<r],[`blend`,n,`numbers from 0 to 1`,e=>e>=0&&e<=1]],a=[n,`numbers from 0 to 2`,e=>e>=0&&e<=2],o=[...i.map(([t,...n])=>[[t],e[t],...n]),...Object.entries(e.scatter??{}).map(([e,t])=>[[`scatter`,e],t,...a])];for(let[n,r,i,a,s]of o)r!==void 0&&(r.length!==i&&t.addIssue({code:`custom`,path:[...n],message:`has ${r.length} rows; a ${e.size} m map at ${e.cell} m cells has ${i}`}),r.forEach((e,r)=>{let o=e.split(` `);(o.length!==i||!o.every(e=>e===`.`||ye.test(e)&&s(Number(e))))&&t.addIssue({code:`custom`,path:[...n,r],message:`needs ${i} ${a} or dots`})}));e.overlay===void 0!=(e.blend===void 0)&&t.addIssue({code:`custom`,path:[e.overlay?`blend`:`overlay`],message:`overlay and blend are written together`})})).check(r(`What the map editor's brush painted over the map's base, written by the editor and spawnite map terrain rather than by hand; a map without it has no edits.`)),xe=t.readonly(t.object({...v,terrain:t.optional(be)}).check(t.refine(e=>Number.isInteger(e.size/e.cell),{message:`cell must divide size`,path:[`cell`]}),t.superRefine((e,t)=>e.ground.rules.forEach(({region:n},r)=>{n!==void 0&&!(n in e.regions)&&t.addIssue({code:`custom`,path:[`ground`,`rules`,r,`region`],message:`names ${n}, which is not one of the map's regions`})})))).check(r(`One map: the terrain and the flat ground cut into it.`)),Se=t.record(_,t.nullable(me)),Ce=xe.parse({seed:0,size:8,cell:8,hills:{amplitude:0,wavelength:16,detail:0},scatter:{treeRound:0,treeTall:0,bushRound:0,rockBoulder:0,rockFlat:0,grassTuft:0,seedOffset:0},regions:{},paths:{},rim:0,ground:{base:`grid`,steep:`grid`}}),we=xe.parse({seed:139,size:100,cell:1,hills:{amplitude:6,wavelength:40,detail:0},scatter:{treeRound:.4,treeTall:.4,bushRound:.8,rockBoulder:.3,rockFlat:.3,grassTuft:1,seedOffset:0},regions:{meadow:{center:[0,0],radius:20,fade:12,hills:{amplitude:0,wavelength:40,detail:0},scatter:{treeRound:.15,treeTall:.15,bushRound:.5,rockBoulder:.05,rockFlat:.05,grassTuft:1,seedOffset:1,clearing:8}},rollingField:{center:[35,-35],radius:25,fade:15,hills:{amplitude:12,wavelength:25,detail:.35},scatter:{treeRound:.5,treeTall:.5,bushRound:1,rockBoulder:.5,rockFlat:.5,grassTuft:1,seedOffset:2,clearing:0}}},paths:{meadowToRollingField:{halfWidth:1.6,fade:2,points:[[0,0],[12,-10],[25,-25],[40,-40]]}},rim:8}),Te=t.object({x:t.number(),y:t.number(),z:t.number()}),Ee=t.object({current:t.number().check(t.nonnegative()),maximum:t.number().check(t.positive())}),De=t.object({flat:t.optional(t.number()),percent:t.optional(t.number()),more:t.optional(t.number())}),Oe=t.object({base:t.optional(t.number()),modifiers:t.array(t.extend(De,{source:t.string()}))}),ke=t.object({item:t.string(),count:t.int().check(t.positive()),data:t.optional(t.catchall(t.object({modifiers:t.optional(t.record(t.string(),De))}),t.unknown()))}),Ae=t.object({score:t.number().check(t.nonnegative()),seconds:t.number().check(t.nonnegative())});function y(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}var je=/* @__PURE__ */ function(e){return e.Stores=`stores`,e.Kits=`kits`,e.Engine=`engine`,e}({}),Me=`game`,Ne=Object.values(je),Pe=t.object({version:t.int().check(t.positive()),state:t.unknown()});function Fe(e){let t=e.indexOf(`.`);return t<0?[e]:[e.slice(0,t),e.slice(t+1)]}function Ie(e,t){let[n,r]=Fe(t);if(r===void 0)return{value:e[n]};let i=e[n];return i===void 0?{value:void 0}:y(i)?{value:Object.hasOwn(i,r)?i[r]:void 0}:{problem:`the record's ${n} is no record of keys`}}function Le(e,t,n){let[r,i]=Fe(t);if(i===void 0)return{...e,[r]:n};let a=e[r];return{...e,[r]:{...y(a)?a:{},[i]:n}}}function Re(e){let[t]=e.issues,n=t.path.map(String).join(`.`);return`${n?`at ${n}, `:``}${t.message}`}function ze(e,t,n){return{ok:!1,key:e,owner:t,reason:n,message:`The save did not load: ${e?`${t}'s key ${e}`:t} failed, ${n}. Nothing was restored, and the save must not be written over.`}}function Be(e,t){let n=Pe.safeParse(e);if(!n.success)return{reason:`its entry is no { version, state }: ${Re(n.error)}`};let{version:r}=n.data,{state:i}=n.data;if(r>t.version)return{reason:`a newer version wrote it: version ${r}, and this game reads up to version ${t.version}`};for(let e=r+1;e<=t.version;e++){let n=t.migrations?.[e];if(!n)return{reason:`it is at version ${r}, and there is no step to version ${e}`};try{i=n(i)}catch(t){return{reason:`step ${e} threw: ${t instanceof Error?t.message:String(t)}`}}if(i===void 0)return{reason:`step ${e} returned undefined: a step returns the state at version ${e}, such as (old) => ({ ...old, quests: [] })`}}let a=t.schema.safeParse(i);return a.success?{stored:i,read:a.data}:{reason:`its schema refused it, ${Re(a.error)}`}}function Ve(e,t){if(e==null)return{ok:!0,record:{},states:/* @__PURE__ */ new Map};if(!y(e))return ze(``,`the record`,`it is no record of keys`);let n;try{n=structuredClone(e)}catch{return ze(``,`the record`,`it holds a value JSON cannot`)}let r={...n},i=/* @__PURE__ */ new Map;for(let[e,a]of t){let t=Ie(n,e);if(t.problem)return ze(e,a.owner,t.problem);if(t.value===void 0)continue;let o=Be(t.value,a);if(o.reason!==void 0)return ze(e,a.owner,o.reason);r=Le(r,e,{version:a.version,state:o.stored}),i.set(e,o.read)}return{ok:!0,record:r,states:i}}function He(e,t,n){let r=e;for(let[e,i]of t){let t=n.get(e);if(!t)throw Error(`No owner reads the save's key ${e}: the keys are ${[...n.keys()].join(`, `)}.`);r=Le(r,e,{version:t.version,state:i})}return r}function Ue(e){let t={},n=(e,n)=>{let r=Pe.safeParse(n);r.success&&(t[e]=r.data.version)};n(Me,e.game);for(let t of Ne){let r=e[t];if(y(r))for(let[e,i]of Object.entries(r))n(`${t}.${e}`,i)}return t}function We(e,t){return Object.entries(t).some(([t,n])=>Object.hasOwn(e,t)&&e[t]<n)}function Ge(e,t,n){if(!Number.isInteger(t)||t<1)throw Error(`${e}'s save has version ${t}: a version is a whole number from 1, raised when the saved shape changes.`);for(let r of Object.keys(n??{}).map(Number))if(!Number.isInteger(r)||r<2||r>t)throw Error(`${e}'s save has a step ${r}, and its version is ${t}: step n turns version n-1 into n, so the steps run from 2 to the version.`);for(let r=2;r<=t;r++)if(!n?.[r])throw Error(`${e}'s save is at version ${t} and has no step ${r}: write migrations: { ${r}: (old) => new }, which turns a version ${r-1} state into version ${r}.`)}var Ke=t.looseObject({$form:t.int().check(t.positive())}),b=Symbol.for(`spawnite.save.codec`);function qe(e,n){let r=t.superRefine((e,t)=>{try{n(e)}catch(n){t.addIssue({code:`custom`,message:n instanceof Error?n.message:String(n),input:e})}}),i=Ke.check(r),a={name:e,check:r};return Object.assign(i._zod.def,{[b]:a}),i}var Je=16777216,Ye=4194304,Xe=2*Je,Ze=2*Ye,Qe=.75,$e=`.spawnite/saves`,et=`.cleared`,tt=`spawnite-saves-cleared`,nt=`spawnite:start-save`,rt=/* @__PURE__ */ function(e){return e.Character=`character`,e.Stats=`stats`,e.Inventory=`inventory`,e.Resources=`resources`,e.Wallet=`wallet`,e.Levels=`levels`,e.Loot=`loot`,e.Random=`random`,e}({}),it={character:{version:1,schema:t.object({scene:t.optional(t.string()),position:Te,facing:t._default(t.number(),0),health:Ee})},stats:{version:1,schema:t.object({stats:t.record(t.string(),Oe)})},inventory:{version:1,schema:t.object({slots:t.array(t.nullable(ke)),equipment:t.record(t.string(),ke)})},resources:{version:1,schema:t.object({amounts:t.record(t.string(),t.number().check(t.nonnegative()))})},wallet:{version:1,schema:t.object({coins:t.number()})},levels:{version:1,schema:t.object({finished:t.record(t.string(),Ae)})},loot:{version:1,schema:t.object({left:t.record(t.string(),t.int().check(t.nonnegative()))})},random:{version:1,schema:t.tuple([t.int(),t.int(),t.int(),t.int()])}};function at(e){return{...it[e],owner:`the engine's ${e} save`}}var ot=Object.fromEntries(Object.values(rt).map(e=>[e,at(e)])),st=/* @__PURE__ */ function(e){return e.Same=`same`,e.ChangedWithoutVersion=`changed-without-version`,e.VersionLowered=`version-lowered`,e.VersionRaised=`version-raised`,e.Added=`added`,e.Unchecked=`unchecked`,e}({}),ct=t.object({version:t.int(),fingerprint:t.optional(t.nullable(t.object({format:t.int(),hash:t.string()})))});function lt(e,t,n){if(t.length===0)return n?`unchecked`:`added`;let r=Math.max(...t.map(({version:e})=>e));if(e.version<r)return`version-lowered`;if(n)return`unchecked`;if(e.version>r)return`version-raised`;let i=t.filter(({version:e})=>e===r),a=i.filter(({fingerprint:t})=>t?.format===e.fingerprint?.format);return!e.fingerprint||a.length===0?`unchecked`:a.some(({fingerprint:t})=>t?.hash!==e.fingerprint?.hash)?`changed-without-version`:a.length<i.length?`unchecked`:`same`}function ut(e,t){let n=e.filter(e=>e!=null),r=n.some(e=>!y(e));return Object.fromEntries(Object.entries(t).map(([e,t])=>{let i=[],a=r;for(let t of n){if(!y(t)||!Object.hasOwn(t,e))continue;let n=ct.safeParse(t[e]);n.success?i.push(n.data):a=!0}return[e,lt(t,i,a)]}))}function dt(e){if(e!=null)try{let t=JSON.parse(e);return y(t)?t:void 0}catch{return}}function ft(e,t){let n={};for(let r of[e??{},t])for(let[e,t]of Object.entries(r)){let r=ct.safeParse(t);if(!r.success)continue;let i={version:r.data.version,fingerprint:r.data.fingerprint??null},a=n[e];(a===void 0||i.version>a.version||i.version===a.version&&!a.fingerprint)&&(n[e]=i)}return n}function pt(e,t){let n=[],r=[];if(!t||!e)return{failures:n,warnings:r};let i=ut([e],t);for(let[a,o]of Object.entries(t)){let t=i[a],s=e[a]?.version,c=Number.isInteger(s)?s:o.version;t===`version-lowered`?n.push(`${a}: version ${o.version} is below version ${c}, which a published version of this game saved. Players may hold saves at version ${c}, which this version could not read: raise version to ${c+1} and add a step from each version before it.`):t===`changed-without-version`?n.push(`${a}: its schema changed and its version is still ${o.version}. Raise version to ${o.version+1} in its declaration and add migrations: { ${o.version+1}: (old) => new }, which turns a version ${o.version} state into the new shape.`):t===`unchecked`&&r.push(a.startsWith(`stores.`)?`${a}: a persisted store has no schema to compare, so publish checks only its version, as expected; when the store's shape changes, raise its version and add a migrate, which keeps the saves players hold loading.`:`${a}: its schema could not be compared with version ${c}'s, which a published version saved, so the check passed it. If the saved shape changed, raise version to ${c+1} and add a step.`)}return{failures:n,warnings:r}}function mt(e){return[`The new version would not load saves players already hold, so nothing was published:`,...e.map(e=>`- ${e}`),`There is no way to publish past this check: the fix is the version and a step in the save's declaration.`].join(`
2
+ `)}var ht=50,gt=20,_t=[30,60],vt=60,yt=300;function bt(e){return typeof e==`number`&&Number.isInteger(e)&&e>=1&&e<=50}function xt(e){return _t.some(t=>t===e)}function St(e,t){if(typeof e!=`object`||!e||Array.isArray(e))return[`${t} is ${JSON.stringify(e)}: it is an object such as { maxPlayers: 20, sendRate: 30 }, each field optional.`];let n=[];for(let r of Object.keys(e))r!==`maxPlayers`&&r!==`sendRate`&&r!==`rejoinHoldSeconds`&&n.push(`${t} names ${r}, which a room does not read: a room's settings are maxPlayers, sendRate and rejoinHoldSeconds.`);let{maxPlayers:r,sendRate:i,rejoinHoldSeconds:a}=e;return r!==void 0&&!bt(r)&&n.push(`${t} sets maxPlayers to ${JSON.stringify(r)}: a room holds a whole number of players from 1 to 50, or leave it out for 20. For more players, run several rooms.`),i!==void 0&&!xt(i)&&n.push(`${t} sets sendRate to ${JSON.stringify(i)}: a room sends 30 or 60 times a second, or leave it out for 60.`),a!==void 0&&!(typeof a==`number`&&a>0&&a<=300)&&n.push(`${t} sets rejoinHoldSeconds to ${JSON.stringify(a)}: a room holds a dropped player's seat for more than 0 and at most 300 seconds, or leave it out for 15 after a closed page and 30 after a broken connection.`),n}function Ct(e){return{maxPlayers:e?.maxPlayers??20,sendRate:e?.sendRate??60}}var wt=/* @__PURE__ */ new Set([`properties`,`patternProperties`,`$defs`]),Tt=/* @__PURE__ */ new Set([`default`,`const`,`enum`]),Et=/* @__PURE__ */ new Set([`description`,`title`,`examples`,`$schema`]);function Dt(e){return Array.isArray(e)?e.map(Dt):y(e)?Object.fromEntries(Object.keys(e).sort().map(t=>[t,Dt(e[t])])):e}function x(e){return Array.isArray(e)?e.map(x):y(e)?Object.fromEntries(Object.keys(e).filter(e=>!Et.has(e)).sort().map(t=>{let n=e[t];return t===`required`&&Array.isArray(n)?[t,n.map(String).sort()]:Tt.has(t)?[t,Dt(n)]:wt.has(t)&&y(n)?[t,Object.fromEntries(Object.keys(n).sort().map(e=>[e,x(n[e])]))]:[t,x(n)]})):e}var Ot={custom:`a refinement`,overwrite:`a check that rewrites the value`},kt=`~constantCatch`;function At(t){if(t.type===`pipe`)return`a pipe, of which JSON Schema shows one side`;if(t[e])return`a ${t.type} a function computes, which each build computes anew`;if(t.type===`catch`&&!t.catchValue?.[kt])return`a fallback a function computes, which each build computes anew`;let n=t[b]?.check,r=t.checks?.find(e=>e!==n&&Object.hasOwn(Ot,e._zod.def.check));return r&&Ot[r._zod.def.check]}var jt=`#/$defs/`;function Mt(e){if(!y(e)||!y(e.$defs))return e;let t=e.$defs,n=/* @__PURE__ */ new Map,r=e=>{if(Array.isArray(e))return e.forEach(r);if(y(e)){for(let[i,a]of Object.entries(e))if(i!==`$defs`){if(i===`$ref`&&typeof a==`string`&&a.startsWith(jt)){let e=a.slice(8);if(n.has(e))continue;n.set(e,`def${n.size}`),r(t[e])}else r(a)}}};r(e);let i=e=>Array.isArray(e)?e.map(i):y(e)?Object.fromEntries(Object.entries(e).map(([e,t])=>e===`$ref`&&typeof t==`string`&&t.startsWith(jt)?[e,`${jt}${n.get(t.slice(8))}`]:e===`$defs`&&y(t)?[e,Object.fromEntries([...n].sort(([,e],[,t])=>e.localeCompare(t)).map(([e,n])=>[n,i(t[e])]))]:[e,i(t)])):e;return i(e)}function Nt(e){let n;try{let r=t.toJSONSchema(e,{io:`input`,override:({zodSchema:e,jsonSchema:t})=>{let r=e._zod.def;n??=At(r);let i=r[b];i&&(t[`x-save-codec`]=i.name),r.type===`catch`&&(t[`x-save-catch`]=!0),r.coerce&&(t[`x-save-coerce`]=!0)}});return n===void 0?{json:Mt(x(r))}:{unsaid:n}}catch(e){return{unsaid:e.message}}}function Pt(e){let t=Nt(e);if(`unsaid`in t)throw Error(`The saved schema cannot be snapshot whole: it holds ${t.unsaid}. Say the saved form in plain fields, objects, arrays, records, unions and checks JSON Schema keeps, and do any conversion in read and restore.`);return t.json}var Ft=1;function It(e){let t=14695981039346656037n;for(let n=0;n<e.length;n++)t^=BigInt(e.charCodeAt(n)),t=t*1099511628211n&18446744073709551615n;return t.toString(16).padStart(16,`0`)}function Lt(e){let t=Nt(e);return`unsaid`in t?null:{format:1,hash:It(JSON.stringify(t.json))}}function Rt(e){return Object.fromEntries(e.map(({key:e,version:t,schema:n})=>[e,{version:t,fingerprint:Lt(n)}]))}function zt(e){return Object.fromEntries(Object.entries(e).map(([e,{version:t}])=>[e,t]))}function Bt(e){return Array.isArray(e)?[...e].map(String).sort():[]}function Vt(e,t){return JSON.stringify(e)===JSON.stringify(t)}function S(e,t){if(Array.isArray(e)||Array.isArray(t))return Array.isArray(e)&&Array.isArray(t)&&e.length===t.length&&e.every((e,n)=>S(e,t[n]));if(!y(e)||!y(t))return e===t;let{properties:n,required:r,...i}=e,{properties:a,required:o,...s}=t;if(y(n)&&y(a))return!Vt(Bt(r),Bt(o))||!Object.entries(n).every(([e,t])=>Object.hasOwn(a,e)&&S(t,a[e]))?!1:S(i,s);let c=Object.keys(e);return Vt(c.sort(),Object.keys(t).sort())&&c.every(n=>S(e[n],t[n]))}var Ht=/* @__PURE__ */ function(e){return e.Auto=`auto`,e.Minimum=`0`,e.Low=`1`,e.Medium=`2`,e.High=`3`,e}({}),Ut=/* @__PURE__ */ function(e){return e.Shadows=`shadows`,e.PostProcessing=`postProcessing`,e.AmbientOcclusion=`ambientOcclusion`,e.AntiAliasing=`antiAliasing`,e.Foliage=`foliage`,e.Terrain=`terrain`,e}({}),Wt=/* @__PURE__ */ function(e){return e.Software=`software`,e.Touch=`touch`,e.Computer=`computer`,e.GraphicsCard=`graphicsCard`,e.GraphicsCardLargeScreen=`graphicsCardLargeScreen`,e}({}),Gt=t.enum([`0`,`1`,`2`,`3`]),Kt=t.number().check(t.gt(0),t.lte(1)),C=t.catch(t.optional(Gt),void 0),qt=t.object({shadows:C,postProcessing:C,ambientOcclusion:C,antiAliasing:C,foliage:C,terrain:C});function Jt(){return t.catch(t.object({level:t.catch(t.enum(Ht),`auto`),groups:t.catch(qt,{}),renderScale:t.catch(t.optional(Kt),void 0),dynamicResolution:t.catch(t.optional(t.boolean()),void 0),automatic:t.catch(t.optional(t.object({deviceClass:t.enum(Wt),renderer:t.string(),coarsePointer:t.boolean(),screenPixels:t.optional(t.number().check(t.gte(0))),devicePixelRatio:t.number().check(t.gt(0)),level:Gt})),void 0)}),{level:`auto`,groups:{}})}var Yt=/* @__PURE__ */ function(e){return e.Off=`off`,e.Minimal=`minimal`,e.Standard=`standard`,e.Detailed=`detailed`,e}({}),Xt=/* @__PURE__ */ function(e){return e.OpenMic=`openMic`,e.PushToTalk=`pushToTalk`,e}({}),w={masterVolume:100,musicVolume:60,effectsVolume:100,interfaceVolume:100,mouseSensitivity:1,invertLook:!1,fieldOfView:null,cameraShake:100,fullscreenOnPlay:!0,performanceStats:`off`,showPlayerNames:!0,voiceVolume:100,voiceMode:`openMic`,voiceSensitivity:-60,pushToTalkKey:`KeyV`,microphoneVolume:100,noiseSuppression:!0,autoGainControl:!0,shiftLock:!0,reducedMotion:null,grassSway:null,keepReplay:!0,frameRateCap:null},T={masterVolume:t.number(),musicVolume:t.number(),effectsVolume:t.number(),interfaceVolume:t.number(),mouseSensitivity:t.number(),invertLook:t.boolean(),fieldOfView:t.nullable(t.number()),cameraShake:t.number(),fullscreenOnPlay:t.boolean(),performanceStats:t.union([t.enum(Yt),t.pipe(t.boolean(),t.transform(e=>e?`standard`:`off`))]),showPlayerNames:t.boolean(),voiceVolume:t.number(),voiceMode:t.enum(Xt),voiceSensitivity:t.number(),pushToTalkKey:t.string(),microphoneVolume:t.number(),noiseSuppression:t.boolean(),autoGainControl:t.boolean(),shiftLock:t.boolean(),reducedMotion:t.nullable(t.boolean()),grassSway:t.nullable(t.boolean()),keepReplay:t.boolean(),frameRateCap:t.nullable(t.number().check(t.gt(0)))},Zt=t.catch(t.object({masterVolume:t.catch(T.masterVolume,w.masterVolume),musicVolume:t.catch(T.musicVolume,w.musicVolume),effectsVolume:t.catch(T.effectsVolume,w.effectsVolume),interfaceVolume:t.catch(T.interfaceVolume,w.interfaceVolume),mouseSensitivity:t.catch(T.mouseSensitivity,w.mouseSensitivity),invertLook:t.catch(T.invertLook,w.invertLook),fieldOfView:t.catch(T.fieldOfView,w.fieldOfView),cameraShake:t.catch(T.cameraShake,w.cameraShake),fullscreenOnPlay:t.catch(T.fullscreenOnPlay,w.fullscreenOnPlay),performanceStats:t.catch(T.performanceStats,w.performanceStats),showPlayerNames:t.catch(T.showPlayerNames,w.showPlayerNames),voiceVolume:t.catch(T.voiceVolume,w.voiceVolume),voiceMode:t.catch(T.voiceMode,w.voiceMode),voiceSensitivity:t.catch(T.voiceSensitivity,w.voiceSensitivity),pushToTalkKey:t.catch(T.pushToTalkKey,w.pushToTalkKey),microphoneVolume:t.catch(T.microphoneVolume,w.microphoneVolume),noiseSuppression:t.catch(T.noiseSuppression,w.noiseSuppression),autoGainControl:t.catch(T.autoGainControl,w.autoGainControl),shiftLock:t.catch(T.shiftLock,w.shiftLock),reducedMotion:t.catch(T.reducedMotion,w.reducedMotion),grassSway:t.catch(T.grassSway,w.grassSway),keepReplay:t.catch(T.keepReplay,w.keepReplay),frameRateCap:t.catch(T.frameRateCap,w.frameRateCap)}),w),Qt=t.object({muted:t.boolean()}),$t=[`masterVolume`,`voiceVolume`,`voiceMode`,`voiceSensitivity`,`pushToTalkKey`,`microphoneVolume`,`noiseSuppression`,`autoGainControl`,`muted`,`mouseSensitivity`,`invertLook`,`shiftLock`,`reducedMotion`,`cameraShake`,`fullscreenOnPlay`,`performanceStats`,`showPlayerNames`,`frameRateCap`];function E(e){return t.catch(t.optional(e),void 0)}var en=t.catch(t.pipe(t.object({masterVolume:E(T.masterVolume),voiceVolume:E(T.voiceVolume),voiceMode:E(T.voiceMode),voiceSensitivity:E(T.voiceSensitivity),pushToTalkKey:E(T.pushToTalkKey),microphoneVolume:E(T.microphoneVolume),noiseSuppression:E(T.noiseSuppression),autoGainControl:E(T.autoGainControl),muted:E(t.boolean()),mouseSensitivity:E(T.mouseSensitivity),invertLook:E(T.invertLook),shiftLock:E(T.shiftLock),reducedMotion:E(T.reducedMotion),cameraShake:E(T.cameraShake),fullscreenOnPlay:E(T.fullscreenOnPlay),performanceStats:E(T.performanceStats),showPlayerNames:E(T.showPlayerNames),frameRateCap:E(T.frameRateCap)}),t.transform(e=>Object.fromEntries(Object.entries(e).filter(([,e])=>e!==void 0)))),{}),tn=t.enum($t),nn=t._default(t.pipe(t.array(t.unknown()),t.transform(e=>e.filter(e=>tn.safeParse(e).success))),[]);function rn(e,{changed:t,cleared:n}){return{...Object.fromEntries(Object.entries(e).filter(([e])=>!n.some(t=>t===e))),...t}}var an=`https://wiki.spawnite.com`,on=`https://create.spawnite.com`;function sn({kind:e,name:t}){return`wiki/${e}/${t}.md`}function cn(e){let[,t,n]=/^wiki\/([^/]+)\/(.+)\.md$/.exec(e)??[];return t===void 0?void 0:{kind:t,name:n}}function ln(e){return e.replace(/:\w+\[[^\]]*\]/g,``).trim().toLowerCase().replace(/[^\p{L}\p{M}\p{N}\p{Pc} -]/gu,``).replaceAll(` `,`-`)}var un=`wiki/index.md`,dn=t.string().check(t.regex(/^(?!\.)(?![\s\S]*\.\.)[^/\\]+$/,`no / or \\, no .. and no leading dot, so the page stays under wiki/`)),fn={page:t.string().check(r(`A page's address, such as engine/entities/player, or one section of it after a #, such as engine/entities/player#the-jump`)),part:t.optional(t.int().check(t.gte(1))).check(r(`Which part of a page or section too long for one reply; the reply says how many there are. The first by default`))},pn=/* @__PURE__ */ function(e){return e.Guides=`guides`,e.Reference=`reference`,e.All=`all`,e}({}),mn={query:t.string().check(r(`Words to look for, such as an export's name or what a game needs`)),scope:t._default(t.enum(pn),`all`).check(r(`Which pages to search; all of them by default`))},hn={},D=t.string().check(t.regex(/^[A-Z][A-Za-z0-9]*$/,`PascalCase, such as HealthBar`)),O=t.string().check(t.regex(/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/,`lowercase words joined by single dashes, such as boss-fight`)),k=t.optional(t.string()).check(r(`The game's folder; leave it out for the session's game`)),A=t.optional(t.boolean()).check(r(`Refuse what a real call refuses, and return each file's content instead of writing it`)),gn={folder:t.string().check(r(`Where to write the project, made if absent`)),template:t.string().check(r(`The registry template, such as example`)),theme:t.optional(t.string()).check(r(`The look: glass or slate; absent, the template keeps its own, glass for the shipped ones`)),room:t.optional(t.boolean()).check(r(`Play it with friends: run its scene in a local room, for a template that plays alone and can run in one. list_templates says which can; a room game's template runs in its room without it`))},_n={project:k,name:D.check(r(`The behaviour's name, such as Glow`)),system:t.optional(t.boolean()).check(r(`Also write its system, a rule the step runs over every entity with the trait, beside it in src/behaviours/<Name>System.ts, added as the last rule of the game's own plugin, src/rules.plugin.ts, which a room alone runs in a game that has one; for a behaviour that reacts each step, such as a respawn, rather than one that animates in its component`)),dryRun:A},vn=t.enum([`top-left`,`top`,`top-right`,`left`,`center`,`right`,`bottom-left`,`bottom`,`bottom-right`]),yn={project:k,name:D.check(r(`The HUD part's name, such as TargetFrame`)),slot:t.optional(vn).check(r(`Where it stands on the screen; top by default`)),dryRun:A},bn={project:k,name:D.check(r(`The entity's name, such as Coin`)),dryRun:A},xn={project:k,name:D.check(r(`The NPC's name, such as Mira`)),dryRun:A},Sn={project:k,npc:D.check(r(`The NPC that talks, by its component's name, such as Mira: the one at src/npcs/Mira.tsx`)),dryRun:A},Cn={project:k,name:O.check(r(`The scene's name, such as boss-fight`)),dryRun:A},wn={project:k,name:O.check(r(`The map's name, such as blockout: its file under src/maps and what <World map> calls it`)),size:t.optional(t.int().check(t.positive())).check(r(`Metres across, centred on the origin; 64 by default`)),from:t.optional(t.enum([`meadow`])).check(r(`Copy the engine's meadow, hills and scatter included, instead of a flat, empty map`)),dryRun:A},Tn={project:k,name:D.check(r(`The panel's name, such as Character`)),dryRun:A},En={project:k,name:D.check(r(`The shader's name, such as Portal`)),dryRun:A},Dn={},On={project:k,kind:t.optional(dn).check(r(`The entry's kind, as spawnite play wiki names it, such as item or system. Leave kind and name out to write the overview`)),name:t.optional(dn).check(r(`The entry's name, as spawnite play wiki names it, such as sword or entity.move`)),markdown:t.string().check(r(`The whole page: markdown, tables and mermaid fences included`))},kn=O.check(r(`The item's name, as a bag and <Loot item> read it, such as iron-sword`)),An={flat:t.optional(t.number()),percent:t.optional(t.number()),more:t.optional(t.number())},jn=t.strictObject({displayName:t.string().check(t.minLength(1),r(`What a player reads`)),model:t.optional(t.string().check(t.minLength(1))).check(r(`The registered model a Loot and a dropped copy are drawn as; a glowing gem without one`)),color:t.optional(t.string()).check(r(`The gem's colour when no model is named`)),maxStack:t.optional(t.int().check(t.gte(1))).check(r(`The most one slot holds; 1 by default`)),tags:t.optional(t.array(t.string())).check(r(`Words a Lock matches the item by, such as key`)),equip:t.optional(t.object({slot:t.enum([`head`,`body`,`hands`,`feet`,`ring1`,`ring2`,`neck`,`mainhand`,`offhand`,`ring`]),modifiers:t.optional(t.record(t.string(),t.strictObject(An))).check(r(`By stat name, such as { speed: { percent: 0.1 } }`)),abilities:t.optional(t.array(t.string().check(t.minLength(1)))).check(r(`The abilities it grants while worn, by the names registerAbility gives them, such as ["arcane-bolt"]`))})).check(r(`Where it is worn and what it changes while it is`)),use:t.optional(t.object({effects:t.optional(t.array(t.discriminatedUnion(`type`,[t.strictObject({type:t.literal(`heal`),amount:t.number()}),t.strictObject({type:t.literal(`coins`),amount:t.number()}),t.strictObject({type:t.literal(`modify`),stat:t.string().check(t.minLength(1)),...An,seconds:t.optional(t.number().check(t.positive()))})]))).check(r(`Run in order: { type: heal, amount }, { type: coins, amount } or { type: modify, stat, flat?, percent?, seconds? }`)),consume:t.optional(t.boolean()),cooldownSeconds:t.optional(t.number().check(t.nonnegative())),cooldownGroup:t.optional(t.string())})).check(r(`What using it does`))}),Mn={project:k,name:kn,definition:jn.check(r(`The item's definition; the inventory page of the wiki says each field`))},Nn={project:k,scene:O.check(r(`The scene to stand it in, as its <Scene name> gives it, such as run`)),name:O.check(r(`The loot's name: its Entity's row in the devtools tree. A name the scene has is replaced`)),item:kn,count:t.optional(t.int().check(t.gte(1))).check(r(`1 by default`)),radius:t.optional(t.number().check(t.positive())).check(r(`Metres within which a character takes it; 1 by default`)),id:t.optional(O).check(r(`A name unique in the game that keeps it taken across reloads; without one it lies again, whole, at every load`)),position:g.check(r(`[x, y, z] in metres; y is the ground's height there, which map_query reads`))},Pn=`/__map`;function j(e,t){let n=new Float64Array(t*t).fill(NaN);return e?.forEach((e,r)=>e.split(` `).forEach((e,i)=>{e!==`.`&&(n[r*t+i]=Number(e))})),n}function M(e,t){if(!e.every(Number.isNaN))return Array.from({length:t},(n,r)=>Array.from(e.subarray(r*t,(r+1)*t),e=>Number.isNaN(e)?`.`:String(Number(e.toFixed(3)))).join(` `))}function Fn(e){let t=Math.round(e.size/e.cell);return{size:e.size,cell:e.cell,palette:[...e.palette??[]],paint:j(e.paint,t),overlay:j(e.overlay,t),blend:j(e.blend,t),scatter:Object.fromEntries(Object.entries(e.scatter??{}).map(([e,n])=>[e,j(n,t)])),erased:Object.fromEntries(Object.entries(e.erased??{}).map(([t,n])=>{let r=/* @__PURE__ */ new Map;for(let t of n){let n=In(e,t);r.set(n,[...r.get(n)??[],t])}return[t,r]}))}}function In({size:e,cell:t},[n,r]){let i=Math.round(e/t),a=n=>Math.min(Math.max(Math.floor((n+e/2)/t),0),i-1);return a(r)*i+a(n)}function Ln({size:e,cell:t,palette:n,paint:r,overlay:i,blend:a,scatter:o={},erased:s={}}){let c=Math.round(e/t),l=/* @__PURE__ */ new Set;for(let e of[r,i])for(let t of e)Number.isNaN(t)||l.add(t);let u=n.filter((e,t)=>l.has(t)),d=e=>e.map(e=>Number.isNaN(e)?e:u.indexOf(n[e])),f={paint:M(d(r),c),overlay:M(d(i),c),blend:M(a,c)},p=Object.entries(o).flatMap(([e,t])=>{let n=M(t,c);return n?[[e,n]]:[]}),m=Object.entries(s).flatMap(([e,t])=>{let n=[...t.values()].flat().sort(([e,t],[n,r])=>e-n||t-r);return n.length>0?[[e,n]]:[]});return{...u.length>0&&{palette:u},...f.paint&&{paint:f.paint},...f.overlay&&{overlay:f.overlay,blend:f.blend},...p.length>0&&{scatter:Object.fromEntries(p)},...m.length>0&&{erased:Object.fromEntries(m)}}}var Rn=[`sculpt`,`smooth`,`flatten`,`paint`,`scatter`],zn=[`circle`,`square`],Bn=t.pipe(t.array(t.number()).check(t.length(2)),t.tuple([t.number(),t.number()])),N={tool:t.enum(Rn).check(r(`The height tool to run.`)),points:t.array(Bn).check(t.minLength(1),r(`The stroke's points, [x, z] in metres: one for a dab, more for a line, resampled every quarter of the radius.`)),radius:t.number().check(t.positive(),r(`The brush's radius in metres.`)),strength:t.number().check(t.gte(.1),t.lte(1),r(`How strongly each dab works, from 0.1 to 1.`)),shape:t._default(t.enum(zn),`circle`).check(r(`A circle or a square; a circle without it.`)),lower:t._default(t.boolean(),!1).check(r(`Sculpt lowers rather than raises, as Ctrl does.`)),level:t.optional(t.number()).check(r(`Flatten's height in metres; the ground under the stroke's first point without it.`)),material:t.optional(c).check(r(`Paint's material. Strength sets how much of it mixes in as the cell's overlay; at full strength it becomes the cell's material.`)),families:t.optional(t.array(t.enum(a)).check(t.minLength(1))).check(r(`The scatter families a scatter stroke paints: tree, bush, rock or tuft.`)),multiplier:t.optional(t.number().check(t.gte(0),t.lte(2))).check(r(`The multiplier of the map's density a scatter stroke paints toward, from 0, none, to 2, double; strength sets how far each cell moves toward it.`)),erase:t._default(t.boolean(),!1).check(r(`Scatter takes out each instance of its families that stands under the brush, by its spot, in place of painting a multiplier; a stroke over 1 grows them back.`)),clear:t._default(t.boolean(),!1).check(r(`Paint hands each cell back to the map's rules, and scatter hands it back to the map's density and grows back what was erased in it.`))},Vn=t.object(N).check(t.refine(({tool:e,material:t,clear:n})=>e!==`paint`||n||t!==void 0,{message:`paint needs a material, or clear`,path:[`material`]}),t.refine(({tool:e,families:t,multiplier:n,erase:r,clear:i})=>e!==`scatter`||t!==void 0&&(i||r||n!==void 0),{message:`scatter needs a family and a multiplier, erase or clear`,path:[`multiplier`]})),Hn=t.object({radius:N.radius,strength:N.strength,shape:N.shape}),Un=t.object({paint:t.nullable(c),overlay:t.nullable(c),blend:t.nullable(t.number().check(t.gte(0),t.lte(1))),scatter:t.optional(t.partialRecord(t.enum(a),t.number().check(t.gte(0),t.lte(2)))),erased:t.optional(t.partialRecord(t.enum(a),t.array(t.tuple([t.number(),t.number()]))))}),Wn=t.object({points:t.array(t.int().check(t.nonnegative())),height:t.array(t.nullable(t.number())),cells:t._default(t.array(t.int().check(t.nonnegative())),[]),paint:t._default(t.array(Un),[])}),Gn=Bn,P=t.object({material:t.optional(c).check(r(`The material every cell in the area takes.`)),level:t.optional(t.number()).check(r(`The height in metres every point in the area is flattened to.`)),clear:t._default(t.boolean(),!1).check(r(`Hands every cell in the area back to the rules.`)),rect:t.optional(t.array(Gn).check(t.length(2))).check(r(`Two opposite corners, [[x, z], [x, z]] in metres.`)),region:t.optional(t.string()).check(r(`A region of the map, where its weight is over one half.`))}).check(t.superRefine((e,t)=>{[e.material!==void 0,e.level!==void 0,e.clear].filter(Boolean).length!==1&&t.addIssue({code:`custom`,message:`fills one of a material, a level or clear`}),e.rect===void 0==(e.region===void 0)&&t.addIssue({code:`custom`,message:`fills one of a rect or a region`})})),Kn=[`keep-heights`,`move-with-ground`],qn=t.object({map:O,revision:t.int().check(t.nonnegative()),stroke:t.optional(Vn),fill:t.optional(P),restore:t.optional(Wn),settle:t.optional(t.enum(Kn)),material:t.optional(t.object({name:c,look:p})),restoreMaterial:t.optional(t.object({name:c,look:t.nullable(p)})),props:t.optional(Se),sea:t.optional(ge)}),Jn={project:k,tab:t.optional(t.boolean()).check(r(`Left out, attach to the creator's open tab of the game when there is one, else start a headless page; true attaches or fails, false always starts a headless page`)),scene:t.optional(t.string()).check(r(`A scene to go to once the game's start scene has shown, as its <Scene name> gives it, such as run; the reply is that scene's tree. Without it the playtest stays on the start scene`)),save:t.optional(t.string()).check(r(`A save file the headless page starts from, once: the page's save in a game with no rooms, every player's in a room game's room. A reload or a rejoin keeps what was played since, and nothing is written over the file. A path by its path from the game's folder, such as .spawnite/saves/Ada-3f2a9c1e0b7d4a65.json; a new game without it`)),latency:t.optional(t.number().check(t.gte(0),t.lte(1e4))).check(r(`Play the headless page over a slow link to the game's room: the round trip in milliseconds, such as 150, each way holding half. read_engine_state's room line names the link. A room game's headless page alone; direct without it`)),jitter:t.optional(t.number().check(t.gte(0),t.lte(1e4))).check(r(`How much each message's trip varies on that link, in milliseconds, such as 20: most trips land within it of half the round trip, and now and then one runs two or three times it`)),stall:t.optional(t.string()).check(r(`A stall now and then on that link, as <milliseconds>/<every seconds>, such as 500/10: nothing gets through either way for half a second every ten seconds, then everything held lands at once`))},Yn={project:k,accept:t.optional(t.boolean()).check(r(`true keeps the shots as the accepted set in test/shots, once the look is right; left out, compares with that set`))},F=t.array(t.string()),Xn=/* @__PURE__ */ function(e){return e.Top=`top`,e.Bottom=`bottom`,e.Front=`front`,e.Back=`back`,e.Left=`left`,e.Right=`right`,e.ThreeQuarter=`three-quarter`,e.Iso=`iso`,e}({}),Zn=t.union([t.enum(Xn),t.strictObject({azimuth:t.number().check(r(`Degrees round the subject: 0 stands at +z (front), 90 at +x (right)`)),elevation:t.number().check(t.gte(-90),t.lte(90),r(`Degrees above the ground: 90 is straight down`))})]),Qn=t.strictObject({angle:t._default(Zn,`iso`).check(r(`top, bottom, front, back, left or right, orthographic; three-quarter, 45° at eye level, orthographic; iso, 45° from the south-east in perspective; or { azimuth, elevation } in degrees, in perspective`)),around:t._default(t.union([t.string(),g,t.array(g).check(t.minLength(1))]),`all`).check(r(`What the shot frames: all, every prop or the whole map when it has none; a prop, region, path or view by name; a point [x, y, z]; or a list of points`)),zoom:t._default(t.number().check(t.positive()),1).check(r(`2 stands twice as close, or spans half as wide`)),ortho:t.optional(t.boolean()).check(r(`Overrides the angle's own projection`))}).check(r(`A camera framed round a subject`)),$n=t.union([de,Qn]),er=t.optional(t.string()).check(r(`The entity row the AI tree is seen from, by id or name; the player without it`)),tr={ai:t.optional(t.boolean()).check(r(`Also return the AI tree after the steps: each described entity in words`)),subject:er},nr={project:k,until:t.optional(t.string()).check(r(`A JavaScript expression over the world; when given, the world steps until it holds and that step is shot`)),seconds:t.optional(t.number().check(t.positive())).check(r(`Seconds of game time to step: alone, step that long and shoot; with until, the most to step before giving up, 10 by default`)),keys:t.optional(F).check(r(`Keys held down while the world steps toward until, and released after the shot: w walks the character`)),view:t.optional(t.string()).check(r(`A camera from the scene's map, by name: a views entry, a region, a path, or map for the whole ground from above. Without it the shot is the player's view.`)),camera:t.optional($n).check(r(`The camera, in place of view, as the description says`)),map:t.optional(t.string()).check(r(`Shoot this map alone through the ?map= preview, with no character; starts the playtest when none runs. The page stays on the preview afterwards`)),size:t.optional(t.object({width:t.int().check(t.positive()),height:t.int().check(t.positive())})).check(r(`The viewport in pixels for this shot; 960×540 without it.`))},I={project:k,map:t.optional(t.string()).check(r(`The map, as <World map> names it. Left out, the game's one map file, or meadow when it has none; required when it has several`)),file:t.optional(t.string()).check(r(`A module in the project whose default export is the map's values, described instead of the registered one`))},L=t.pipe(t.array(t.number()).check(t.length(2)),t.tuple([t.number(),t.number()])),rr={...I,at:t.optional(L).check(r(`A point on the ground, [x, z] in metres, to read`)),along:t.optional(t.object({from:L,to:L,every:t._default(t.number().check(t.positive()),1).check(r(`Metres between samples, at most; 1 without it`))})).check(r(`A line on the ground whose profile to read, both ends included`))},ir={project:k,map:I.map,tool:t.enum([...Rn,`fill`,`sea`,`keep-heights`,`move-with-ground`,`resample`]).check(r(`sculpt raises, or lowers with lower; smooth evens; flatten levels to level, or the height under the first point; paint lays material under the brush, as the cell's material at full strength and as an overlay below it, or hands cells back to the rules with clear; scatter moves each cell's multiplier of the families' density toward multiplier, 0 none to 2 double, over 1 growing back what was erased, takes out each instance of the families under the brush with erase, or hands it back to the map's density with clear. fill paints material, levels to level, or clears, over rect or region, and ignores the brush. sea sets the map's sea to level in metres and ignores the brush. keep-heights keeps each edited point's height over a base the map changed; move-with-ground keeps each edited point's offset, so it rides on the new base; resample moves the edits onto the map's new grid, and all three ignore the brush`)),at:t.optional(L).check(r(`One dab, [x, z] in metres`)),along:t.optional(t.array(L)).check(r(`A line of points, [x, z] in metres, resampled every quarter of the radius as the editor resamples a drag`)),radius:t._default(N.radius,4).check(r(`The brush's radius in metres; 4 without it`)),strength:t._default(N.strength,.5).check(r(`How strongly each dab works, 0.1 to 1; 0.5 without it`)),shape:N.shape,lower:N.lower,level:t.optional(t.number()).check(r(`Metres: flatten's and fill's height, the ground under the stroke's first point when flatten has none; the sea's height for sea`)),material:N.material,families:N.families,multiplier:N.multiplier,erase:N.erase,clear:N.clear,rect:P.shape.rect,region:P.shape.region},ar={project:k,map:I.map,name:c.check(r(`The material: a built-in such as grass changes it on this map, and a new name with from makes a new one`)),...p.shape},or={project:k,map:t.string().check(r(`The map file's name: src/maps/<name>.json, as add_map wrote it`)),props:t.record(t.string(),t.nullable(t.looseObject({}))).check(r(`Props by name. Each is the map's prop shape: { kind: box|sphere|cylinder|plane, position: [x, z] or [x, y, z], size?, color?, yaw?, scale? }, { kind: a scatter kind such as treeRound, position, yaw?, scale? }, { model: a registered name or a served path, position, yaw?, scale? }, or { asset: an id from list_assets, position, yaw?, scale? }, which downloads it. A name that exists is replaced; null removes it`))},sr={project:k,scene:t.string().check(r(`The scene to run, as its <Scene name> gives it, such as run; its file exports the component of that name`)),seconds:t.optional(t.number().check(t.positive())).check(r(`Seconds of game time to step: alone, step that long; with until, the most to step before giving up, 10 by default`)),until:nr.until,keys:t.optional(F).check(r(`Keys held down while the world steps, through the game's key table: w, a, s, d and the arrows steer the character and Space jumps it once, unless controls() moves them; a declared button or axis is held, and a declared press with a key is sent once. A key nothing binds fails, naming the world's keys`)),profile:t.optional(t.boolean()).check(r(`Time each system of the step and print its milliseconds per step, slowest first`)),fields:t.optional(t.array(t.string())).check(r(`Only these traits, or fields inside one, such as ["transform", "wallet.coins"], and only the entities that hold one, in place of the whole dump`)),save:t.optional(t.string()).check(r(`A save file to start from, as a page loads a player's save, by its path from the game's folder, such as .spawnite/saves/Ada-3f2a9c1e0b7d4a65.json; a new game without it`)),...tr},cr={project:k,url:t.optional(t.string()).check(r(`A page already served to profile instead of the project's build`)),seconds:t._default(t.number().check(t.positive()),20).check(r(`Seconds to record`)),budget:t._default(t.number().check(t.positive()),7.5).check(r(`A frame's budget in milliseconds: framesOver counts the frames that take longer`)),fps:t.optional(t.number().check(t.positive())).check(r(`Pace the page's frames to this rate, such as 120 for a 120 Hz display: each frame runs at the first display tick past its 1/fps s, so beside vsync false any rate is reachable`)),vsync:t._default(t.boolean(),!0).check(r(`False launches Chromium with vsync and its frame-rate cap off, so a frame's time is its cost rather than the display's period`)),cpu:t._default(t.boolean(),!0).check(r(`False leaves V8's sampler off, so the frame times carry none of its cost`)),gpu:t.optional(t.boolean()).check(r(`Time each frame's GPU work with a timer query into gpuMs, read with vsync false alone`)),gpuPasses:t.optional(t.boolean()).check(r(`Time each pass of the GPU's frame into gpuPasses, such as the shadow map, the scene and ambient occlusion, on every other frame, and the whole frame on the others into gpuMs; with vsync false alone`)),scene:t.optional(t.string()).check(r(`Open the game on this registered scene in place of its start scene`)),render:t.optional(t.boolean()).check(r(`Count each frame's draws, triangles and instances into gl, to the screen and into render targets such as the shadow map`)),skip:t.optional(t.enum([`rt`,`rtinstanced`,`instanced`,`skinned`,`rtskinned`,`sky`])).check(r(`Drop one class of draw, whose cost is then the frame time's difference from a run without it`)),alloc:t.optional(t.boolean()).check(r(`Sample the heap's allocations into allocations: the garbage a frame makes by call site`)),trace:t.optional(t.boolean()).check(r(`Record a Chromium trace into trace: garbage collections, paints and raster, the page's Skia programs as skiaPrograms, and long GPU-thread tasks as gpuStalls, each named on the hitch it held back`)),quality:t.optional(t.enum(Object.keys(Ht).map(e=>e.toLowerCase()))).check(r(`Play at this quality level rather than the one the game picks`)),scale:t.optional(t.number().check(t.positive())).check(r(`The page's device scale, such as 2 for a retina laptop`)),keepProfile:t.optional(t.union([t.boolean(),t.string()])).check(r(`Keep the browser's profile between runs, true for one kept for the game, or a folder, so a second run finds the compiled shaders cached; a fresh profile by default`)),browserArgs:t.optional(t.array(t.string())).check(r(`Chromium switches to start the browser with, such as ["--force_low_power_gpu"] to reach a laptop's integrated GPU; the result's renderer names the GPU the page got`)),viewport:t.optional(t.object({width:t.int().check(t.positive()),height:t.int().check(t.positive())})).check(r(`The page's size in CSS pixels, such as { width: 3440, height: 1440 }; 1920 by 1080 when left out`)),until:t.optional(t.string()).check(r(`A JavaScript expression in the page, such as document.body.innerText.includes('Finish!'): the walk plays until it holds, then records; two minutes at most`)),jump:t.optional(t.boolean()).check(r(`False walks without pressing Space, for a game where Space does more than jump`)),drive:t.optional(t.enum([`walk`,`idle`,`bot`])).check(r(`Who plays: walk, the scripted walk, recorded after 7 s of it, by default; idle, nobody, the default with mapCamera; bot, which turns to the nearest living entity with health, fires at it and walks. Idle and the bot record from the loading screen's lift`)),mapCamera:t.optional(t.number().check(t.positive())).check(r(`Record under the devtools' map camera at this zoom of the whole map, as a screenshot's zoom reads it: 3 is where the map camera lands, 6 twice as close. It serves the dev page, which mounts the devtools, and nobody plays unless drive says so`)),hitch:t.optional(t.number().check(t.positive())).check(r(`A frame longer than this many milliseconds is a hitch: 50 by default`)),invulnerable:t.optional(t.boolean()).check(r(`Every player's character in the game's own room takes no damage, so the bot plays on through the run`)),functions:t.optional(t.array(t.string())).check(r(`Functions to time, into timedFunctions: a path on the page's window, such as Worker.prototype.postMessage, is wrapped and each call timed; any other name is read from V8's sampler`)),queries:t.optional(t.array(t.string()).check(t.maxLength(5))).check(r(`Parameters to add to the page's address, one configuration apiece, such as replay=off, and an empty string for none; at most 5. More than one, or a repeat above 1, replies with the sweep: each later configuration compared with the first, or one configuration's runs as a series`)),repeat:t.optional(t.int().check(t.positive(),t.lte(10))).check(r(`Runs of each configuration, taken in turn round after round: at most 10`))},lr=t.object({key:t.string().check(r(`The control, as the testing wiki's Name a control lists them, such as KeyR, Space or MouseLeft; a chord such as Shift+KeyW holds together`)),at:t.number().check(t.nonnegative(),r(`Seconds from the last page's outcome when it goes down`)),until:t.optional(t.number()).check(r(`Seconds from the last page's outcome when it lets go: held from at until then; a tap without it`))}).check(t.refine(({at:e,until:t})=>t===void 0||t>e,`a press's until is later than its at, so it lets go after it goes down`)),ur={project:k,room:t.optional(t.string().check(t.regex(/^wss?:\/\//))).check(r(`The room's WebSocket address, ws:// or wss://, such as a room a teammate runs or a deployed one. Left out, the game's own room, started fresh on a free port as its spawnite.room runs it, and ended with the call`)),pages:t._default(t.int().check(t.positive(),t.lte(12)),1).check(r(`Players' pages to join, one after another, each in a browser context of its own so it joins as a player of its own`)),dev:t.optional(t.boolean()).check(r(`Serve the game's dev server in place of its build: a dev page names no build, so a room of any build admits it. Use it to check a change without a build, not to check the build`)),url:t.optional(t.string()).check(r(`A page already served to open in place of the game's build, such as the deployed game; the room's address goes in its room parameter`)),joinSeconds:t._default(t.number().check(t.positive()),90).check(r(`Seconds each page has to be welcomed or refused, long enough by default for a dev server's first start`)),presses:t.optional(t.array(lr)).check(r(`Controls pressed on each welcomed page once every page has its outcome, such as [{ key: 'KeyR', at: 0 }] to ready up`)),seconds:t.optional(t.number().check(t.positive())).check(r(`Seconds the welcomed pages play after the joins, before each is read; give this or until`)),until:t.optional(t.string()).check(r(`A JavaScript expression the welcomed pages play until it holds on each, over the page's globals and the engine's camera, loop, cursor and room, such as room.players === 2`)),untilSeconds:t.optional(t.number().check(t.positive())).check(r(`Seconds until may take to hold: 120 by default`)),screenshot:t.optional(t.boolean()).check(r(`Shoot every page as the call ends, numbered under the game's .spawnite/shots, or under a temporary folder for a page already served against a room by its address with no project given; each page's line names its file`)),roomEnv:t.optional(t.record(t.string(),t.string())).check(r(`The game's own room's environment, over its spawnite.room's, such as { MAX_PLAYERS: '2' }; a room given by its address runs as it was started`)),seat:t.optional(t.union([t.string(),t.literal(!0)])).check(r(`A game whose rooms the backend runs, such as production's, by its id or a link to its page; true for the one package.json names under spawnite.game. The call takes the signed-in account's seat through the backend's join, as the app does, and joins one page to the room it answers, so give no room`))},dr=t.object({x:t.number(),y:t.number()}),fr={project:k,keys:t.optional(F).check(r(`Keys pressed together for one step before the first frame, such as ["3"] for the third ability; give keys or clip`)),clip:t.optional(t.string()).check(r(`A clip to play on the character in place of the keys, one registerClip named, such as mage-spell-cast-6; the sheet starts on its first frame`)),every:t.optional(t.number().check(t.positive())).check(r(`Seconds of game time between two frames, snapped to whole steps of 1/60 s; 0.1 without it`)),seconds:t.optional(t.number().check(t.positive())).check(r(`Seconds of game time the sheet covers; 2 without it`)),crop:t.optional(t.string()).check(r(`The entity each frame is cropped round, by dump key or selector, [controlled] without it; none keeps the whole page shrunk to the cell`))},pr={keys:t.optional(F).check(r(`Keys to hold together, by Playwright's names: w, Space, ArrowLeft`)),holdSeconds:t._default(t.number().check(t.nonnegative()),.1).check(r(`How long the keys stay down, in game seconds: the world steps that long with them held`)),click:t.optional(t.union([dr,t.object({entity:t.string().check(r(`The entity to click, where it shows nearest its middle: a dump key, a selector one entity answers, such as [npc.name=Mira], or its row's name`))}),t.string()])).check(r(`What to click: a control's accessible name, such as a button's, a link's, a radio's or a tab's, which an icon takes from its label, or the start of a word of one in any case, so an icon before it is passed over; any element's exact text; a point in a screenshot's pixels: on a headless page, its viewport, which a screenshot taken without size shows pixel for pixel, and on the creator's tab, its canvas at its drawing size, which the tab's screenshot is; or { entity }, an entity in the world, such as an NPC to talk to. A control scrolled out of view, inside a dialog too, is scrolled into view first. A name reaches the game's own controls: the devtools' controls, such as the Spawnite toggle at the top left, are neither clicked by name nor listed when a name misses; a point reaches them`)),type:t.optional(t.object({label:t.string().check(r(`The text field's label, as its <label>, aria-label or aria-labelledby reads, or a part of it in any case`)),text:t.string().check(r(`The text the field holds after`))})).check(r(`A text field to fill after the click and before the keys, as a player types into it, such as { label: "Name", text: "Mira" }; keys ["Enter"] then submits it`)),walkTo:t.optional(ie).check(r(`A point on the map, [x, y, z] as the dump writes a transform. After the click and the keys, the player's character walks there round obstacles, as after a click on the ground, and the world steps until she arrives`))},mr={subject:er},hr={id:t.string().check(r(`The row's id from the tree outline, its entity's dump key, as a read_game_wiki entry's usedBy holds it, which also finds an entity with no row, or its name, such as Player, where one entity row has it`))},gr={subject:t.string(),path:t.string(),value:t.string().check(r(`JSON; null takes the trait off`))},_r={query:t.optional(t.string()).check(r(`Keeps the entries whose name, kind or description holds it, ignoring case, such as health`)),kind:t.optional(t.string()).check(r(`Keeps one kind and its entries, such as behaviour, trait or system`))},vr=60,yr=10,br={changes:`changed by`,"held by":`holds`,scatters:`scattered by`,"lays out":`laid out by`,reads:`read by`,handles:`handled by`,"handled by":`handles`};function xr(e){return e===void 0?`referred to by`:br[e]??`linked from`}var Sr=`spawnite-engine`,Cr=`/__spawnite/engine`,wr=86400,Tr={timeoutSeconds:t.optional(t.number().check(t.positive(),t.lte(wr))).check(r(`Wall-clock seconds this call may take, a day at most, in place of the limit its description gives`)),wait:t.optional(t.boolean()).check(r(`False returns a run id at once, which read_run reads`))},Er={id:t.string().check(r(`The run's id, as the call started with wait: false returned it`))};function Dr(e){return e.replace(/(\p{Ll})(\p{Lu})/gu,`$1 $2`).replace(/(\p{Lu})(\p{Lu}\p{Ll})/gu,`$1 $2`).replace(/(\p{L})(\p{N})/gu,`$1 $2`).split(/[^\p{L}\p{N}]+/u).filter(Boolean).map(e=>e[0].toUpperCase()+e.slice(1)).join(` `)}var Or=`${an}/schema/registry-item.json`,kr=`${an}/schema/registry.json`,Ar=t.strictObject({path:t.string(),type:t.literal(`registry:file`),target:t.string(),content:t.string(),encoding:t.optional(t.literal(`base64`))}),R={Always:`always`,Optional:`optional`},jr=t.strictObject({mode:t.enum([R.Always,R.Optional]),scene:t.string(),env:t.optional(t.record(t.string(),t.string()))}),Mr=`Add the room to the game's package.json, with the scene the room runs: "spawnite": { "room": { "mode": "always", "scene": "./src/scenes/Lobby.tsx" } }`;function Nr(e){let n=e?.spawnite?.room;if(n===void 0)return;let r=jr.safeParse(n);if(!r.success)throw Error(`The room in package.json has the wrong shape: ${t.prettifyError(r.error)}\n${Mr}. "mode" is "always" or "optional", and an optional "env" holds settings such as { "PORT": "8788" }.`);return r.data}function Pr(e){return Nr(e)?.mode??`none`}var Fr=t.strictObject({$schema:t.literal(Or),name:t.string(),type:t.enum([`registry:block`,`registry:component`]),title:t.string(),description:t.optional(t.string()),dependencies:t.array(t.string()),devDependencies:t.array(t.string()),files:t.array(Ar),room:t.optional(jr)}),Ir=t.omit(Fr,{$schema:!0,files:!0}),Lr=t.strictObject({$schema:t.literal(kr),name:t.string(),homepage:t.string(),items:t.array(Ir)}),z=e=>typeof e==`object`&&!!e,B=(e,t)=>z(e)&&t(e),V=(e,t)=>Array.isArray(e)&&e.every(e=>B(e,t)),H=()=>!0,Rr=e=>t=>z(t)&&(typeof t.type!=`string`||!Object.hasOwn(e,t.type)||e[t.type](t)),zr={ConstantValue:H,IntervalValue:H,PiecewiseBezier:e=>V(e.functions,e=>z(e.function))},U=Rr(zr),W=(...e)=>t=>e.every(e=>U(t[e])),G=(...e)=>t=>e.every(e=>z(t[e])),K=Rr({...zr,AxisAngle:e=>z(e.axis)&&U(e.angle),Euler:W(`angleX`,`angleY`,`angleZ`),RandomQuat:H,Vector3Function:W(`x`,`y`,`z`)}),Br=/* @__PURE__ */ new Set([`Vector3`,`Vector4`,`Color`]),Vr=e=>{let t=Br.has(String(e.subType));return V(e.keys,e=>!t||e.value!==null&&e.value!==void 0)},Hr=G(`a`,`b`),Ur=e=>e.functions?V(e.functions,e=>B(e.function,Hr)):B(e.alpha,Vr)&&B(e.color,Vr),Wr=Rr({ConstantColor:G(`color`),ColorRange:Hr,RandomColor:Hr,Gradient:Ur,RandomColorBetweenGradient:e=>B(e.gradient1,Ur)&&B(e.gradient2,Ur)}),q=e=>!e.speed||U(e.speed),Gr={circle:q,cone:q,donut:q,point:H,sphere:q,hemisphere:q,grid:H,rectangle:q,mesh_surface:H},Kr=e=>z(e.speedRange),qr={ApplyForce:e=>{let t=e.magnitude??e.force;return z(e.direction)&&U(t)&&t.type!==`IntervalValue`},Noise:W(`frequency`,`power`,`positionAmount`,`rotationAmount`),TurbulenceField:G(`scale`,`velocityMultiplier`,`timeScale`),GravityForce:G(`center`),ColorOverLife:e=>Wr(e.color),RotationOverLife:W(`angularVelocity`),SizeOverLife:e=>K(e.size),ColorBySpeed:e=>Wr(e.color)&&Kr(e),RotationBySpeed:e=>W(`angularVelocity`)(e)&&Kr(e),SizeBySpeed:e=>K(e.size)&&Kr(e),SpeedOverLife:W(`speed`),FrameOverLife:W(`frame`),ForceOverLife:W(`x`,`y`,`z`),OrbitOverLife:W(`orbitSpeed`),WidthOverLength:W(`width`),ChangeEmitDirection:W(`angle`),EmitSubParticleSystem:H,LimitSpeedOverLife:W(`speed`)},Jr=e=>typeof e.type==`string`&&Object.hasOwn(qr,e.type)?qr[e.type](e):!(String(e.type)in Object.prototype),Yr=1,Xr=3,Zr=e=>{let t=e.rendererEmitterSettings;return e.renderMode===Xr?B(t,e=>e.startLength===null||e.startLength===void 0||U(e.startLength)):e.renderMode===Yr?t==null?e.speedFactor===null||e.speedFactor===void 0:z(t):!0},Qr=e=>typeof e.count==`number`||U(e.count),$r=e=>B(e,e=>B(e.shape,e=>typeof e.type==`string`&&Object.hasOwn(Gr,e.type)&&Gr[e.type](e))&&Zr(e)&&W(`startLife`,`startSpeed`,`emissionOverTime`,`emissionOverDistance`)(e)&&K(e.startRotation)&&K(e.startSize)&&Wr(e.startColor)&&(e.emissionBursts===null||e.emissionBursts===void 0||V(e.emissionBursts,Qr))&&(typeof e.startTileIndex==`number`||U(e.startTileIndex))&&V(e.behaviors,Jr));function J(e){if(typeof e==`number`)return e;if(!z(e))return 0;let t=e=>e.reduce((e,t)=>Math.max(e,Number(t)),-1/0);switch(e.type){case`ConstantValue`:return Number(e.value);case`IntervalValue`:return t([e.a,e.b]);case`PiecewiseBezier`:return t(e.functions.flatMap(({function:e})=>{let{p0:t,p1:n,p2:r,p3:i}=e;return[t,n,r,i]}));default:return 0}}function ei(e){let t=J(e.startLife),n=J(e.emissionOverTime),r=(e.emissionBursts??[]).reduce((e,t)=>e+J(t.count),0),i=e.looping!==!1,a=Number(e.duration??1),o=i?Math.max(1,Math.ceil(t/a)):1;return{perSecond:n,perMetre:J(e.emissionOverDistance),particles:n*t+r*o,prewarmSeconds:i&&e.prewarm?a:0}}var ti=`spawnite.net`;function ni(e){return`https://${e}.${ti}`}var ri=`engine`,ii=`https://${ri}.${ti}`,ai=`https://assets.spawnite.com`,oi=RegExp(`^(?:${ri}|www|m-.+)$`);function si(e){return oi.test(e)}var ci=/^[1-9]\d{7,}$/;function li(e){return ci.test(e)}var Y={shortest:3,longest:40},ui=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;function di(e){return ui.test(e)&&e.length>=Y.shortest&&e.length<=Y.longest}function fi(e){if(!di(e))return`A slug is ${Y.shortest} to ${Y.longest} lowercase letters, digits and single hyphens, and starts with a letter, such as tower-defense.`}var pi=`game`;function mi(e){let t=e.normalize(`NFKD`).replace(/\p{M}+/gu,``).toLowerCase().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``),n=/^[a-z]/.test(t)?t:[pi,t].filter(Boolean).join(`-`);return(n.length<Y.shortest?`${n}-${pi}`:n).slice(0,Y.longest).replace(/-+$/,``)}var hi=/(?:^|\/)games\/(?:[a-z][a-z0-9-]*\/)?([1-9]\d{7,})(?:[/?#]|$)/,gi=RegExp(`^(?:https?://)?([1-9]\\d{7,})\\.${ti.replaceAll(`.`,`\\.`)}(?:[/:?#]|$)`);function _i(e){let t=e.trim(),n=hi.exec(t)?.[1]??gi.exec(t)?.[1]??t;return li(n)?n:void 0}function vi({id:e,slug:t}){return`/games/${t}/${e}`}var yi=(e,t)=>`assets/${e}${t.toLowerCase()}`,bi=(e,t)=>`${ai}/${yi(e,t)}`,xi=t.enum([`model`,`avatar`,`clip`,`texture`,`sound`,`particle`]),Si=t.enum([`characters`,`animals`,`nature`,`buildings`,`props`,`vehicles`,`textures`,`skies`,`sounds`,`animations`,`effects`]),Ci=t.enum([`low-poly`,`stylized`,`realistic`]),wi=t.enum([`cc0`,`platform`,`mixamo`,`vroid`,`unknown`]),Ti=t.object({name:t.string().check(t.minLength(1)),url:t.optional(t.url()),credit:t.string().check(t.minLength(1))}),Ei=t.object({name:t.string().check(t.minLength(1)),category:Si,tags:t.optional(t.array(t.string())),style:t.optional(Ci),licence:wi,source:Ti,metres:t.optional(t.number().check(t.positive()))}),Di=t.record(t.string(),Ei),Oi=t.array(t.number()),X=t.object({id:t.string().check(t.regex(/^[a-z0-9]+(-[a-z0-9]+)*$/)),kind:xi,bytes:t.int().check(t.nonnegative()),sha256:t.string().check(t.regex(/^[0-9a-f]{64}$/)),url:t.url(),...Ei.shape,pack:t.optional(t.string()),triangles:t.optional(t.int().check(t.nonnegative())),clips:t.optional(t.array(t.string())),rigged:t.optional(t.boolean()),bounds:t.optional(t.object({min:Oi,max:Oi})),resolution:t.optional(t.tuple([t.int(),t.int()])),seconds:t.optional(t.number().check(t.nonnegative())),looping:t.optional(t.boolean()),thumbnail:t.optional(t.url())}),ki=t.object({assets:t.array(X)}),Ai=t.omit(X,{id:!0,kind:!0,bytes:!0,sha256:!0,url:!0,pack:!0,thumbnail:!0,looping:!0}),ji={path:t.string().check(r(`The asset file, such as models/rock.glb, or a folder of them: absolute, or from the session's folder. Its folder holds the catalog.json with each file's row`))},Mi={project:k,name:X.shape.id.check(r(`The model's id, such as fire-imp: the script is models/<name>.py and the file public/assets/<name>.glb`))},Ni={files:t.array(t.string()).check(t.minLength(1),r(`The sound files to check, such as public/assets/wind.mp3 or a downloaded .ogg or .wav: absolute, or from the session's folder`))},Pi=/* @__PURE__ */ function(e){return e.Shaded=`shaded`,e.Silhouette=`silhouette`,e.Outline=`outline`,e.Wireframe=`wireframe`,e.Vertices=`vertices`,e}({}),Fi=t.strictObject({image:t.string(),angles:t.array(t.enum(Xn)).check(t.minLength(1)),crop:t.optional(t.strictObject({x:t.int().check(t.nonnegative()),y:t.int().check(t.nonnegative()),width:t.int().check(t.positive()),height:t.int().check(t.positive())}))}),Ii={files:t.array(t.string()).check(t.minLength(1),r(`GLB files, or VRMA clips`)),angles:t.optional(t.array(Zn).check(t.minLength(1),r(`A column each`))),styles:t.optional(t.array(t.enum(Pi)).check(t.minLength(1),r(`A band each; shaded by default`))),references:t.optional(t.array(Fi).check(r(`Concepts to measure against`))),clip:t.optional(t.string().check(r(`The clip to pose rigged files in`))),atSeconds:t.optional(t.array(t.number().check(t.nonnegative())).check(t.minLength(1),r(`Seconds into the clip, a row each`))),fit:t.optional(t.boolean().check(r(`Fit concepts by scale and offset`)))},Li={query:t.optional(t.string()).check(r(`Words to search for in an asset's name, id, tags, category, pack and source; the best matches first`)),kind:t.optional(xi),category:t.optional(Si),pack:t.optional(t.string()).check(r(`The pack's folder name`)),style:t.optional(Ci),licence:t.optional(wi),source:t.optional(t.string()).check(r(`The source's name, such as Kenney`)),rigged:t.optional(t.boolean()),animated:t.optional(t.boolean()).check(r(`Whether the asset carries animation clips`))},Ri=Li,zi={project:k,id:X.shape.id.check(r(`The asset's id, as list_assets prints it, such as bolt-burst`))},Z=e=>(e??``).toLowerCase(),Bi=e=>Z(e).split(/[^a-z0-9]+/).filter(Boolean);function Vi(e,t){let n=Z(e.name);return n.startsWith(t)?4:Bi(n).some(e=>e.startsWith(t))?3:e.tags?.some(e=>Z(e).includes(t))?2:+!![e.category,e.pack,e.source.name,e.id].some(e=>Z(e).includes(t))}function Hi(e,t){let n=Bi(t.query??``),r=(e,t)=>e===void 0||Z(e)===Z(t),i=(e,t)=>e===void 0||e===t;return e.filter(e=>r(t.kind,e.kind)&&r(t.category,e.category)&&r(t.pack,e.pack)&&r(t.style,e.style)&&r(t.licence,e.licence)&&r(t.source,e.source.name)&&i(t.rigged,e.rigged??!1)&&i(t.animated,(e.clips?.length??0)>0)).map(e=>({asset:e,score:n.reduce((t,n)=>{let r=Vi(e,n);return t===-1||r===0?-1:t+r},0)})).filter(({score:e})=>e>=0).sort((e,t)=>t.score-e.score).map(({asset:e})=>e)}var Ui=t.enum([`Int8Array`,`Uint8Array`,`Uint8ClampedArray`,`Int16Array`,`Uint16Array`,`Int32Array`,`Uint32Array`,`Float32Array`,`Float64Array`]),Wi=4096,Gi=t.int().check(t.gte(1),t.lte(Wi)),Ki=t.looseObject({data:t.array(t.number()),type:Ui,width:Gi,height:Gi}).check(t.refine(({data:e,width:t,height:n})=>{let r=e.length/(t*n);return Number.isInteger(r)&&r>=1&&r<=4},`Pixel data holds one to four values for each pixel of its width and height`)),qi=t.union([t.string(),Ki]),Ji=t.optional(t.array(t.looseObject({url:t.union([qi,t.array(qi)])})));function*Yi(e){let t=[e];for(;t.length>0;){let e=t.pop();if(yield e,Xi(e)&&e.children)for(let n of e.children)t.push(n)}}var Xi=e=>typeof e==`object`&&!!e&&(!(`children`in e)||e.children===void 0||Array.isArray(e.children)),Zi=e=>[...Yi(e)],Qi=e=>e.type===`ParticleEmitter`,$i=e=>$r(e.ps)&&(e.ps?.looping===void 0||typeof e.ps.looping==`boolean`);function ea(e){let t;try{t=JSON.parse(e)}catch{return}if(typeof t!=`object`||!t||!(`object`in t))return;let n=[...Yi(t.object)];if(!n.every(Xi))return;let r=n.filter(Qi);if(r.length!==0&&r.every($i)&&Ji.safeParse(`images`in t?t.images:void 0).success)return t}var ta={objects:64,emitters:16,particlesPerSecond:1e3,particlesPerMetre:100,particles:5e3,prewarmSeconds:5};function na({object:e}){let t=Zi(e),n=t.filter(Qi),r=n.reduce((e,{ps:t={}})=>{let{perSecond:n,perMetre:r,particles:i,prewarmSeconds:a}=ei(t);return{perSecond:e.perSecond+n,perMetre:e.perMetre+r,particles:e.particles+i,prewarmSeconds:e.prewarmSeconds+a}},{perSecond:0,perMetre:0,particles:0,prewarmSeconds:0}),i=ta,a=[[t.length,i.objects,`holds {} objects`],[n.length,i.emitters,`holds {} emitters`],[r.perSecond,i.particlesPerSecond,`emits up to {} particles a second`],[r.perMetre,i.particlesPerMetre,`emits up to {} particles a metre`],[r.particles,i.particles,`holds up to {} particles at once`],[r.prewarmSeconds,i.prewarmSeconds,`prewarms {} seconds of play`]].find(([e,t])=>!(e<=t));if(!a)return null;let[o,s,c]=a;return`${c.replace(`{}`,String(o))}, past the ${s} an effect may`}function ra({object:e}){return Zi(e).some(e=>Qi(e)&&e.ps?.looping!==!1)}var ia=t.strictObject({x:t.number(),y:t.number(),z:t.number(),width:t.number().check(t.positive(),r(`Metres of lane edge to edge, blended to the next point.`)),zone:t.optional(t.string().check(t.minLength(1))).check(r(`The material tag the point stands in, such as ice, blended to the next point; the game says what one it leaves out is.`))}).check(r(`A point the centerline passes through.`)),aa={at:t.number().check(t.nonnegative(),r(`Metres along the centerline.`)),side:t.number().check(r(`Metres across it, positive to the right of travel.`))},oa=t.strictObject({...aa,scale:t.optional(t.number().check(t.positive())).check(r(`Times its size.`))}).check(r(`A copy of something that stands on the course.`)),sa=t.strictObject({...aa,lift:t.optional(t.number().check(t.positive())).check(r(`Metres it floats higher, such as a coin over a rock that only a rider in the air takes.`))}).check(r(`A spot a rider hits: a coin, a rock.`)),ca=t.string().check(t.minLength(1)),la=t.strictObject({name:t.string().check(t.minLength(1)),track:t.strictObject({points:t.array(ia).check(t.minLength(2))}),placements:t._default(t.record(ca,t.array(oa)),{}).check(r(`What stands along the course, by kind.`)),triggers:t._default(t.record(ca,t.array(sa)),{}).check(r(`What a rider hits along the course, by kind.`)),finish:t.strictObject({at:t.number().check(t.nonnegative(),r(`Metres along the centerline.`))})}).check(r(`One level of a track game.`));function ua(e){let n=la.safeParse(e);if(!n.success)throw Error(`Not a track level:\n${t.prettifyError(n.error)}`);return n.data}var da={triangles:1e5}.triangles,fa=`.spawnite`,pa=`shots`,ma=`chat`,ha=`read`,ga=`servers`,_a=`/__`,va=`/__shots`,ya=`/__chat`,ba=`/__tab`,xa=`x-spawnite-token`,Sa=t.object({url:t.string(),token:t.string(),pid:t.number()}),Ca=t.object({id:t.string(),href:t.string(),load:t.string(),visible:t.boolean(),automated:t.boolean(),seenAt:t.number()}),wa=t.object({expression:t.string().check(t.minLength(1)),tab:t.optional(t.string()),timeoutMilliseconds:t._default(t.number().check(t.positive()),6e4)}),Ta=t.object({png:t.string().check(t.startsWith(`data:image/png;base64,`))}),Ea=t.object({text:t.string().check(t.minLength(1)),shots:t._default(t.array(t.string()),[]),attachments:t._default(t.array(t.object({id:t.string(),label:t.string()})),[])}),Da={wait:t.optional(t.boolean()).check(r(`While a sign-in login started waits for the creator's approval, hold the call until they approve, up to timeoutSeconds, rather than answer at once`))},Oa={name:t.string().check(r(`The nickname other players see and the store shows as the maker of the creator's games: 3 to 20 Latin letters or digits, with one space, _, - or . between words`))},ka={project:k,wait:t.optional(t.boolean()).check(r(`With no unread message, hold the call until one lands, up to timeoutSeconds, rather than answer at once`)),all:t.optional(t.boolean()).check(r(`Every message, read or not, and mark none read; for looking back at what the creator sent`))},Aa=`spawnite:agent`,ja=5e3,Ma=/* @__PURE__ */ function(e){return e.Attached=`attached`,e.Busy=`busy`,e.Detached=`detached`,e}({}),Na=`clips`,Pa=`/__clips`,Fa=/* @__PURE__ */ function(e){return e.Wide=`16:9`,e.Tall=`9:16`,e}({}),Ia=/* @__PURE__ */ function(e){return e.Wall=`wall`,e.Steps=`steps`,e}({}),La=/* @__PURE__ */ function(e){return e.Start=`start`,e.Stop=`stop`,e}({}),Ra=t.string().check(t.regex(/^\d{8}-\d{6}(-\d+)?$/)),za={id:t.string(),from:t.number(),to:t.number()},Ba=t.object({startedAt:t.number(),shape:t.enum(Fa),width:t.number(),height:t.number(),fps:t.number(),durationMs:t.number(),droppedFrames:t.number(),frames:t.array(t.object({at:t.number()})),replay:t.optional(t.object(za)),warnings:t.array(t.string())}),Va=t.object({name:Ra,createdAt:t.string(),shape:t.enum(Fa),width:t.number(),height:t.number(),fps:t.number(),durationMs:t.number(),bytes:t.number(),droppedFrames:t.number(),sheet:t.string(),frames:t.array(t.object({file:t.string(),at:t.number()})),events:t.array(t.looseObject({at:t.number(),text:t.string()})),replay:t.optional(t.object({...za,saved:t.string()})),warnings:t.array(t.string())});function Ha(e){let t=Math.floor(e/1e3);return`${Math.floor(t/60)}:${String(t%60).padStart(2,`0`)}`}function Ua(e){return`${(e/1024**2).toFixed(1)} MB`}function Wa(e){let[,t=``]=e.split(`-`);return t.replace(/(\d\d)(\d\d)(\d\d)/,`$1:$2:$3`)}var Ga={project:k},Ka=e=>t.optional(t.number()).check(r(e)),qa={project:k,name:t.string().check(r(`The clip's name, as list_clips prints it`)),from:Ka(`Seconds from the clip's start; with to, return the stills of that span instead of the sheet`),to:Ka(`Seconds from the clip's start where the span ends`)},Ja={action:t.optional(t.enum(La)).check(r(`start begins a clip and stop saves it, with other playtest calls between; leave it out with seconds`)),seconds:t.optional(t.number().check(t.positive())).check(r(`Seconds of game time to step and record in one call, in place of action`)),keys:t.optional(t.array(t.string())).check(r(`With seconds, keys held down while the world steps, by Playwright's names: w walks the character`)),shape:t._default(t.enum(Fa),`16:9`).check(r(`The clip's shape, 16:9 or 9:16, which the page is letterboxed to`))},Ya=t.number(),Xa=24,Za=t.number().check(t.positive(),t.refine(e=>Number.isFinite(Xa/e))),Qa=t.record(t.string(),t.unknown()),$a=t.object({presetsByComponent:t.record(t.string(),t.record(t.string(),Qa))}),eo=/* @__PURE__ */ function(e){return e.World=`world`,e.Behaviour=`behaviour`,e.Stat=`stat`,e}({}),to=t.object({kind:t.enum(eo),component:t.optional(t.string()),behaviour:t.optional(t.string()),field:t.string()}),no=t.object({favorites:t.array(to)}),ro={bundleMegabytes:1024,bundleWarningMegabytes:100,bundleFiles:5e3,initialDownloadMegabytes:50,initialDownloadExtensions:[`html`,`js`,`mjs`,`css`]},io=`page`,ao=`server`,oo=`manifest.json`,so=ro,co=t.object({bytes:t.int().check(t.nonnegative()),sha256:t.string().check(t.regex(/^[0-9a-f]{64}$/))}),lo=t.object({engine:t.object({version:t.string().check(t.regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/)),pin:t.optional(t.literal(`exact`))}),rooms:t.enum([R.Always,R.Optional,`none`]),scene:t.optional(t.string().check(t.regex(/^[\w-]+$/))),maxPlayers:t.optional(t.int().check(t.gte(1),t.lte(50))),sendRate:t.optional(t.literal(_t)),items:t.array(t.object({id:t.string(),name:t.string()})),save:t.optional(t.record(t.string(),t.object({version:t.int().check(t.positive()),fingerprint:t.nullable(t.object({format:t.int().check(t.positive()),hash:t.string()}))}))),files:t.record(t.string(),co)}),Q={name:50,tagline:80,description:1e3,alt:200,previews:8},uo={extensions:[`.png`,`.jpg`,`.jpeg`,`.webp`],maxBytes:2097152,minWidth:1280,minHeight:720,maxWidth:3840,maxHeight:2160},fo=[`13+`,`16+`,`18+`];function po(e){return t.string().check(t.trim(),t.minLength(1),t.maxLength(e))}var mo=t.object({file:t.string().check(t.regex(/\.(png|jpe?g|webp)$/i)),alt:po(Q.alt)}),ho=t.object({name:t.optional(po(Q.name)),tagline:po(Q.tagline),description:po(Q.description),age:t.enum(fo),capture:mo,previews:t.optional(t.array(mo).check(t.maxLength(Q.previews)))}),go={project:k,game:t.optional(t.string()).check(r(`The game's id, such as 48201973, or a link to its page. Left out, the game the project's package.json names as spawnite.game`))},_o=t.optional(t.string()).check(r(`The game's id or a link to its page. Left out, the game the project's package.json names as spawnite.game`)),vo=t.optional(t.boolean()).check(r(`true writes spawnite.engine.pin into package.json and pins this version and each later one to the engine release it is built against, which then gets no engine fixes until a new publish; false removes it. Left out, package.json decides`)),yo={project:k,game:_o,folder:t.optional(t.string()).check(r(`A bundle folder spawnite build wrote, manifest.json with page/ and server/, sent as it is. Left out, the game is built into its dist/bundle first`)),pin:vo},bo={project:k,game:_o,version:t.optional(t.int().check(t.positive())).check(r(`An uploaded version to make live as it is, newer or older than the live one, as a rollback. Left out, the game is built and uploaded first, and that version goes live`)),public:t.optional(t.boolean()).check(r(`true also puts the game on the store, which needs a store page; refused before anything is built where the game has none`)),slug:t.optional(t.string()).check(r(`The readable text in the game's address, which other games may share. Left out, the slug the game has stays`)),pin:vo},xo={project:k,game:_o,visibility:t.enum([`public`,`unlisted`]).check(r(`public puts the game on the store, which needs a live version and a store page; unlisted takes it off, and only people with its keyed link can play`))},So=`spawnite-error-reports`,Co=`/__spawnite/errors`,wo=204800,To=t.object({dsn:t.string(),game:t.string(),version:t.number(),release:t.string()}),Eo=t.int().check(t.positive()),Do=t.custom(e=>Object.prototype.toString.call(e)===`[object Uint8Array]`,`Expected the body as a Uint8Array of gzipped JSON.`),Oo=t.record(t.string(),t.int().check(t.nonnegative())),ko=11,Ao=/* @__PURE__ */ function(e){return e.Hello=`spawnite:hello`,e.Connect=`spawnite:connect`,e}({}),jo=t.int().check(t.positive()),Mo=t.object({type:t.literal(`spawnite:hello`),version:jo,release:t.optional(t.string())}),No=t.object({type:t.literal(`spawnite:connect`),version:jo}),Po=/* @__PURE__ */ function(e){return e.Init=`init`,e.Insets=`insets`,e.Settings=`settings`,e.Leave=`leave`,e.Saved=`saved`,e.SaveFailed=`saveFailed`,e.SaveNow=`saveNow`,e.Seat=`seat`,e.Fullscreen=`fullscreen`,e}({}),Fo={init:1,insets:3,settings:5,leave:7,saved:8,saveFailed:8,saveNow:8,seat:10,fullscreen:11},Io=/* @__PURE__ */ function(e){return e.Ready=`ready`,e.Fullscreen=`fullscreen`,e.Settings=`settings`,e.Menu=`menu`,e.Left=`left`,e.Save=`save`,e.SaveLoadFailed=`saveLoadFailed`,e.Connection=`connection`,e}({}),Lo={ready:1,fullscreen:4,settings:5,menu:6,left:7,save:8,saveLoadFailed:8,connection:10},Ro=t.object({id:t.optional(t.string()),name:t.optional(t.string())}),zo=t.looseObject({kind:t.string()}),Bo=/* @__PURE__ */ function(e){return e.Seat=`seat`,e}({}),Vo={seat:2},Ho=t.object({kind:t.literal(`seat`),ticket:t.string(),room:t.string()}),Uo=t.optional(t.number().check(t.nonnegative())),Wo=t.object({top:Uo,right:Uo,bottom:Uo,left:Uo}),Go=[`top`,`right`,`bottom`,`left`],Ko=t.object({body:t.nullable(Do),next:Eo}),qo=t.object({type:t.literal(`init`),player:Ro,credential:t.optional(zo),insets:Wo,save:t.optional(Ko)}),Jo=t.object({type:t.literal(`insets`),insets:Wo}),Yo=t.object({type:t.literal(`settings`),settings:en}),Xo=t.object({type:t.literal(`leave`),moved:t.optional(t.boolean())}),Zo=t.object({type:t.literal(`saved`),number:Eo}),Qo=t.object({type:t.literal(`saveFailed`),number:Eo}),$o=t.object({type:t.literal(`saveNow`)}),es=t.object({type:t.literal(`seat`),credential:t.optional(zo)}),ts=t.object({type:t.literal(`fullscreen`),shown:t.boolean()}),ns=t.discriminatedUnion(`type`,[qo,Jo,Yo,Xo,Zo,Qo,$o,es,ts]),rs=t.object({type:t.literal(`ready`)}),is=t.object({type:t.literal(`fullscreen`)}),as=t.object({type:t.literal(`settings`),changed:en,cleared:nn}),os=t.object({type:t.literal(`left`),released:t.boolean()}),ss=t.object({type:t.literal(`menu`),open:t.boolean()}),cs=t.object({number:Eo,body:Do,versions:Oo}),ls=t.object({type:t.literal(`save`),record:t.nullable(cs),cloud:t.boolean()}),us=t.object({type:t.literal(`saveLoadFailed`),key:t.string(),owner:t.string(),reason:t.string(),pastLimit:t.optional(t.literal(!0))}),ds=/* @__PURE__ */ function(e){return e.Connecting=`connecting`,e.Joined=`joined`,e.Reconnecting=`reconnecting`,e.Rejoining=`rejoining`,e.Closed=`closed`,e}({}),fs=t.object({type:t.literal(`connection`),status:t.enum(ds)}),ps=t.discriminatedUnion(`type`,[rs,is,as,ss,os,ls,us,fs]),ms={latencyMilliseconds:0,jitterMilliseconds:0,stall:null},$=1e4;function hs({latencyMilliseconds:e,jitterMilliseconds:t,stall:n}){return e===0&&t===0&&!n}function gs(e,t){let n=Number(e.trim());return e.trim()!==``&&Number.isFinite(n)&&n>=0&&n<=t?n:void 0}function _s(e){let t=e.split(`/`),[n,r]=t.map(e=>gs(e,$));if(t.length!==2||n===void 0||r===void 0||n===0||r===0)throw Error(`A stall takes <milliseconds>/<every seconds>, such as 500/10 for half a second of nothing every ten seconds; got "${e}".`);if(n>=r*1e3)throw Error(`A stall of ${n} ms must be shorter than the seconds between stalls, ${r} s, or nothing ever gets through; got "${e}".`);return{milliseconds:n,everySeconds:r}}function vs({latency:e,jitter:t,stall:n}){let r=e===void 0?0:gs(e,$);if(r===void 0)throw Error(`Latency takes the round trip in milliseconds, such as 150, from 0 to ${$}; got "${e}".`);let i=t===void 0?0:gs(t,$);if(i===void 0)throw Error(`Jitter takes milliseconds, such as 20, from 0 to ${$}; got "${t}".`);return{latencyMilliseconds:r,jitterMilliseconds:i,stall:n===void 0?null:_s(n)}}function ys({latencyMilliseconds:e,jitterMilliseconds:t,stall:n}){return{...e>0&&{latency:String(e)},...t>0&&{jitter:String(t)},...n&&{stall:`${n.milliseconds}/${n.everySeconds}`}}}function bs(e){if(hs(e))return`direct`;let{latencyMilliseconds:t,jitterMilliseconds:n,stall:r}=e;return[`${t} ms`,n>0&&`jitter ${n} ms`,r&&`stall ${r.milliseconds} ms every ${r.everySeconds} s`].filter(Boolean).join(`, `)}export{Ma as AgentState,Po as AppMessageType,Ao as ChannelHandshake,Ia as ClipClock,La as ClipRecordAction,Fa as ClipShape,o as CollisionShape,d as CoverKind,Bo as CredentialKind,Wt as DeviceClass,rt as EngineSaveName,eo as FavoriteKind,Pi as ModelStyle,ds as PageConnectionStatus,Io as PageMessageType,Yt as PerformanceStatsMode,fe as PropShape,Ut as QualityGroup,Ht as QualityLevel,R as RoomMode,st as SaveOwnerChange,je as SaveSection,a as ScatterFamily,i as ScatterKind,Xn as ShotAngle,Xt as VoiceMode,pn as WikiScope,zi as addAssetInput,_n as addBehaviourInput,Sn as addDialogInput,bn as addEntityInput,yn as addHudInput,Mn as addItemInput,wn as addMapInput,xn as addNpcInput,Tn as addPanelInput,Cn as addSceneInput,En as addShaderInput,Aa as agentActivityEvent,ja as agentHeartbeatMilliseconds,ko as appChannelVersion,zo as appCredential,Go as appInsetEdges,Wo as appInsets,ns as appMessage,Fo as appMessageVersions,Ro as appPlayer,Yo as appSettingsMessage,Di as assetCatalog,Si as assetCategory,Ai as assetDeclaration,X as assetEntry,Li as assetFilters,yi as assetKey,xi as assetKind,wi as assetLicence,ki as assetManifest,Ti as assetSource,Ci as assetStyle,bi as assetUrl,ai as assetsSite,zn as brushShapes,Rt as buildSaveManifestEntry,co as bundleFile,so as bundleLimits,lo as bundleManifest,oo as bundleManifestFile,io as bundlePageFolder,ao as bundleServerFolder,nn as carriedSettingKeyList,$t as carriedSettingKeys,en as carriedSettings,Ei as catalogRow,Un as cellPaint,No as channelConnect,Mo as channelHello,ma as chatFolderName,ya as chatPath,Ea as chatPost,ha as chatReadFolderName,fi as checkGameSlug,Ii as checkModelInput,Ge as checkSaveMigrations,pt as checkSavePublish,Ni as checkSoundInput,Ba as clipFinish,Va as clipMeta,Ra as clipName,Na as clipsFolderName,Pa as clipsPath,ut as compareSaveManifestEntries,Yn as compareShotsInput,D as componentName,yr as conditionSeconds,fs as connectionMessage,Jt as createQualitySchema,on as createSite,qe as createTaggedSaveValueSchema,Vo as credentialVersions,Ya as dayHour,Za as dayLength,w as defaultPlayerSettings,gt as defaultRoomPlayers,vt as defaultRoomSendRate,hr as describeEntityInput,I as describeMapInput,mt as describeSavePublishRefusal,et as devSavesClearedFile,tt as devSavesClearedMetaName,$e as devSavesFolder,Sa as devServerFile,_a as devServerPrefix,no as devtoolsFavorites,$a as devtoolsPresets,ms as directLink,ar as editMaterialInput,ir as editTerrainInput,Sr as engineBuildMetaName,Cr as engineBuildPath,ri as engineLabel,ii as engineOrigin,ot as engineSaveReaders,it as engineSaveSchemas,To as errorReportsConfig,So as errorReportsMetaName,Co as errorReportsPath,to as favoriteField,Hi as filterAssets,na as findParticleEffectExcess,St as findRoomSettingsProblems,In as findTerrainCell,Lt as fingerprintSaveSchema,Ha as formatClipLength,Ua as formatClipSize,bs as formatLinkConditions,Qn as framedShotSchema,ts as fullscreenAnswerMessage,is as fullscreenMessage,ee as gameMaterialsName,te as gameMaterialsSchema,ni as gameOrigin,vi as gamePagePath,Me as gameSaveKey,Ce as gridMap,ne as groundRuleSchema,re as groundSchema,vn as hudSlot,qo as initMessage,Ko as initSave,Jo as insetsMessage,S as isAdditiveSchemaChange,li as isGameId,di as isGameSlug,si as isKeptLabel,hs as isLinkDirect,We as isOlderReader,ra as isParticleEffectLooping,bt as isRoomPlayers,xt as isRoomSendRate,jn as itemDefinition,ur as joinRoomInput,Xo as leaveMessage,os as leftMessage,Ri as listAssetsInput,Ga as listClipsInput,Dn as listTemplatesInput,hn as listWikiInput,Tr as longCallInput,wr as longestTimeoutSeconds,Mi as makeModelInput,_e as mapBaseSchema,Pn as mapPath,ie as mapPoint,qn as mapPost,me as mapPropSchema,Se as mapPropsPatch,he as mapPropsSchema,xe as mapSchema,ge as mapSeaSchema,be as mapTerrainSchema,f as materialCoverSchema,p as materialLookSchema,c as materialName,l as materialSets,m as materialsSchema,wo as maxErrorReportBytes,Ye as maxSaveBodyBytes,Ze as maxSaveBodyCeilingBytes,Je as maxSaveUnpackedBytes,Xe as maxSaveUnpackedCeilingBytes,ss as menuMessage,rn as mergeCarriedSettings,ft as mergeSaveHighest,Fi as modelReferenceSchema,da as modelTriangleBudget,yt as mostRejoinHoldSeconds,ht as mostRoomPlayers,_ as nameSchema,gn as newGameInput,jr as packageRoom,Mr as packageRoomHint,ps as pageMessage,Lo as pageMessageVersions,as as pageSettingsMessage,vs as parseLinkConditions,ta as particleEffectLimits,Wi as particleSpriteMaxSide,Nn as placeLootInput,or as placePropsInput,ti as playDomain,cr as profileGameInput,k as projectFolder,ji as publishAssetsInput,bo as publishVersionInput,rr as queryMapInput,mr as readAiTreeInput,ka as readChatInput,qa as readClipInput,Wa as readClipStartTime,_i as readGameId,_r as readGameWikiInput,Nr as readPackageRoom,Fn as readPaintGrid,ea as readParticleEffect,Pr as readRoomMode,Er as readRunInput,dt as readSaveMark,Ve as readSaveRecord,j as readTerrainGrid,ua as readTrackLevel,fn as readWikiInput,rs as readyMessage,Ja as recordClipInput,Lr as registry,Ir as registryEntry,Ar as registryFile,Fr as registryItem,Or as registryItemSchemaUrl,kr as registrySchemaUrl,Ct as resolveRoomSettings,xr as reverseWikiRelation,_t as roomSendRates,b as saveCodecMark,Qo as saveFailedMessage,Ft as saveFingerprintFormat,Qe as saveLimitWarningShare,us as saveLoadFailedMessage,ls as saveMessage,$o as saveNowMessage,cs as saveRecordMessage,Ee as savedHealthSchema,Ae as savedLevelSchema,Zo as savedMessage,ke as savedStackSchema,Oe as savedStatSchema,Te as savedVectorSchema,s as scatterKinds,O as sceneName,nr as screenshotInput,mn as searchWikiInput,Ho as seatCredential,es as seatMessage,pr as sendInputInput,ga as serversFolderName,Oa as setNicknameInput,gr as setTraitInput,xo as setVisibilityInput,Zt as settingsSchema,Zn as shotAngleSchema,$n as shotCameraSchema,Ta as shotPost,pa as shotsFolderName,va as shotsPath,sr as simulateInput,Y as slugLimits,mi as slugifyGameName,Pt as snapshotSaveSchema,g as spacePointSchema,fa as spawniteFolderName,Jn as startPlaytestInput,nt as startSaveKey,vr as stepsPerSecond,fo as storeAgeBands,ho as storePage,Q as storePageLimits,mo as storePicture,uo as storePictureRules,Ca as tabInfo,ba as tabPath,wa as tabPost,xa as tabTokenHeader,Ke as taggedSaveValueSchema,Hn as terrainBrush,P as terrainFill,we as terrainMap,Wn as terrainRestore,Kn as terrainSettlings,Vn as terrainStroke,N as terrainStrokeShape,Rn as terrainTools,fr as timelineInput,Dr as toDisplayName,ln as toWikiAnchor,la as trackLevelSchema,ve as untouched,go as uploadStorePageInput,yo as uploadVersionInput,Ue as versionsOf,zt as versionsOfSaveManifestEntry,de as viewSchema,Qt as voiceSettings,Da as whoamiInput,un as wikiOverviewPath,sn as wikiPagePath,cn as wikiPageRef,dn as wikiPathPart,an as wikiSite,ys as writeLinkSettings,Ln as writePaintGrid,He as writeSaveRecord,M as writeTerrainGrid,On as writeWikiPageInput,n as z};
package/dist/z.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { _default as _default$1, prefault as prefault$1 } from "zod/mini";
2
+ export * from "zod/mini";
3
+ export declare const _default: typeof _default$1;
4
+ export declare const prefault: typeof prefault$1;
package/dist/z.js ADDED
@@ -0,0 +1 @@
1
+ import{r as e,t}from"./3cnTQfiz.js";export*from"zod/mini";export{t as _default,e as prefault};
package/dist/zod.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { _default as _default$1, prefault as prefault$1 } from "zod/mini";
2
+ export * from "zod/mini";
3
+ export declare const _default: typeof _default$1;
4
+ export declare const prefault: typeof prefault$1;
package/dist/zod.js ADDED
@@ -0,0 +1 @@
1
+ import{r as e,t}from"./3cnTQfiz.js";import{config as n}from"zod/mini";import r from"zod/v4/locales/en.js";export*from"zod/mini";n(r());export{t as _default,e as prefault};
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@spawnite/schema",
3
+ "description": "The zod schemas the games, the tools and the backend share: maps, saves, settings, scaffolds.",
4
+ "version": "0.1.0-beta.16",
5
+ "license": "SEE LICENSE IN LICENSE.md",
6
+ "homepage": "https://wiki.spawnite.com",
7
+ "publishConfig": {
8
+ "registry": "https://registry.npmjs.org",
9
+ "access": "public"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "LICENSE.md",
14
+ "!dist/*.tsbuildinfo",
15
+ "!dist/types",
16
+ "!dist/**/*.map"
17
+ ],
18
+ "type": "module",
19
+ "sideEffects": [
20
+ "./dist/index.js",
21
+ "./dist/zod.js"
22
+ ],
23
+ "main": "./dist/index.js",
24
+ "module": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ "./package.json": "./package.json",
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js",
31
+ "default": "./dist/index.js"
32
+ }
33
+ },
34
+ "dependencies": {
35
+ "zod": "4.6.5"
36
+ }
37
+ }