incanto 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/3d.d.ts +34 -0
- package/dist/3d.js +3 -3
- package/dist/{create-game-OSntcIiM.js → create-game-B5GWzy1E.js} +2 -109
- package/dist/index.js +1 -1
- package/dist/{physics-3d-D_kUdOjr.js → physics-3d-CDlvC9Z_.js} +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-C9-16x_t.js → register-Lp2qn1Wq.js} +435 -37
- package/dist/test.js +3 -3
- package/editor/assets/{agent8-BDC4LhjC.js → agent8-Cw7igxDG.js} +1 -1
- package/editor/assets/{index-7qOYg7t8.js → index-N3Hnrv0w.js} +217 -118
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +25 -0
- package/skills/incanto-environment.md +14 -2
- package/skills/incanto-node-reference.md +6 -0
- package/templates-app/beacon-isle-3d/PROJECT/Status.md +1 -1
- package/templates-app/beacon-isle-3d/generate-world.ts +23 -18
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/behaviors.ts +70 -4
- package/templates-app/beacon-isle-3d/src/game.scene.json +20 -21
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/village-quest-3d/PROJECT/Context.md +1 -1
- package/templates-app/village-quest-3d/PROJECT/Requirements.md +1 -1
- package/templates-app/village-quest-3d/PROJECT/Structure.md +6 -1
- package/templates-app/village-quest-3d/generate-dressing.ts +769 -0
- package/templates-app/village-quest-3d/package.json +1 -1
- package/templates-app/village-quest-3d/src/behaviors.ts +74 -8
- package/templates-app/village-quest-3d/src/grove.scene.json +1 -13
- package/templates-app/village-quest-3d/src/village.scene.json +2989 -162
package/editor/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Incanto Scene Editor</title>
|
|
7
7
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><rect width='16' height='16' rx='3' fill='%236ee7dc'/><text x='8' y='12' text-anchor='middle' font-size='11' font-family='monospace' fill='%230e1018'>i</text></svg>" />
|
|
8
|
-
<script type="module" crossorigin src="./assets/index-
|
|
8
|
+
<script type="module" crossorigin src="./assets/index-N3Hnrv0w.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="./assets/GameServer-C56iOUgF.js">
|
|
10
10
|
<link rel="stylesheet" crossorigin href="./assets/index-D8QvwvOm.css">
|
|
11
11
|
</head>
|
package/package.json
CHANGED
|
@@ -6449,6 +6449,31 @@
|
|
|
6449
6449
|
"underwater": {
|
|
6450
6450
|
"type": "boolean",
|
|
6451
6451
|
"default": true
|
|
6452
|
+
},
|
|
6453
|
+
"swell": {
|
|
6454
|
+
"type": "number",
|
|
6455
|
+
"default": 0
|
|
6456
|
+
},
|
|
6457
|
+
"swellDirectionDeg": {
|
|
6458
|
+
"type": "number",
|
|
6459
|
+
"default": 0
|
|
6460
|
+
},
|
|
6461
|
+
"swellWavelength": {
|
|
6462
|
+
"type": "number",
|
|
6463
|
+
"default": 30
|
|
6464
|
+
},
|
|
6465
|
+
"whitecaps": {
|
|
6466
|
+
"type": "number",
|
|
6467
|
+
"default": 0
|
|
6468
|
+
},
|
|
6469
|
+
"preset": {
|
|
6470
|
+
"type": "string",
|
|
6471
|
+
"enum": ["custom", "ocean", "pool", "lake", "pond"],
|
|
6472
|
+
"default": "custom"
|
|
6473
|
+
},
|
|
6474
|
+
"causticsAbove": {
|
|
6475
|
+
"type": "number",
|
|
6476
|
+
"default": 0
|
|
6452
6477
|
}
|
|
6453
6478
|
},
|
|
6454
6479
|
"additionalProperties": false
|
|
@@ -608,8 +608,14 @@ non-default composition.
|
|
|
608
608
|
## Water3D
|
|
609
609
|
|
|
610
610
|
A commercial-quality water surface (in `incanto/3d`, registered by
|
|
611
|
-
`registerNodes3D`).
|
|
612
|
-
|
|
611
|
+
`registerNodes3D`). **Four curated water types are one prop away:**
|
|
612
|
+
`preset: 'ocean' | 'pool' | 'lake' | 'pond'` — see the prop table; the four
|
|
613
|
+
`examples/water-*-3d` templates stage one each (cove pier, resort pool,
|
|
614
|
+
mirror-lake dock, walled garden pond). The default `quality: 'fancy'` is a
|
|
615
|
+
real water shader:
|
|
616
|
+
9-iteration simplex-FBM wave displacement with analytic normals, optional
|
|
617
|
+
DIRECTIONAL TRAVELING SWELL trains + open-water whitecaps (`swell` /
|
|
618
|
+
`whitecaps` — the open-ocean look; see the prop table), THREE
|
|
613
619
|
animated ANISOTROPIC detail-normal layers — wind-stretched (~2.8× along the
|
|
614
620
|
wind), ridge-sharpened fine ripple in crossing directions, each layer
|
|
615
621
|
distance-faded at its own range so the far field goes glassy instead of
|
|
@@ -668,6 +674,12 @@ hue reads grey.
|
|
|
668
674
|
| `detailStrength` | `0.3` | animated detail-normal strength (≥ 0; 0 = vertex normals only) |
|
|
669
675
|
| `absorption` | `0.15` | Beer's-law constant per meter of water depth (≥ 0; lower = clearer/wider turquoise band; red absorbs ~3× faster than blue under the hood) |
|
|
670
676
|
| `refraction` | `true` | screen-space refraction of the submerged scene |
|
|
677
|
+
| `preset` | `"custom"` | **THE one-choice water type** (0.16.0): `ocean` \| `pool` \| `lake` \| `pond` bundles every wave/color/clarity/caustics knob into a hand-tuned sea state. A preset value applies ONLY where a prop still sits at its schema default — pick the type, then override any detail prop and your value wins. ocean = traveling swell + whitecaps + wide turquoise→deep absorption · pool = near-flat crystal water + strong above-water caustics · lake = calm high-reflectivity mirror, green-teal murk · pond = still, mossy, opaque quickly. `custom` (default) changes nothing |
|
|
678
|
+
| `causticsAbove` | `0` | above-water CAUSTICS intensity 0–3 (fancy + `refraction` only): the dancing light web on the submerged bottom seen THROUGH the surface — the pool / tropical-shallows look. Reconstructed on the real refracted bottom position, strongest through the first meters of water, distance-faded. Presets set it (pool 1.0); override freely |
|
|
679
|
+
| `swell` | `0` | **THE open-ocean dial** (fancy only, 0.15.0): directional traveling swell amplitude in meters. Three peaked wave trains (primary + two crossing sets) march across the surface at real deep-water speeds; crests light up toward the peak color and wave FACES get sun-lit/shadow shading, so the swell reads at any distance. 0.15 = lively lake chop, 0.4 = coastal sea, 0.7 = heavy weather. `0` (default) keeps the legacy calm look byte-identical |
|
|
680
|
+
| `swellDirectionDeg` | `0` | compass direction the swell TRAVELS toward (0 = +z, 90 = +x) |
|
|
681
|
+
| `swellWavelength` | `30` | primary swell wavelength, meters (≥ 2, load-time check); the crossing trains derive from it |
|
|
682
|
+
| `whitecaps` | `0` | open-water whitecap foam on the tallest crests, 0–1 (fancy only; load-time check). Pairs with `swell` — the caps ride its crest lines and fade with distance so the horizon stays clean |
|
|
671
683
|
| `underwater` | `true` | submerged-camera look: ONLY when the camera EYE is below this surface (inside its XZ footprint) — wading with the camera above the water never tints the view — the scene switches to short-range underwater fog + tint, the sky is hidden, and animated **caustics** (depth-aware, projected onto the submerged floor/props) play. `false` disables it; an object `{ color?, visibility?, caustics? }` overrides the murk hue / view distance (m, default 22) / caustics. `caustics` is `true` (default) \| `false` \| `{ color?, intensity?, scale?, speed? }`. The murk color defaults to a darkened shade of `color`. Works for both qualities (the caustics composite runs only while the camera is genuinely underwater) |
|
|
672
684
|
|
|
673
685
|
Performance notes (fancy): beyond drawing the surface, each rendered frame
|
|
@@ -953,6 +953,12 @@ Signals: `blocksChanged`
|
|
|
953
953
|
| `absorption` | `0.15` | number |
|
|
954
954
|
| `refraction` | `true` | boolean |
|
|
955
955
|
| `underwater` | `true` | boolean |
|
|
956
|
+
| `swell` | `0` | number |
|
|
957
|
+
| `swellDirectionDeg` | `0` | number |
|
|
958
|
+
| `swellWavelength` | `30` | number |
|
|
959
|
+
| `whitecaps` | `0` | number |
|
|
960
|
+
| `preset` | `"custom"` | one of: `custom` `ocean` `pool` `lake` `pond` |
|
|
961
|
+
| `causticsAbove` | `0` | number |
|
|
956
962
|
|
|
957
963
|
Signals: `entered(body)` · `exited(body)`
|
|
958
964
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
dusk start → each lit ward buys back light → the boss drags the isle to a
|
|
12
12
|
deep-red horizon (17.8 — sun kept ABOVE the horizon; below it the
|
|
13
13
|
atmosphere sky and its IBL go black) → victory brings morning.
|
|
14
|
-
- **Gliding**: hold JUMP while falling — capped descent
|
|
14
|
+
- **Gliding**: hold JUMP while falling — capped descent; landing thumps a dust puff.
|
|
15
15
|
- Keeper NPC tracks the player with `BoneLookAt3D`; shoreline rocks are one
|
|
16
16
|
`InstancedMesh3D` draw call.
|
|
17
17
|
|
|
@@ -178,7 +178,7 @@ const ward = (i: number, [x, y, z]: [number, number, number]): unknown => ({
|
|
|
178
178
|
type: 'MeshInstance3D',
|
|
179
179
|
props: {
|
|
180
180
|
mesh: 'cylinder',
|
|
181
|
-
size: [
|
|
181
|
+
size: [0.8, 1.2, 0.8], // RADIUS (cylinder size[0]) = collider box half-extent
|
|
182
182
|
material: { color: '#7d8494', roughness: 0.9 },
|
|
183
183
|
castShadow: true,
|
|
184
184
|
receiveShadow: true,
|
|
@@ -317,7 +317,7 @@ const scene = {
|
|
|
317
317
|
fog: { near: 110, far: 560 },
|
|
318
318
|
shadows: { mapSize: 2048, radius: 1.2 },
|
|
319
319
|
exposure: 1.12,
|
|
320
|
-
ambient: { color: '#ffe8d4', intensity: 0.
|
|
320
|
+
ambient: { color: '#ffe8d4', intensity: 0.85 },
|
|
321
321
|
iblIntensity: 0.9,
|
|
322
322
|
bloom: { threshold: 1.2, strength: 0.55 },
|
|
323
323
|
post: { vignette: 0.28, saturation: 1.12, contrast: 1.02 },
|
|
@@ -380,7 +380,24 @@ const scene = {
|
|
|
380
380
|
// must keep animating — auditScene flags exactly this mistake.
|
|
381
381
|
children: (terrain.children ?? []).map((c) =>
|
|
382
382
|
c.type === 'Water3D' || c.name === 'Clouds'
|
|
383
|
-
? c
|
|
383
|
+
? c.name === 'Sea'
|
|
384
|
+
? {
|
|
385
|
+
...c,
|
|
386
|
+
// the OPEN-OCEAN dial (0.15.0): directional swell trains +
|
|
387
|
+
// whitecaps + deeper absorption — the sea moves, banded and
|
|
388
|
+
// alive, instead of a flat noise sheet
|
|
389
|
+
props: {
|
|
390
|
+
...(c.props ?? {}),
|
|
391
|
+
swell: 0.55,
|
|
392
|
+
swellWavelength: 42,
|
|
393
|
+
swellDirectionDeg: 262,
|
|
394
|
+
whitecaps: 0.45,
|
|
395
|
+
absorption: 0.28,
|
|
396
|
+
reflectionInterval: 400,
|
|
397
|
+
colors: { trough: '#0c3d58', surface: '#1a7d9c', peak: '#9adfe0' },
|
|
398
|
+
},
|
|
399
|
+
}
|
|
400
|
+
: c
|
|
384
401
|
: { ...c, props: { ...(c.props ?? {}), static: true } },
|
|
385
402
|
),
|
|
386
403
|
},
|
|
@@ -407,7 +424,7 @@ const scene = {
|
|
|
407
424
|
type: 'MeshInstance3D',
|
|
408
425
|
props: {
|
|
409
426
|
mesh: 'cylinder',
|
|
410
|
-
size: [
|
|
427
|
+
size: [1.5, 11, 1.5], // RADIUS = collider half-extent (was 3 → camera clipped inside the 1.5 m shell)
|
|
411
428
|
material: { color: '#e8e2d4', roughness: 0.8 },
|
|
412
429
|
castShadow: true,
|
|
413
430
|
receiveShadow: true,
|
|
@@ -418,7 +435,7 @@ const scene = {
|
|
|
418
435
|
type: 'MeshInstance3D',
|
|
419
436
|
props: {
|
|
420
437
|
mesh: 'cylinder',
|
|
421
|
-
size: [
|
|
438
|
+
size: [1.56, 1.6, 1.56],
|
|
422
439
|
position: [0, 1.2, 0],
|
|
423
440
|
material: { color: '#b8452e', roughness: 0.8 },
|
|
424
441
|
},
|
|
@@ -582,18 +599,6 @@ const scene = {
|
|
|
582
599
|
},
|
|
583
600
|
],
|
|
584
601
|
},
|
|
585
|
-
{
|
|
586
|
-
name: 'DashTrail',
|
|
587
|
-
type: 'Trail3D',
|
|
588
|
-
props: {
|
|
589
|
-
width: 0.5,
|
|
590
|
-
color: '#ffffff',
|
|
591
|
-
opacity: 0.28,
|
|
592
|
-
seconds: 0.45,
|
|
593
|
-
emitting: false,
|
|
594
|
-
position: [0, -0.4, 0],
|
|
595
|
-
},
|
|
596
|
-
},
|
|
597
602
|
{
|
|
598
603
|
name: 'SwingSfx',
|
|
599
604
|
type: 'AudioPlayer',
|
|
@@ -607,7 +612,7 @@ const scene = {
|
|
|
607
612
|
type: 'Node',
|
|
608
613
|
script: {
|
|
609
614
|
name: 'DayNight',
|
|
610
|
-
props: { paused: true, startHour: 17.
|
|
615
|
+
props: { paused: true, startHour: 17.25, nightDarkness: 0.32, maxSunElevationDeg: 48 },
|
|
611
616
|
},
|
|
612
617
|
},
|
|
613
618
|
{
|
|
@@ -21,7 +21,7 @@ import type {
|
|
|
21
21
|
Voice,
|
|
22
22
|
} from 'incanto';
|
|
23
23
|
import { Behavior, findPath } from 'incanto';
|
|
24
|
-
import type { MeshInstance3D, Node3D, Terrain3D, Trail3D } from 'incanto/3d';
|
|
24
|
+
import type { Camera3D, MeshInstance3D, Node3D, Terrain3D, Trail3D } from 'incanto/3d';
|
|
25
25
|
import { buildTerrainNav, Particles3D, type TerrainNav } from 'incanto/3d';
|
|
26
26
|
import { type DayNight, goToScene } from 'incanto/gameplay';
|
|
27
27
|
import gameJsonRaw from './game.scene.json';
|
|
@@ -54,6 +54,39 @@ const PATROL_SPEED = 2.6;
|
|
|
54
54
|
const HUNT_SPEED = 3.6;
|
|
55
55
|
const WARD_LIGHT_RANGE = 4;
|
|
56
56
|
|
|
57
|
+
/** Sprint feel — see sprintFeel(): FOV kick + per-stride dust. */
|
|
58
|
+
const FOV_BASE = 60;
|
|
59
|
+
const FOV_SPRINT = 66.5;
|
|
60
|
+
const STRIDE_SECONDS = 0.22;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* One kicked-up earth puff, world-anchored at the foot spot (one-shot,
|
|
64
|
+
* self-removing). Non-additive expanding dust — NOT a glow: it reads as
|
|
65
|
+
* dirt leaving the ground, then thins out.
|
|
66
|
+
*/
|
|
67
|
+
function spawnFootDust(root: Node, at: [number, number, number], scale = 1): void {
|
|
68
|
+
const puff = new Particles3D('FootDust');
|
|
69
|
+
puff.position = [at[0], at[1], at[2]];
|
|
70
|
+
puff.rate = 0;
|
|
71
|
+
puff.burst = Math.round(4 * scale);
|
|
72
|
+
puff.blend = 'normal';
|
|
73
|
+
puff.lifetime = [0.3, 0.5];
|
|
74
|
+
puff.speed = [8, 34];
|
|
75
|
+
puff.directionDeg = -90; // up out of the ground…
|
|
76
|
+
puff.spreadDeg = 75;
|
|
77
|
+
puff.gravity = [0, 30]; // …then settle back down
|
|
78
|
+
puff.drag = 2.5;
|
|
79
|
+
puff.sizeStart = 11 * scale;
|
|
80
|
+
puff.sizeEnd = 30 * scale; // dust EXPANDS as it dissipates
|
|
81
|
+
puff.colorStart = '#c2b394'; // dry peat kicked into the dusk light
|
|
82
|
+
puff.colorEnd = '#8a7c66';
|
|
83
|
+
puff.alphaStart = 0.24;
|
|
84
|
+
puff.alphaEnd = 0;
|
|
85
|
+
puff.maxParticles = 24;
|
|
86
|
+
puff.on('finished', () => puff.free());
|
|
87
|
+
root.addChild(puff);
|
|
88
|
+
}
|
|
89
|
+
|
|
57
90
|
export class IsleDirector extends Behavior {
|
|
58
91
|
private wind: Voice | null = null;
|
|
59
92
|
private nav: TerrainNav | null = null;
|
|
@@ -63,6 +96,9 @@ export class IsleDirector extends Behavior {
|
|
|
63
96
|
private bobClock = 0;
|
|
64
97
|
private fireworksIn = -1;
|
|
65
98
|
private glideActive = false;
|
|
99
|
+
private strideClock = 0;
|
|
100
|
+
private wasAirborne = false;
|
|
101
|
+
private fov = FOV_BASE;
|
|
66
102
|
|
|
67
103
|
private get dialogue(): UiDialogue {
|
|
68
104
|
return this.node.getNode('HUD/Dialogue') as UiDialogue;
|
|
@@ -139,6 +175,37 @@ export class IsleDirector extends Behavior {
|
|
|
139
175
|
}
|
|
140
176
|
}
|
|
141
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Sprint FEEL — speed you can see instead of a ribbon glued to the hips:
|
|
180
|
+
* - a soft camera FOV kick while at full sprint (eased both ways)
|
|
181
|
+
* - kicked-up earth at the feet, one puff per stride
|
|
182
|
+
* - a bigger thump-puff on landing after airtime (jump or glide)
|
|
183
|
+
*/
|
|
184
|
+
private sprintFeel(dt: number, state: string): void {
|
|
185
|
+
const camera = this.node.getNodeOrNull('Camera') as Camera3D | null;
|
|
186
|
+
if (camera) {
|
|
187
|
+
const target = state === 'fastRun' ? FOV_SPRINT : FOV_BASE;
|
|
188
|
+
this.fov += (target - this.fov) * Math.min(1, dt * 6);
|
|
189
|
+
camera.fov = this.fov;
|
|
190
|
+
}
|
|
191
|
+
const player = this.node.getNodeOrNull('Player') as (Node3D & Positioned) | null;
|
|
192
|
+
if (!player) return;
|
|
193
|
+
const [px, py, pz] = player.position;
|
|
194
|
+
const feet: [number, number, number] = [px ?? 0, (py ?? 0) - 0.62, pz ?? 0];
|
|
195
|
+
const airborne = state === 'airborne';
|
|
196
|
+
if (this.wasAirborne && !airborne) spawnFootDust(this.node, feet, 1.7); // landing thump
|
|
197
|
+
this.wasAirborne = airborne;
|
|
198
|
+
if (state === 'fastRun') {
|
|
199
|
+
this.strideClock -= dt;
|
|
200
|
+
if (this.strideClock <= 0) {
|
|
201
|
+
this.strideClock = STRIDE_SECONDS;
|
|
202
|
+
spawnFootDust(this.node, feet);
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
this.strideClock = 0;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
142
209
|
private ambient(dt: number, engine: Engine | null | undefined): void {
|
|
143
210
|
const controller = this.node.getNode('Player/Controller') as unknown as {
|
|
144
211
|
state: string;
|
|
@@ -152,8 +219,7 @@ export class IsleDirector extends Behavior {
|
|
|
152
219
|
: { pitch: 0.85, volume: 0.2 };
|
|
153
220
|
this.wind.set(w);
|
|
154
221
|
}
|
|
155
|
-
|
|
156
|
-
if (dash && controller) dash.emitting = controller.state === 'fastRun';
|
|
222
|
+
if (controller) this.sprintFeel(dt, controller.state);
|
|
157
223
|
// shades hover-bob and face their travel
|
|
158
224
|
this.bobClock += dt;
|
|
159
225
|
for (const node of this.node.tree?.getNodesInGroup('shade') ?? []) {
|
|
@@ -241,7 +307,7 @@ export class IsleDirector extends Behavior {
|
|
|
241
307
|
? 8.7
|
|
242
308
|
: quest.bossRisen
|
|
243
309
|
? 17.8 // sun ~+2 deg — deep red horizon, still lit (below 18 the sky dies)
|
|
244
|
-
: 17.
|
|
310
|
+
: 17.25 - quest.lit.size * 0.4; // each ward buys back the light
|
|
245
311
|
sky.hour += (targetHour - sky.hour) * Math.min(1, dt * 0.4);
|
|
246
312
|
}
|
|
247
313
|
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"exposure": 1.12,
|
|
23
23
|
"ambient": {
|
|
24
24
|
"color": "#ffe8d4",
|
|
25
|
-
"intensity": 0.
|
|
25
|
+
"intensity": 0.85
|
|
26
26
|
},
|
|
27
27
|
"iblIntensity": 0.9,
|
|
28
28
|
"bloom": {
|
|
@@ -138,7 +138,18 @@
|
|
|
138
138
|
"props": {
|
|
139
139
|
"size": [1920, 1920],
|
|
140
140
|
"position": [0, 9.19, 0],
|
|
141
|
-
"opacity": 1
|
|
141
|
+
"opacity": 1,
|
|
142
|
+
"swell": 0.55,
|
|
143
|
+
"swellWavelength": 42,
|
|
144
|
+
"swellDirectionDeg": 262,
|
|
145
|
+
"whitecaps": 0.45,
|
|
146
|
+
"absorption": 0.28,
|
|
147
|
+
"reflectionInterval": 400,
|
|
148
|
+
"colors": {
|
|
149
|
+
"trough": "#0c3d58",
|
|
150
|
+
"surface": "#1a7d9c",
|
|
151
|
+
"peak": "#9adfe0"
|
|
152
|
+
}
|
|
142
153
|
}
|
|
143
154
|
},
|
|
144
155
|
{
|
|
@@ -1451,7 +1462,7 @@
|
|
|
1451
1462
|
"type": "MeshInstance3D",
|
|
1452
1463
|
"props": {
|
|
1453
1464
|
"mesh": "cylinder",
|
|
1454
|
-
"size": [
|
|
1465
|
+
"size": [1.5, 11, 1.5],
|
|
1455
1466
|
"material": {
|
|
1456
1467
|
"color": "#e8e2d4",
|
|
1457
1468
|
"roughness": 0.8
|
|
@@ -1465,7 +1476,7 @@
|
|
|
1465
1476
|
"type": "MeshInstance3D",
|
|
1466
1477
|
"props": {
|
|
1467
1478
|
"mesh": "cylinder",
|
|
1468
|
-
"size": [
|
|
1479
|
+
"size": [1.56, 1.6, 1.56],
|
|
1469
1480
|
"position": [0, 1.2, 0],
|
|
1470
1481
|
"material": {
|
|
1471
1482
|
"color": "#b8452e",
|
|
@@ -1602,7 +1613,7 @@
|
|
|
1602
1613
|
"type": "MeshInstance3D",
|
|
1603
1614
|
"props": {
|
|
1604
1615
|
"mesh": "cylinder",
|
|
1605
|
-
"size": [
|
|
1616
|
+
"size": [0.8, 1.2, 0.8],
|
|
1606
1617
|
"material": {
|
|
1607
1618
|
"color": "#7d8494",
|
|
1608
1619
|
"roughness": 0.9
|
|
@@ -1690,7 +1701,7 @@
|
|
|
1690
1701
|
"type": "MeshInstance3D",
|
|
1691
1702
|
"props": {
|
|
1692
1703
|
"mesh": "cylinder",
|
|
1693
|
-
"size": [
|
|
1704
|
+
"size": [0.8, 1.2, 0.8],
|
|
1694
1705
|
"material": {
|
|
1695
1706
|
"color": "#7d8494",
|
|
1696
1707
|
"roughness": 0.9
|
|
@@ -1778,7 +1789,7 @@
|
|
|
1778
1789
|
"type": "MeshInstance3D",
|
|
1779
1790
|
"props": {
|
|
1780
1791
|
"mesh": "cylinder",
|
|
1781
|
-
"size": [
|
|
1792
|
+
"size": [0.8, 1.2, 0.8],
|
|
1782
1793
|
"material": {
|
|
1783
1794
|
"color": "#7d8494",
|
|
1784
1795
|
"roughness": 0.9
|
|
@@ -2667,18 +2678,6 @@
|
|
|
2667
2678
|
}
|
|
2668
2679
|
]
|
|
2669
2680
|
},
|
|
2670
|
-
{
|
|
2671
|
-
"name": "DashTrail",
|
|
2672
|
-
"type": "Trail3D",
|
|
2673
|
-
"props": {
|
|
2674
|
-
"width": 0.5,
|
|
2675
|
-
"color": "#ffffff",
|
|
2676
|
-
"opacity": 0.28,
|
|
2677
|
-
"seconds": 0.45,
|
|
2678
|
-
"emitting": false,
|
|
2679
|
-
"position": [0, -0.4, 0]
|
|
2680
|
-
}
|
|
2681
|
-
},
|
|
2682
2681
|
{
|
|
2683
2682
|
"name": "SwingSfx",
|
|
2684
2683
|
"type": "AudioPlayer",
|
|
@@ -2705,8 +2704,8 @@
|
|
|
2705
2704
|
"name": "DayNight",
|
|
2706
2705
|
"props": {
|
|
2707
2706
|
"paused": true,
|
|
2708
|
-
"startHour": 17.
|
|
2709
|
-
"nightDarkness": 0.
|
|
2707
|
+
"startHour": 17.25,
|
|
2708
|
+
"nightDarkness": 0.32,
|
|
2710
2709
|
"maxSunElevationDeg": 48
|
|
2711
2710
|
}
|
|
2712
2711
|
}
|
|
@@ -19,7 +19,7 @@ again button.
|
|
|
19
19
|
| `UiDialogue` + choices | the Elder conversation IS the quest state machine |
|
|
20
20
|
| `UiButton` | the play-again button |
|
|
21
21
|
| `findPath` + `PathFollow` | wolf patrol rings through the hedge maze |
|
|
22
|
-
| `Trail3D` | sword arc
|
|
22
|
+
| `Trail3D` | sword arc |
|
|
23
23
|
| `engine.sfx.startVoice('wind')` | live ambience that rises as you run |
|
|
24
24
|
| `goToScene` | village ⇄ grove, with the black fade |
|
|
25
25
|
| controller `animations` map | idle/walk/run/sprint/jump — zero animation code |
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
## Feel
|
|
27
27
|
|
|
28
28
|
- Wind ambience always present, louder + higher while sprinting.
|
|
29
|
-
-
|
|
29
|
+
- Sprint FEEL: per-stride foot-dust puffs + a soft camera FOV kick (no body ribbon).
|
|
30
30
|
- Village = warm sunset; grove = cold dusk, denser fog, moonlight.
|
|
31
31
|
|
|
32
32
|
## Verification (must stay green)
|
|
@@ -4,11 +4,16 @@
|
|
|
4
4
|
index.html canvas + loading bar
|
|
5
5
|
src/main.ts preload GLB/anim assets → createGame3D (village)
|
|
6
6
|
src/village.scene.json sunset village: Elder (Interactable), gate Area3D,
|
|
7
|
-
player rig (controller `animations` map, Trail3D
|
|
7
|
+
player rig (controller `animations` map, sword Trail3D),
|
|
8
8
|
HUD (UiText/UiBanner/UiDialogue/UiButton)
|
|
9
9
|
src/grove.scene.json dusk grove: 3 wolves (PathFollow), return portal,
|
|
10
10
|
empty `Maze` holder (hedges are built at runtime)
|
|
11
11
|
src/behaviors.ts GROVE_GRID + VillageDirector / GroveDirector / SwordStrike
|
|
12
|
+
generate-dressing.ts VILLAGE dressing generator: rebuilds environment + the
|
|
13
|
+
visual `Village` subtree (timber cottages, fences,
|
|
14
|
+
lanterns, stall, grass/flowers, treelines) — edit
|
|
15
|
+
numbers, `bun generate-dressing.ts`, reload. Gameplay
|
|
16
|
+
nodes (Elder/Player/HUD/GroveGate) are preserved.
|
|
12
17
|
src/quest.ts cross-scene quest flags (module singleton)
|
|
13
18
|
verify.ts audit + headless quest E2E + replay determinism
|
|
14
19
|
```
|