@nano133/nanoterra 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +82 -12
- package/ai/SPEC.md +5 -1
- package/dist/admin/panel/index.d.ts +1 -1
- package/dist/admin/panel/index.js +1 -1
- package/dist/admin/panel/shelf.d.ts +7 -0
- package/dist/admin/panel/shelf.js +24 -0
- package/dist/character/bases/bee.js +7 -5
- package/dist/character/bases/spec.d.ts +7 -0
- package/dist/character/create.d.ts +15 -0
- package/dist/character/create.js +69 -2
- package/dist/character/ctx.d.ts +12 -2
- package/dist/character/index.d.ts +2 -0
- package/dist/character/index.js +2 -0
- package/dist/character/make.js +2 -1
- package/dist/character/preview.d.ts +32 -0
- package/dist/character/preview.js +112 -0
- package/dist/character/sockets.d.ts +37 -0
- package/dist/character/sockets.js +66 -0
- package/dist/model/check.d.ts +59 -0
- package/dist/model/check.js +681 -0
- package/dist/model/index.d.ts +2 -0
- package/dist/model/index.js +5 -0
- package/dist/model/limits.d.ts +103 -0
- package/dist/model/limits.js +132 -0
- package/package.json +3 -2
- package/src/admin/panel/index.ts +1 -1
- package/src/admin/panel/shelf.ts +24 -0
- package/src/admin/panel/style.css +9 -0
- package/src/character/bases/bee.ts +7 -5
- package/src/character/bases/spec.ts +4 -0
- package/src/character/create.ts +73 -2
- package/src/character/ctx.ts +17 -2
- package/src/character/index.ts +2 -0
- package/src/character/make.ts +2 -1
- package/src/character/preview.ts +148 -0
- package/src/character/sockets.ts +81 -0
- package/src/model/check.ts +624 -0
- package/src/model/index.ts +5 -0
- package/src/model/limits.ts +193 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// The limits a 3D model is checked against (@nano133/nanoterra/model; the README's "Model check"; on nano133.com
|
|
2
|
+
// docs/agents/launch.md, "The island model"). The checker (check.ts) is general: it takes one of these objects, so each
|
|
3
|
+
// kind of model (an island, a cosmetic on each socket) is a preset here with the same code. Pure data: no network, no DOM.
|
|
4
|
+
|
|
5
|
+
/** A named node the model must (or may) have. `empty`: a node with no mesh (a point, as `nano_door`); `mesh`: a node with a mesh that is never drawn (a collider). */
|
|
6
|
+
export type MarkerRule = { name: string; required: boolean; kind: "empty" | "mesh"; /** A `mesh` marker's own triangle limit (it is not counted in `maxTriangles`). */ maxTriangles?: number; /** What the marker is for, in plain words, for a fix line. */ purpose: string };
|
|
7
|
+
|
|
8
|
+
export type ZoneBox = { width: number; depth: number; height: number; below: number };
|
|
9
|
+
export type ZoneDisc = { radius: number; height: number; below: number };
|
|
10
|
+
|
|
11
|
+
export type ModelLimits = {
|
|
12
|
+
/** What the model is, for a fix line ("island model"). */
|
|
13
|
+
what: string;
|
|
14
|
+
/** The whole .glb, in bytes. */
|
|
15
|
+
maxBytes: number;
|
|
16
|
+
/** The JSON chunk, in bytes (the model's description; the data is in the BIN chunk). */
|
|
17
|
+
maxJsonBytes: number;
|
|
18
|
+
/** Triangles drawn, every mesh instance counted, the `mesh` markers left out. */
|
|
19
|
+
maxTriangles: number;
|
|
20
|
+
/** Draw calls: each part (glTF primitive) of each drawn mesh instance. Left out: no limit. */
|
|
21
|
+
maxDraws?: number;
|
|
22
|
+
/** Images in the file. */
|
|
23
|
+
maxTextures: number;
|
|
24
|
+
/** The longest side of each image, in pixels. */
|
|
25
|
+
maxTextureSide: number;
|
|
26
|
+
/**
|
|
27
|
+
* The build zone, in metres, in the model's own axes (glTF: +Y up). A box: x from -width/2 to width/2, z from -depth/2
|
|
28
|
+
* to depth/2. A disc: every point at most `radius` from the y axis (x and z together). Both: y from -below to height.
|
|
29
|
+
* The model's origin is the middle of the plot, on the ground.
|
|
30
|
+
*/
|
|
31
|
+
zone: ZoneBox | ZoneDisc;
|
|
32
|
+
markers: readonly MarkerRule[];
|
|
33
|
+
/**
|
|
34
|
+
* A marker whose origin is the attach point (a hand item's `grip_R`): when the file has it, the zone is measured from it, not from the
|
|
35
|
+
* file's origin. It should be one of `markers` too (so a misspelt name gets a fix line).
|
|
36
|
+
*/
|
|
37
|
+
anchor?: string;
|
|
38
|
+
/** The glTF extensions the file may use (plain data only). Every other one is refused. */
|
|
39
|
+
extensions: readonly string[];
|
|
40
|
+
/** Animations, skins and morph targets: refused unless allowed. When allowed, the zone is checked in the rest pose only. */
|
|
41
|
+
allowAnimations: boolean;
|
|
42
|
+
allowSkins: boolean;
|
|
43
|
+
allowMorphTargets: boolean;
|
|
44
|
+
/** Caps on the description, so a hostile file costs little to read. */
|
|
45
|
+
maxNodes: number;
|
|
46
|
+
maxMeshes: number;
|
|
47
|
+
maxPrimitives: number;
|
|
48
|
+
maxAccessors: number;
|
|
49
|
+
maxBufferViews: number;
|
|
50
|
+
maxMaterials: number;
|
|
51
|
+
/** The node tree's depth. */
|
|
52
|
+
maxDepth: number;
|
|
53
|
+
/** Vertex reads for the exact zone check (every rotated instance reads its vertices); past it, the box of each instance is used (larger, never smaller). */
|
|
54
|
+
maxVertexWork: number;
|
|
55
|
+
/** The whole check ends by then (ms). */
|
|
56
|
+
maxMs: number;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** The texture compression and material extensions three.js reads as plain data. */
|
|
60
|
+
export const PLAIN_EXTENSIONS = [
|
|
61
|
+
"KHR_texture_transform",
|
|
62
|
+
"KHR_texture_basisu",
|
|
63
|
+
"EXT_texture_webp",
|
|
64
|
+
"KHR_mesh_quantization",
|
|
65
|
+
"KHR_materials_emissive_strength",
|
|
66
|
+
"KHR_materials_unlit",
|
|
67
|
+
"KHR_materials_ior",
|
|
68
|
+
"KHR_materials_specular",
|
|
69
|
+
"KHR_materials_clearcoat",
|
|
70
|
+
"KHR_materials_sheen",
|
|
71
|
+
"KHR_materials_transmission",
|
|
72
|
+
"KHR_materials_volume",
|
|
73
|
+
"KHR_materials_iridescence",
|
|
74
|
+
"KHR_materials_anisotropy",
|
|
75
|
+
"KHR_materials_dispersion",
|
|
76
|
+
"KHR_xmp_json_ld",
|
|
77
|
+
] as const;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A creator's island model (the creator islands gameplan, phase 2; Wes approved it on 2026-10-27): the entry of their
|
|
81
|
+
* game on their plot. 5 MB, 50,000 triangles, 8 textures of at most 1,024 px, inside a disc of 10 m radius (20 m across)
|
|
82
|
+
* and 18 m high on the plot's middle: the plot's free middle (PLOT_FREE_R in app/nanoterra/wheel/plots.ts; Wes chose the
|
|
83
|
+
* disc over a 24 x 24 m box on 2026-10-28, as the box reached the gates' standing places); an empty `nano_door` where Play stands, an optional empty `nano_spawn` (the arrival point) and an
|
|
84
|
+
* optional simple `nano_collide` mesh. No lights, no scripts, nothing outside the file. A still model for now.
|
|
85
|
+
*/
|
|
86
|
+
export const ISLAND_MODEL: ModelLimits = {
|
|
87
|
+
what: "island model",
|
|
88
|
+
maxBytes: 5 * 1024 * 1024,
|
|
89
|
+
maxJsonBytes: 1024 * 1024,
|
|
90
|
+
maxTriangles: 50_000,
|
|
91
|
+
maxTextures: 8,
|
|
92
|
+
maxTextureSide: 1024,
|
|
93
|
+
zone: { radius: 10, height: 18, below: 0 },
|
|
94
|
+
markers: [
|
|
95
|
+
{ name: "nano_door", required: true, kind: "empty", purpose: "where Play stands" },
|
|
96
|
+
{ name: "nano_spawn", required: false, kind: "empty", purpose: "where players arrive" },
|
|
97
|
+
{ name: "nano_collide", required: false, kind: "mesh", maxTriangles: 5_000, purpose: "the simple shape players walk on" },
|
|
98
|
+
],
|
|
99
|
+
extensions: PLAIN_EXTENSIONS,
|
|
100
|
+
allowAnimations: false,
|
|
101
|
+
allowSkins: false,
|
|
102
|
+
allowMorphTargets: false,
|
|
103
|
+
maxNodes: 4096,
|
|
104
|
+
maxMeshes: 1024,
|
|
105
|
+
maxPrimitives: 4096,
|
|
106
|
+
maxAccessors: 16_384,
|
|
107
|
+
maxBufferViews: 16_384,
|
|
108
|
+
maxMaterials: 256,
|
|
109
|
+
maxDepth: 64,
|
|
110
|
+
maxVertexWork: 4_000_000,
|
|
111
|
+
maxMs: 2_000,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The caps on the description of a cosmetic: a worn thing is small, so a file far past these is not one (and costs little to refuse).
|
|
116
|
+
*/
|
|
117
|
+
const COSMETIC_CAPS = {
|
|
118
|
+
maxJsonBytes: 256 * 1024,
|
|
119
|
+
extensions: PLAIN_EXTENSIONS,
|
|
120
|
+
allowAnimations: false,
|
|
121
|
+
allowSkins: false,
|
|
122
|
+
allowMorphTargets: false,
|
|
123
|
+
maxNodes: 256,
|
|
124
|
+
maxMeshes: 32,
|
|
125
|
+
maxPrimitives: 64,
|
|
126
|
+
maxAccessors: 1024,
|
|
127
|
+
maxBufferViews: 1024,
|
|
128
|
+
maxMaterials: 16,
|
|
129
|
+
maxDepth: 32,
|
|
130
|
+
maxVertexWork: 500_000,
|
|
131
|
+
maxMs: 1_000,
|
|
132
|
+
} as const;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* A worn thing on the character's head socket (@nano133/nanoterra/character, `socket("head")`; Wes approved the cosmetic limits on
|
|
136
|
+
* 2026-10-28). The origin is the socket's point, the middle of the hat frame of the Classic's head, in metres for a character
|
|
137
|
+
* 2.29 m tall (the socket scales it to every other head). The zone is 0.9 x 0.9 m and 0.8 m high, from 0.1 m below the origin.
|
|
138
|
+
* 3,000 triangles, 1 texture of at most 512 px, 2 draws, 300 KB. Rigid: no skin, no animation (it rides its bone).
|
|
139
|
+
*/
|
|
140
|
+
export const HEAD_ITEM: ModelLimits = {
|
|
141
|
+
...COSMETIC_CAPS,
|
|
142
|
+
what: "head item",
|
|
143
|
+
maxBytes: 300 * 1024,
|
|
144
|
+
maxTriangles: 3_000,
|
|
145
|
+
maxDraws: 2,
|
|
146
|
+
maxTextures: 1,
|
|
147
|
+
maxTextureSide: 512,
|
|
148
|
+
zone: { width: 0.9, depth: 0.9, height: 0.7, below: 0.1 },
|
|
149
|
+
markers: [],
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* A worn thing in the character's right hand (`socket("hand")`). Its attach point is its `grip_R` marker (an empty node where the right
|
|
154
|
+
* hand holds it), else the file's origin; a `grip_L` marker (where a left hand would hold it) is allowed. The zone is 0.96 x 0.68 m and
|
|
155
|
+
* 1.6 m long, from 1.17 m below the grip to 0.43 m above it: a tool held near its top, as a shovel by its T-handle (Wes chose this zone on
|
|
156
|
+
* 2026-10-28 over the first 0.4 x 0.4 m from 0.3 m below, which the example shovel did not fit). 3,000 triangles, 1 texture of at most
|
|
157
|
+
* 512 px, 2 draws, 300 KB.
|
|
158
|
+
*/
|
|
159
|
+
export const HAND_ITEM: ModelLimits = {
|
|
160
|
+
...COSMETIC_CAPS,
|
|
161
|
+
what: "hand item",
|
|
162
|
+
maxBytes: 300 * 1024,
|
|
163
|
+
maxTriangles: 3_000,
|
|
164
|
+
maxDraws: 2,
|
|
165
|
+
maxTextures: 1,
|
|
166
|
+
maxTextureSide: 512,
|
|
167
|
+
zone: { width: 0.96, depth: 0.68, height: 0.43, below: 1.17 },
|
|
168
|
+
markers: [
|
|
169
|
+
{ name: "grip_R", required: false, kind: "empty", purpose: "where the right hand holds it" },
|
|
170
|
+
{ name: "grip_L", required: false, kind: "empty", purpose: "where a left hand would hold it" },
|
|
171
|
+
],
|
|
172
|
+
anchor: "grip_R",
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* A worn thing on the character's back (`socket("back")`). The origin is the socket's point, on the back of the torso at the chest's
|
|
177
|
+
* height; the side that touches the body is at the origin and the bulk goes back (-Z). The zone is 1.0 m wide, 0.6 m deep (its middle at
|
|
178
|
+
* the origin) and 1.2 m high, from 0.6 m below. 4,000 triangles, 1 texture of at most 512 px, 2 draws, 400 KB.
|
|
179
|
+
*/
|
|
180
|
+
export const BACK_ITEM: ModelLimits = {
|
|
181
|
+
...COSMETIC_CAPS,
|
|
182
|
+
what: "back item",
|
|
183
|
+
maxBytes: 400 * 1024,
|
|
184
|
+
maxTriangles: 4_000,
|
|
185
|
+
maxDraws: 2,
|
|
186
|
+
maxTextures: 1,
|
|
187
|
+
maxTextureSide: 512,
|
|
188
|
+
zone: { width: 1.0, depth: 0.6, height: 0.6, below: 0.6 },
|
|
189
|
+
markers: [],
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/** The cosmetic preset of each socket. */
|
|
193
|
+
export const COSMETIC_LIMITS = { head: HEAD_ITEM, hand: HAND_ITEM, back: BACK_ITEM } as const;
|