PageSourceSearch

https://celexly.com/home-worlds.js

js celexly.com collected 2026-10-02 01:25:28 UTC 102,364 bytes, 2,164 lines download raw bytes

1/* Celexly home: a small living world at the foot of every band.
2   Each one is a 3D diorama built on the engine in home-3d.js, and the same
3   figure, WuYuXiang, walks through all of them, so scrolling the page reads
4   as one person moving through the year rather than a set of unrelated
5   backdrops. Scroll position drives where he is in each scene: he walks in
6   from the left as a band arrives, stops to watch while it holds the screen,
7   and walks out to the right as the next season takes over. */
8(function () {
9  "use strict";
10
11  var E = window.CLX3D;
12  if (!E) return;
13  var reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
14
15  var C = E.hex2rgb;
16  function shade(c, k) { return [c[0] * k, c[1] * k, c[2] * k]; }
17
18  /* The last click, in world coordinates, decaying. Only one scene builds at
19     a time, so a single shared slot is enough and every scene can read it. */
20  var ACT = { pulse: 0, x: 0 };
21
22  /* deterministic scatter so a resize never reshuffles a scene */
23  function rnd(i, k) {
24    var x = Math.sin(i * 127.1 + k * 311.7) * 43758.5453;
25    return x - Math.floor(x);
26  }
27
28  /* ---------------------------------------------------------------- parts */
29
30  /* The floor of the world: a long slab lying in the XZ plane. */
31  function ground(m, P, x0, x1, z0, z1) {
32    var w = (x1 - x0) / 2, d = (z1 - z0) / 2, cx = (x0 + x1) / 2, cz = (z0 + z1) / 2;
33    E.extrude(m, E.roundRect(w, d, 14, 2), -9, 9, P.soilSide, P.soil,
34      function (x, y, z) { return [cx + x, 9 + z, cz + y]; }, { bias: -400 });
35    /* the surface, laid just proud of the earth under it */
36    E.extrude(m, E.roundRect(w - 2, d - 2, 13, 2), -11, -9.4, P.turf, P.turf,
37      function (x, y, z) { return [cx + x, 9 + z, cz + y]; }, { bias: -396 });
38
39    /* a ring going out from wherever it was last touched */
40    if (ACT.pulse > 0.02) {
41      var rr = (1 - ACT.pulse) * 190 + 8;
42      var prev = null;
43      for (var q = 0; q <= 26; q++) {
44        var a = (q / 26) * Math.PI * 2;
45        var pt = [ACT.x + Math.cos(a) * rr, -2, 18 + Math.sin(a) * rr * 0.42];
46        if (prev) m.line(prev, pt, P.ring || [255, 255, 255], 2, ACT.pulse * 0.5);
47        prev = pt;
48      }
49      /* and a few things kicked up out of the ground */
50      for (q = 0; q < 8; q++) {
51        var kf = 1 - ACT.pulse;
52        var ka = (q / 8) * Math.PI * 2;
53        E.box(m, ACT.x + Math.cos(ka) * kf * 70,
54              -6 - Math.sin(ACT.pulse * Math.PI) * 34,
55              18 + Math.sin(ka) * kf * 30,
56              2, 2, 2, P.turf, null, 60, 0.8);
57      }
58    }
59  }
60
61  /* A tapering trunk, stacked so it narrows as it rises. */
62  function trunk(m, x, z, h, w, colA, colB, lean) {
63    var seg = 5, i;
64    for (i = 0; i < seg; i++) {
65      var f = i / seg, f2 = (i + 1) / seg;
66      var ww = w * (1 - f * 0.45);
67      E.box(m, x + (lean || 0) * f * h, -h * f - h / (seg * 2), z,
68            ww, h / (seg * 2) + 0.6, ww, i % 2 ? colA : colB, null, 0, ww * 0.3);
69    }
70  }
71
72  /* A canopy of overlapping blobs, breathing on the wind. */
73  function canopy(m, x, y, z, r, cols, t, seed) {
74    var n = 5, i;
75    for (i = 0; i < n; i++) {
76      var a = (i / n) * Math.PI * 2 + seed;
77      var rr = r * (0.5 + rnd(seed + i, 1) * 0.36);
78      var sway = Math.sin(t * 0.9 + seed + i) * 2.2;
79      E.gem(m, rr,
80            1,
81            x + Math.cos(a) * r * 0.62 + sway,
82            y + (rnd(seed + i, 2) - 0.5) * r * 0.5,
83            z + Math.sin(a) * r * 0.45,
84            cols[i % cols.length], null);
85    }
86    E.gem(m, r * 0.72, 1, x, y, z, cols[0], null);
87  }
88
89  function blobTree(m, x, z, h, r, cols, bark, t, seed) {
90    trunk(m, x, z, h, 5.5, bark[0], bark[1], 0);
91    canopy(m, x, -h - r * 0.5, z, r, cols, t, seed);
92  }
93
94  /* A conifer: stacked tiers, snow optional. */
95  function pineTree(m, x, z, h, col, dark, snow) {
96    trunk(m, x, z, h * 0.34, 3.6, bark0, bark1, 0);
97    var tiers = 4, i;
98    for (i = 0; i < tiers; i++) {
99      var f = i / tiers;
100      var y = -h * 0.3 - f * h * 0.62;
101      var rr = 20 * (1 - f * 0.62);
102      E.extrude(m, E.disc(rr, 9), -5, 5, i % 2 ? col : dark, col,
103        function (px, py, pz) { return [x + px, y + pz, z + py]; });
104      if (snow) {
105        E.extrude(m, E.disc(rr * 0.72, 9), -6.6, -5.2, [320, 326, 334], [330, 334, 340],
106          function (px, py, pz) { return [x + px, y + pz, z + py]; }, { bias: 30 });
107      }
108    }
109  }
110  var bark0 = [122, 86, 62], bark1 = [96, 66, 46];
111
112  /* A low dome. A sphere squashed by eye reads as a rock; stacked discs
113     read as something settled on the ground. */
114  function dome(m, x, z, r, h, col, top) {
115    var tiers = 8, i;
116    for (i = 0; i < tiers; i++) {
117      var f = i / tiers, f2 = (i + 1) / tiers;
118      var rr = r * Math.sqrt(Math.max(0.05, 1 - f * f));
119      E.extrude(m, E.disc(rr, 14), -f2 * h, -f * h, col, top || col,
120        function (px, py, pz) { return [x + px, pz, z + py]; }, { bias: -22 + i });
121    }
122  }
123
124  /* Falling matter: petals, leaves or snow, each drifting on its own curl. */
125  function fall(m, t, cols, n, size, speed, x0, x1, spin) {
126    for (var i = 0; i < n; i++) {
127      var ph = rnd(i, 7);
128      var y = -((t * speed + ph * 200) % 200);
129      var x = x0 + rnd(i, 8) * (x1 - x0) + Math.sin(t * 0.8 + ph * 9) * 16;
130      var z = -70 + rnd(i, 9) * 140;
131      var a = t * spin + ph * 7;
132      var ca = Math.cos(a), sa = Math.sin(a);
133      var s = size * (0.7 + rnd(i, 10) * 0.6);
134      var c = cols[i % cols.length];
135      m.face([
136        [x - s * ca, y - s * 0.5, z - s * sa],
137        [x + s * ca, y - s * 0.3, z + s * sa],
138        [x + s * ca, y + s * 0.5, z + s * sa],
139        [x - s * ca, y + s * 0.3, z - s * sa]
140      ], c, { bias: 120 });
141    }
142  }
143
144  /* ------------------------------------------------------------- the figure
145     WuYuXiang. One skeleton, standing or seated, with a looping action per
146     world: watching the blossom, raking leaves, sipping something hot. He
147     stays put; the season is what changes around him. */
148  function person(m, x, z, t, P, act, face) {
149    var seated = act === "sip" || act === "warm" || act === "tea" || act === "work";
150    var crouch = act === "feed";
151    var breathe = Math.sin(t * 1.5) * 0.9;
152    var base = -breathe;
153
154    /* Everything is built facing the viewer and then turned, so walking can
155       point him along his travel instead of sliding him sideways while he
156       stares out of the screen. */
157    var _cf = Math.cos(face || 0), _sf = Math.sin(face || 0);
158    function part(dx, dy, dz, hx, hy, hz, col, bias, r) {
159      E.box(m, x + dx * _cf - dz * _sf, base - dy, z + dz * _cf + dx * _sf,
160            hx, hy, hz, col, null,
161            bias === undefined ? 20 : bias, r === undefined ? Math.min(hx, hy) * 0.3 : r);
162    }
163    function head(dy, tilt, lean) {
164      part(0, dy - 8, 0, 3, 2.5, 3, P.skin, 24, 0.9);
165      part(0, dy, lean, 8, 7.4, 7, P.skin, 26, 2.4);
166      part(0, dy + 6, lean + 0.6, 8.6, 3.3, 7.6, P.hair, 38, 2);
167      part(0, dy + 1.5, -6.6 + lean, 8.2, 2.9, 1.2, P.hair, 42, 1.4);
168      part(-3.2, dy - 0.5 + tilt, -7.4 + lean, 1.3, 1.7, 0.6, P.eye, 56, 0.4);
169      part(3.2, dy - 0.5 + tilt, -7.4 + lean, 1.3, 1.7, 0.6, P.eye, 56, 0.4);
170      if (P.scarf) {
171        part(0, dy - 5, 0, 8.4, 2.6, 7.4, P.scarf, 44, 1.6);
172        part(5, dy - 11, -6, 2, 5, 1.2, P.scarf, 46, 0.8);
173      }
174      m.anchor("head", [x, base - dy - 14, z]);
175    }
176    /* A two piece limb hung off a fixed shoulder. `sw` swings the upper arm
177       forward from straight down, `el` bends the forearm on from there, both
178       in radians, so the elbow always meets and the hand lands where the two
179       angles say it should. Returns the hand, for anything being held. */
180    function arm(side, sw, el, lift) {
181      var sx = side * 10.5, sy = 30, U = 7, F = 6.5;
182      var ex = sx, ey = sy - (lift || 0) - Math.cos(sw) * U, ez = -Math.sin(sw) * U;
183      var a2 = sw + el;
184      var hx2 = ex, hy2 = ey - Math.cos(a2) * F, hz2 = ez - Math.sin(a2) * F;
185      /* upper arm, at the midpoint of shoulder to elbow */
186      part(sx, (sy + ey) / 2 - (lift || 0) * 0.5, ez / 2, 2.3, U / 2 + 0.7, 2.3, P.skin, 24, 0.9);
187      /* forearm, at the midpoint of elbow to hand */
188      part(ex, (ey + hy2) / 2, (ez + hz2) / 2, 2.1, F / 2 + 0.6, 2.1, P.skin, 26, 0.9);
189      return [hx2, hy2, hz2];
190    }
191
192    if (crouch) {
193      /* down on his heels at the water */
194      var dip = Math.max(0, Math.sin(t * 0.9));
195      part(-4.5, 4, -3, 3.3, 4, 5, P.trouser, 20, 1);
196      part(4.5, 4, -3, 3.3, 4, 5, P.trouser, 20, 1);
197      part(0, 14, 0, 8.2, 7, 5.4, P.coat, 24, 2);
198      part(0, 12, -5.4, 5.8, 5, 0.9, P.coatIn, 40, 1.6);
199      part(-10, 13, -2, 2.3, 5, 2.3, P.skin, 26, 0.9);
200      part(10, 12 - dip * 6, -6 - dip * 5, 2.3, 5.5, 2.3, P.skin, 26, 0.9);
201      head(26, 1.2, 1.6);
202      return;
203    }
204
205    if (seated) {
206      part(-4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1);
207      part(4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1);
208      part(-4.5, 10, -11, 3.2, 4.6, 3.2, P.trouser, 22, 1);
209      part(4.5, 10, -11, 3.2, 4.6, 3.2, P.trouser, 22, 1);
210      part(0, 18, 0, 8.5, 8, 5.5, P.coat, 24, 2);
211      part(0, 16, -5.4, 6, 5.6, 0.9, P.coatIn, 40, 1.6);
212
213      if (act === "warm") {
214        /* both palms held out to the fire, turning slowly */
215        var wv = Math.sin(t * 1.2) * 0.16;
216        part(-8, 20 + wv * 8, -10, 2.3, 4.4, 2.3, P.skin, 26, 0.9);
217        part(8, 20 - wv * 8, -10, 2.3, 4.4, 2.3, P.skin, 26, 0.9);
218        part(-8, 20 + wv * 8, -15, 2, 1.6, 2.6, P.skin, 28, 0.8);
219        part(8, 20 - wv * 8, -15, 2, 1.6, 2.6, P.skin, 28, 0.8);
220        head(30, 0, 0.8);
221        return;
222      }
223      if (act === "work") {
224        /* tapping at something on the bench in front of him */
225        var tap = Math.sin(t * 6) * 1.2 + Math.sin(t * 9.3) * 0.6;
226        part(-7, 19, -10, 2.3, 4.2, 2.3, P.skin, 26, 0.9);
227        part(7, 19 + tap, -10, 2.3, 4.2, 2.3, P.skin, 26, 0.9);
228        part(-7, 16, -14, 2, 1.4, 2.4, P.skin, 28, 0.8);
229        part(7, 16 + tap, -14, 2, 1.4, 2.4, P.skin, 28, 0.8);
230        head(30, 1.4, 1.2);
231        return;
232      }
233      /* sip and tea: the cup comes up to his face, then back down */
234      var cyc = (t * 0.28) % 1;
235      var lift = cyc < 0.34 ? cyc / 0.34 : cyc < 0.52 ? 1 : Math.max(0, 1 - (cyc - 0.52) / 0.3);
236      var cupY = 20 + lift * 9, cupZ = -8.5 + lift * 2.4;
237      part(-9, 18 + lift * 3, -6 - lift * 1.5, 2.3, 4.6, 2.3, P.skin, 26, 0.9);
238      part(9, 18 + lift * 3, -6 - lift * 1.5, 2.3, 4.6, 2.3, P.skin, 26, 0.9);
239      part(0, cupY, cupZ, 3.2, 3, 3, P.mug, 44, 1);
240      part(3.6, cupY, cupZ, 1, 1.4, 0.8, P.mug, 44, 0.4);
241      for (var q = 0; q < 5; q++) {
242        var sp = ((t * 0.7 + q * 0.2) % 1);
243        part(Math.sin(sp * 7 + q) * 3.4, cupY + 4 + sp * 16, cupZ,
244             1 - sp * 0.5, 1 - sp * 0.5, 1 - sp * 0.5, P.steam, 48, 0.5);
245      }
246      head(30 + lift * 1.2, lift * 1.6, 0);
247      return;
248    }
249
250    /* --- standing --- */
251    part(-4.5, 5, 0, 3.3, 5, 3.3, P.boot, 18, 1);
252    part(4.5, 5, 0, 3.3, 5, 3.3, P.boot, 18, 1);
253    part(-4.5, 13, 0, 3.2, 4.6, 3.2, P.trouser, 18, 1);
254    part(4.5, 13, 0, 3.2, 4.6, 3.2, P.trouser, 18, 1);
255    part(0, 25, 0, 8.5, 8.5, 5.5, P.coat, 22, 2);
256    part(0, 23, -5.4, 6, 6, 0.9, P.coatIn, 38, 1.6);
257
258    if (act === "rake") {
259      /* A stroke, not a wave. He reaches the head out, drags it back toward
260         his feet, then resets, and the leaves come with it. */
261      var cyc = (t * 0.55) % 1;
262      var pull = cyc < 0.55
263        ? 1 - Math.pow(1 - cyc / 0.55, 2)     /* the drag, quick then easing */
264        : 1 - (cyc - 0.55) / 0.45;            /* reaching back out */
265      var far = 34 - pull * 24;               /* how far out the head is */
266      var lean = pull * 0.22;
267
268      arm(-1, 0.5 + pull * 0.5, 0.5 - pull * 0.3, 0);
269      arm(1, 0.95 - pull * 0.5, 0.45 - pull * 0.2, 0);
270      /* the shaft runs from his hands down to the head on the ground */
271      var hxp = x + 10, hy0 = base - 26, hz0 = -8;
272      var hz1 = -far, hy1 = base - 2;
273      for (var sg = 0; sg < 6; sg++) {
274        var f2 = sg / 5;
275        E.box(m, hxp, hy0 + (hy1 - hy0) * f2, z + hz0 + (hz1 - hz0) * f2,
276              1.2, 2.4, 1.2, P.tool, null, 24, 0.5);
277      }
278      E.box(m, hxp, base - 3, z + hz1, 9, 1.2, 1.4, P.tool, null, 24, 0.5);
279      for (var ti = -3; ti <= 3; ti++) {
280        E.box(m, hxp + ti * 2.8, base - 1.4, z + hz1, 0.8, 2.4, 0.8, P.tool, null, 24, 0.3);
281      }
282      /* leaves gathered in front of the tines, piling as the stroke lands */
283      for (var lf = 0; lf < 10; lf++) {
284        var lo = (lf % 5) - 2;
285        E.box(m, hxp + lo * 3.4 + (lf > 4 ? 1.6 : 0),
286              base - 1.2 - (lf > 4 ? 2.2 : 0) - pull * 1.6,
287              z + hz1 - 5 - (lf > 4 ? 2 : 0),
288              2.6, 1, 2, lf % 2 ? [214, 96, 48] : [232, 150, 62], null, 22, 0.8);
289      }
290      head(38, 0.8, 0.4 + lean * 4);
291      return;
292    }
293    if (act === "light") {
294      /* reaching up to a lantern, checking the flame */
295      var rr = 0.5 + 0.5 * Math.sin(t * 0.8);
296      arm(-1, 0.35, 0.35, 0);
297      arm(1, -2.5 * rr - 0.2, 0.5 - rr * 0.3, 0);
298      head(38, -0.8 * rr, 1.4 * rr);
299      return;
300    }
301    if (act === "walk") {
302      /* getting from one place to the next, legs and arms opposed */
303      var step = Math.sin(t * 5.4);
304      part(-4.5 + step * 2, 5, step * 3.4, 3.3, 5, 3.3, P.boot, 18, 1);
305      part(4.5 - step * 2, 5, -step * 3.4, 3.3, 5, 3.3, P.boot, 18, 1);
306      part(-4.5 + step * 1.4, 13, step * 2.2, 3.2, 4.6, 3.2, P.trouser, 18, 1);
307      part(4.5 - step * 1.4, 13, -step * 2.2, 3.2, 4.6, 3.2, P.trouser, 18, 1);
308      part(0, 25, 0, 8.5, 8.5, 5.5, P.coat, 22, 2);
309      part(0, 23, -5.4, 6, 6, 0.9, P.coatIn, 38, 1.6);
310      arm(-1, 0.3 - step * 0.5, 0.4, 0);
311      arm(1, 0.3 + step * 0.5, 0.4, 0);
312      head(38, 0, 0.2);
313      return;
314    }
315    if (act === "stretch") {
316      /* reaching for the pack as he comes up */
317      /* sitting up out of sleep, both arms reaching over his head */
318      var up3 = Math.min(1, Math.max(0, Math.sin(t * 0.9) * 0.5 + 0.5));
319      part(-4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1);
320      part(4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1);
321      part(-4.5, 9, -12, 3.2, 3.4, 3.2, P.trouser, 22, 1);
322      part(4.5, 9, -12, 3.2, 3.4, 3.2, P.trouser, 22, 1);
323      part(0, 18, 0, 8.5, 8, 5.5, P.coat, 24, 2);
324      part(0, 16, -5.4, 6, 5.6, 0.9, P.coatIn, 40, 1.6);
325      arm(-1, -2.6 * up3 - 0.2, 0.2, 6);
326      arm(1, -2.6 * up3 - 0.2, 0.2, 6);
327      head(30, -up3 * 1.4, -up3 * 2);
328      return;
329    }
330    if (act === "feedfire") {
331      /* leaning in to lay a log on, then sitting back off the heat */
332      var lay2 = Math.max(0, Math.sin(t * 1.6));
333      part(-4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1);
334      part(4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1);
335      part(-4.5, 10, -11, 3.2, 4.6, 3.2, P.trouser, 22, 1);
336      part(4.5, 10, -11, 3.2, 4.6, 3.2, P.trouser, 22, 1);
337      part(0, 18 - lay2 * 2, -lay2 * 4, 8.5, 8, 5.5, P.coat, 24, 2);
338      part(0, 16 - lay2 * 2, -5.4 - lay2 * 4, 6, 5.6, 0.9, P.coatIn, 40, 1.6);
339      part(-7, 17 - lay2 * 6, -11 - lay2 * 8, 2.3, 4.4, 2.3, P.skin, 26, 0.9);
340      part(7, 17 - lay2 * 6, -11 - lay2 * 8, 2.3, 4.4, 2.3, P.skin, 26, 0.9);
341      /* the log going on */
342      part(0, 15 - lay2 * 7, -16 - lay2 * 10, 5, 1.8, 1.8, [122, 86, 62], 30, 0.8);
343      head(30 - lay2 * 2, 1.2, 1.2 + lay2 * 2);
344      return;
345    }
346    if (act === "roll") {
347      /* both hands on the pin, pushing it out and drawing it back */
348      var rl = Math.sin(t * 2.6);
349      part(-4.5, 5, 0, 3.3, 5, 3.3, P.boot, 18, 1);
350      part(4.5, 5, 0, 3.3, 5, 3.3, P.boot, 18, 1);
351      part(-4.5, 13, 0, 3.2, 4.6, 3.2, P.trouser, 18, 1);
352      part(4.5, 13, 0, 3.2, 4.6, 3.2, P.trouser, 18, 1);
353      part(0, 24, 1.5 + rl * 1.5, 8.5, 8.5, 5.5, P.coat, 22, 2);
354      part(0, 22, -4 + rl * 1.5, 6, 6, 0.9, P.coatIn, 38, 1.6);
355      /* arms out over the board, moving with the pin */
356      part(-8, 22, -10 - rl * 5, 2.3, 5.4, 2.3, P.skin, 26, 0.9);
357      part(8, 22, -10 - rl * 5, 2.3, 5.4, 2.3, P.skin, 26, 0.9);
358      /* the pin, and its handles */
359      part(0, 18, -14 - rl * 6, 7, 1.8, 1.8, P.tool, 30, 0.9);
360      part(-8.5, 18, -14 - rl * 6, 1.6, 1.1, 1.1, P.tool, 30, 0.5);
361      part(8.5, 18, -14 - rl * 6, 1.6, 1.1, 1.1, P.tool, 30, 0.5);
362      head(38, 1.4, 1.2);
363      return;
364    }
365    if (act === "peel") {
366      /* the long peel held out level, in or out of the oven */
367      var pv = Math.sin(t * 1.5) * 0.12;
368      part(-4.5, 5, 0, 3.3, 5, 3.3, P.boot, 18, 1);
369      part(4.5, 5, 0, 3.3, 5, 3.3, P.boot, 18, 1);
370      part(-4.5, 13, 0, 3.2, 4.6, 3.2, P.trouser, 18, 1);
371      part(4.5, 13, 0, 3.2, 4.6, 3.2, P.trouser, 18, 1);
372      part(0, 25, 0, 8.5, 8.5, 5.5, P.coat, 22, 2);
373      part(0, 23, -5.4, 6, 6, 0.9, P.coatIn, 38, 1.6);
374      arm(-1, -1.5 + pv, 1.5, 0);
375      arm(1, -1.5 - pv, 1.5, 0);
376      /* the shaft, running out level, and the blade on the end of it */
377      for (var pq = 0; pq < 7; pq++) {
378        part(2, 27 + pv * 6, -12 - pq * 6, 1, 1, 3.4, P.tool, 30, 0.4);
379      }
380      part(2, 27 + pv * 6, -56, 6, 0.8, 6, P.tool, 30, 0.6);
381      if (P.onPeel) {
382        part(2, 25 + pv * 6, -56, 4, 2.2, 3.2, P.loaf || [206, 156, 96], 34, 1.6);
383      }
384      head(38, 0.8, 1);
385      return;
386    }
387    if (act === "fish") {
388      /* Sat on the bank with the rod well out over the water. Nothing is
389         hauled up: the float just sits there and rings go out from it when
390         something touches the line. */
391      var idle = Math.sin(t * 0.7) * 0.5;
392      var nudge = Math.max(0, Math.sin(t * 0.5));
393      part(-4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1);
394      part(4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1);
395      part(-4.5, 10, -11, 3.2, 4.6, 3.2, P.trouser, 22, 1);
396      part(4.5, 10, -11, 3.2, 4.6, 3.2, P.trouser, 22, 1);
397      part(0, 18, 0, 8.5, 8, 5.5, P.coat, 24, 2);
398      part(0, 16, -5.4, 6, 5.6, 0.9, P.coatIn, 40, 1.6);
399      part(0, 26, 6, 5, 6, 2.4, P.rag || [72, 74, 66], 22, 1);
400      part(-8, 19, -8, 2.3, 4.4, 2.3, P.skin, 26, 0.9);
401      part(8, 19, -9, 2.3, 4.4, 2.3, P.skin, 26, 0.9);
402      /* The rod runs out to his right, because that is where the water is:
403         he sits on the left bank and the pond opens away from him. Running it
404         toward the viewer put the float down on the grass. */
405      for (var rq = 0; rq < 14; rq++) {
406        var rf = rq / 14;
407        part(8 + rf * 74, 22 + rf * 8 + idle * rf, -6 - rf * 10,
408             1.8, 0.8, 0.8, P.tool || [138, 100, 70], 30, 0.3);
409      }
410      /* the line, straight down from the tip to a float on the surface */
411      var fx = 84, fz = -16;
412      for (var lq = 0; lq < 9; lq++) {
413        part(fx, 28 - lq * 3, fz, 0.28, 1.6, 0.28, [222, 226, 232], 28, 0.15);
414      }
415      part(fx, 4, fz, 1.5, 1.5, 1.5, [214, 74, 62], 30, 0.6);
416      part(fx, 6.6, fz, 1.1, 1.1, 1.1, [246, 246, 250], 30, 0.4);
417      /* rings going out from it, two of them, always running */
418      for (var rr3 = 0; rr3 < 2; rr3++) {
419        var rp3 = ((t * 0.45 + rr3 * 0.5) % 1);
420        var rad3 = 3 + rp3 * 26;
421        var prev3 = null;
422        for (var aq = 0; aq <= 20; aq++) {
423          var aa3 = (aq / 20) * Math.PI * 2;
424          var pt3 = [x + fx + Math.cos(aa3) * rad3, base - 3.2,
425                     z + fz + Math.sin(aa3) * rad3 * 0.45];
426          if (prev3) m.line(prev3, pt3, [206, 232, 242], 1.5, (1 - rp3) * 0.75);
427          prev3 = pt3;
428        }
429      }
430      head(38, 0, 1.4 + nudge * 0.3);
431      return;
432    }
433    if (act === "plant") {
434      /* crouched, both hands down, settling something into the ground */
435      var pd = 0.5 + 0.5 * Math.sin(t * 2.4);
436      part(-4.5, 4, -3, 3.3, 4, 5, P.trouser, 20, 1);
437      part(4.5, 4, -3, 3.3, 4, 5, P.trouser, 20, 1);
438      part(0, 14, 0, 8.2, 7, 5.4, P.coat, 24, 2);
439      part(0, 12, -5.4, 5.8, 5, 0.9, P.coatIn, 40, 1.6);
440      part(-7, 11 - pd * 3, -9, 2.3, 5, 2.3, P.skin, 26, 0.9);
441      part(7, 11 - pd * 3, -9, 2.3, 5, 2.3, P.skin, 26, 0.9);
442      head(26, 1.4, 1.8);
443      return;
444    }
445    if (act === "eat") {
446      /* the apple comes up to his mouth and goes back down, a bite at a time */
447      var bite = (t * 0.8) % 1;
448      var up2 = bite < 0.45 ? bite / 0.45 : Math.max(0, 1 - (bite - 0.45) / 0.35);
449      part(-4.5, 5, 0, 3.3, 5, 3.3, P.boot, 18, 1);
450      part(4.5, 5, 0, 3.3, 5, 3.3, P.boot, 18, 1);
451      part(-4.5, 13, 0, 3.2, 4.6, 3.2, P.trouser, 18, 1);
452      part(4.5, 13, 0, 3.2, 4.6, 3.2, P.trouser, 18, 1);
453      part(0, 25, 0, 8.5, 8.5, 5.5, P.coat, 22, 2);
454      part(0, 23, -5.4, 6, 6, 0.9, P.coatIn, 38, 1.6);
455      arm(-1, 0.3, 0.35, 0);
456      arm(1, -0.6 - up2 * 1.1, 0.8 - up2 * 0.35, 0);
457      /* the apple itself, in his hand */
458      part(9, 30 + up2 * 8, -7 - up2 * 2, 2.2 - up2 * 0.4, 2.2 - up2 * 0.4, 2.2,
459           [206, 62, 54], 60, 1);
460      part(9, 32.6 + up2 * 8, -7 - up2 * 2, 0.5, 1, 0.5, [96, 128, 70], 60, 0.3);
461      head(38, up2 * 0.8, 0.6 + up2 * 0.8);
462      return;
463    }
464    if (act === "code") {
465      /* sat at the bench, hands on the keys, head down at the screen */
466      var tap2 = Math.sin(t * 7) * 0.09 + Math.sin(t * 11.3) * 0.05;
467      part(-4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1);
468      part(4.5, 5, -6, 3.4, 3.4, 6.5, P.trouser, 20, 1);
469      part(-4.5, 10, -11, 3.2, 4.6, 3.2, P.trouser, 22, 1);
470      part(4.5, 10, -11, 3.2, 4.6, 3.2, P.trouser, 22, 1);
471      part(0, 18, 0, 8.5, 8, 5.5, P.coat, 24, 2);
472      part(0, 16, -5.4, 6, 5.6, 0.9, P.coatIn, 40, 1.6);
473      part(-7, 19 + tap2 * 8, -11, 2.2, 4.2, 2.2, P.skin, 26, 0.9);
474      part(7, 19 - tap2 * 8, -11, 2.2, 4.2, 2.2, P.skin, 26, 0.9);
475      part(-7, 16 + tap2 * 8, -15, 2, 1.4, 2.4, P.skin, 28, 0.8);
476      part(7, 16 - tap2 * 8, -15, 2, 1.4, 2.4, P.skin, 28, 0.8);
477      head(30, 1.6, 1.4);
478      return;
479    }
480    if (act === "sleep" || act === "stretch") {
481      /* the pack is off and flat on the ground beside him either way */
482      /* flat on the grass, lid up, straps loose */
483      part(-15, 2.2, 10, 6, 2.2, 4.4, P.rag || [72, 74, 66], 16, 1);
484      part(-15, 4.6, 10, 4.4, 0.5, 3, P.coatIn, 18, 0.7);
485      part(-19, 1.2, 10, 1.2, 1.2, 3.4, P.rag || [72, 74, 66], 18, 0.5);
486      part(-11, 1.2, 10, 1.2, 1.2, 3.4, P.rag || [72, 74, 66], 18, 0.5);
487    }
488    if (act === "sleep") {
489      /* flat out on his back, chest rising, one arm behind his head */
490      var br = Math.sin(t * 0.9) * 0.5;
491      /* A part lying on the ground has its centre one half height up, and no
492         more. The extra four units here were holding the whole figure off the
493         grass, which is what looked like it was floating. */
494      function lay(dz, dy, hx, hy, hz, col, bias, r) {
495        E.box(m, x + dy, base - hy, z + dz, hx, hy, hz, col, null,
496              bias === undefined ? 20 : bias, r === undefined ? 1 : r);
497      }
498      /* Low and long. Every part is about half its old height so the figure
499         lies down rather than standing up in section, and it runs along the
500         ground instead of stacking up off it. */
501      lay(20, -4, 3, 2.2, 4, P.boot, 18, 1);
502      lay(20, 4, 3, 2.2, 4, P.boot, 18, 1);
503      lay(10, -4, 2.8, 2.4, 6, P.trouser, 18, 1);
504      lay(10, 4, 2.8, 2.4, 6, P.trouser, 18, 1);
505      lay(-3, 0, 5.5, 2.8 + br, 9, P.coat, 22, 2);
506      lay(-3, 0, 4, 3 + br, 1, P.coatIn, 30, 1.4);
507      lay(1, -8, 2, 1.8, 5, P.skin, 22, 0.8);
508      lay(1, 8, 2, 1.8, 5, P.skin, 22, 0.8);
509      lay(-14, 0, 4.4, 3.2, 4.4, P.skin, 24, 1.8);
510      lay(-18, 0, 4.8, 3.6, 3, P.hair, 30, 1.6);
511      lay(-16, -2.2, 0.8, 0.9, 1.2, P.eye, 40, 0.35);
512      lay(-16, 2.2, 0.8, 0.9, 1.2, P.eye, 40, 0.35);
513      /* the odd Z drifting off him */
514      for (var zq = 0; zq < 3; zq++) {
515        var zf = ((t * 0.4 + zq * 0.33) % 1);
516        E.box(m, x + 6, base - 12 - zf * 26, z - 16, 1.6 - zf, 1.6 - zf, 0.6,
517              P.eye, null, 50, 0.4);
518      }
519      m.anchor("head", [x, base - 16, z - 14]);
520      return;
521    }
522    if (act === "photo") {
523      /* down on the flowers with a camera up at his face, holding still for
524         the shot then lowering it to look at what he got */
525      var pc = (t * 0.34) % 1;
526      var upf = pc < 0.62 ? Math.min(1, pc / 0.16) : Math.max(0, 1 - (pc - 0.62) / 0.18);
527      var snap = pc > 0.44 && pc < 0.47;
528      arm(-1, -1.5 * upf - 0.2, 0.9 - upf * 0.5, 0);
529      arm(1, -1.5 * upf - 0.2, 0.9 - upf * 0.5, 0);
530      /* the camera, at his eye when it is up and at his chest when it is not */
531      var cy2 = base - (22 + upf * 15), cz2 = -9 - upf * 3;
532      E.box(m, x, cy2, z + cz2, 5, 3.6, 3, P.cam || [58, 60, 70], null, 40, 1.2);
533      E.box(m, x + 1.6, cy2 - 4.4, z + cz2, 1.6, 1, 1.6, P.cam || [58, 60, 70], null, 42, 0.5);
534      E.box(m, x, cy2, z + cz2 - 3.4, 2.2, 2.2, 1.2, [120, 160, 190], null, 44, 1);
535      if (snap) {
536        E.box(m, x, cy2, z + cz2 - 6, 5, 5, 3, [330, 330, 330], null, 60, 2);
537      }
538      head(38, upf * 1.4, 1.6 + upf * 1.6);
539      return;
540    }
541    if (act === "gaze") {
542      /* one hand up shading his eyes, scanning slowly */
543      var pan = Math.sin(t * 0.4) * 1.6;
544      arm(-1, 0.3, 0.4, 0);
545      arm(1, -1.9, 1.5, 0);
546      head(38, 0.4, 1 + pan * 0.3);
547      return;
548    }
549    /* watch: head tipped back, and every so often a hand out for a petal */
550    var reach = Math.max(0, Math.sin(t * 0.42));
551    arm(-1, 0.3, 0.35, 0);
552    arm(1, -0.4 - reach * 1.5, 0.6 - reach * 0.45, 0);
553    head(38, -1.2, 2.4);
554  }
555
556  /* What is under the pond. The tank is built with no front wall, so the
557     ground above reads as a bank you are looking in behind: koi working the
558     water, weeds leaning on the current, bubbles going up, light coming
559     down through the surface. */
560  function underwater(m, t, p) {
561    var i, k;
562    var WY0 = 18, WY1 = 300;            /* waterline, running off the frame */
563    var back = [46, 92, 116], side = [38, 78, 100], bed = [128, 132, 112];
564
565    E.box(m, 0, (WY0 + WY1) / 2, 74, 300, (WY1 - WY0) / 2, 2, back, null, -300, 0);
566    E.box(m, -298, (WY0 + WY1) / 2, 0, 2, (WY1 - WY0) / 2, 74, side, null, -298, 0);
567    E.box(m, 298, (WY0 + WY1) / 2, 0, 2, (WY1 - WY0) / 2, 74, side, null, -298, 0);
568
569
570    /* the surface seen from below, and the light coming through it */
571    E.box(m, 0, WY0, 0, 300, 1.4, 74, [120, 186, 210], null, -305, 0);
572    for (i = 0; i < 9; i++) {
573      var lx = -260 + i * 66 + Math.sin(t * 0.5 + i) * 12;
574      m.line([lx, WY0 + 2, -30 + (i % 3) * 30], [lx + 26, 190, 20 + (i % 2) * 30],
575             [176, 224, 236], 2.4, 0.12 + 0.06 * Math.sin(t * 1.3 + i));
576    }
577
578    /* weeds, leaning together on the current */
579    for (i = 0; i < 16; i++) {
580      var wx = -280 + rnd(i, 51) * 560, wz = -50 + rnd(i, 52) * 100;
581      var wh = 18 + rnd(i, 53) * 30;
582      var wbase = 100;
583      var lean = Math.sin(t * 0.7 + i) * 7;
584      for (k = 0; k < 5; k++) {
585        var f = k / 5;
586        E.box(m, wx + lean * f * f, wbase - 6 - f * wh - wh / 10, wz,
587              2.2 - f * 0.8, wh / 10, 2.2 - f * 0.8,
588              k % 2 ? [64, 138, 96] : [48, 116, 78], null, -60, 0.6);
589      }
590    }
591    /* stones, set on a shelf partway down rather than on a floor */
592    for (i = 0; i < 7; i++) {
593      var sr = 8 + rnd(i, 56) * 10;
594      E.box(m, -250 + rnd(i, 54) * 500, 96 - sr * 0.5, -30 + rnd(i, 55) * 60,
595            sr, sr * 0.5, sr * 0.7, [104, 108, 104], null, -70, sr * 0.4);
596    }
597
598    /* koi working the tank, each on its own circuit */
599    for (i = 0; i < 7; i++) {
600      var a = t * (0.16 + i * 0.035) + i * 1.4;
601      var rr = 60 + i * 26;
602      var kx = Math.cos(a) * rr;
603      var kz = Math.sin(a) * rr * 0.36;
604      /* they break toward whatever just hit the water */
605      if (ACT.pulse > 0.02) {
606        var dart = ACT.pulse * 0.5;
607        kx += (ACT.x - kx) * dart;
608        kz += (18 - kz) * dart * 0.5;
609      }
610      var ky = WY0 + 18 + i * 9 + Math.sin(t * 0.8 + i) * 6;
611      var col = i % 3 === 0 ? [252, 246, 244] : i % 3 === 1 ? [246, 130, 92] : [236, 176, 90];
612      var tail = Math.sin(t * 4 + i) * 4;
613      /* body, then a tail that beats behind it */
614      E.box(m, kx, ky, kz, 9, 3.4, 4.4, col, null, -40, 1.6);
615      E.box(m, kx - Math.cos(a) * 11, ky, kz - Math.sin(a) * 4 + tail, 4, 2.4, 3, col, null, -41, 1.2);
616      E.box(m, kx - Math.cos(a) * 15, ky, kz - Math.sin(a) * 6 + tail * 1.6,
617            2.6, 3.4, 1.6, col, null, -42, 0.8);
618      /* a dark eye so it reads as a fish and not a lozenge */
619      E.box(m, kx + Math.cos(a) * 7, ky - 1, kz + Math.sin(a) * 2.4 - 3.4,
620            1.4, 1.4, 0.8, [32, 34, 40], null, -38, 0.4);
621    }
622
623    /* bubbles going up in two threads */
624    for (i = 0; i < 14; i++) {
625      var bf = ((t * 0.34 + rnd(i, 58)) % 1);
626      var bx = i < 7 ? -120 : 130;
627      E.box(m, bx + Math.sin(bf * 8 + i) * 9, 100 - bf * 78, (i % 7) * 8 - 24,
628            1 + bf * 1.6, 1 + bf * 1.6, 1 + bf * 1.6, [186, 226, 238], null, -30, 0.8);
629    }
630  }
631
632  /* The stream that ties the closing band to the footer. Same channel, same
633     depth, same bank, drawn in both scenes so the water runs straight across
634     the join between the two canvases. */
635  function stream(m, t, warm, flow) {
636    var i;
637    var wcol = warm ? [92, 132, 156] : [52, 70, 110];
638    var lip = warm ? [126, 96, 74] : [44, 52, 82];
639    /* the channel, cut back into the bank */
640    E.box(m, 0, 5, 48, 430, 5, 22, lip, null, -200, 0);
641    E.box(m, 0, 3, 48, 426, 1.6, 19, wcol, null, -196, 0);
642    /* the surface catching light, moving downstream */
643    for (i = 0; i < 30; i++) {
644      var sx = -420 + ((i * 39 + t * (34 + (flow || 0) * 90)) % 840);
645      var sz = 40 + (i % 4) * 9;
646      E.box(m, sx, 1.6, sz, 7 + (i % 3) * 4, 0.5, 1.1,
647            warm ? [206, 196, 170] : [128, 152, 206], null, -190, 0.4);
648    }
649    /* stepping stones across it, in both scenes at the same places */
650    var ST = [-176, -104, 12, 96, 208];
651    for (i = 0; i < ST.length; i++) {
652      E.box(m, ST[i], 1, 48 + (i % 2 ? -6 : 6), 9, 3.4, 7,
653            warm ? [150, 142, 130] : [86, 92, 116], null, -170, 2);
654    }
655  }
656
657  /* What is out beyond the platform. Only things that belong in a sky: banks
658     of cloud sitting further back than the near weather, and motes on the
659     wind between them. Nothing hangs there that would not hang there. */
660  function surroundings(m, t, P, kind) {
661    var i, k;
662    var lit = P.cloudLit || [255, 255, 255];
663    var shd = P.cloudShd || [214, 222, 236];
664    /* a far bank, bigger and slower than the near cloud, well back */
665    for (i = 0; i < 5; i++) {
666      var drift = ((t * (2.2 + (i % 3)) + rnd(i, 91) * 1600) % 1700) - 850;
667      var cy = -190 - rnd(i, 92) * 130;
668      var cz = 620 + rnd(i, 93) * 320;
669      var csc = 1.4 + rnd(i, 94) * 1.3;
670      var body = [
671        [0, 0, 0, 26, 7, 12], [-19, 1, -2, 13, 6, 10], [20, 1, 2, 14, 6, 10],
672        [-6, -8, 0, 15, 7, 10], [11, -9, -1, 11, 6, 9], [2, -16, 0, 8, 5, 7]
673      ];
674      for (k = 0; k < body.length; k++) {
675        var bd = body[k];
676        E.box(m, drift + bd[0] * csc, cy + bd[1] * csc, cz + bd[2] * csc,
677              bd[3] * csc, bd[4] * csc, bd[5] * csc,
678              k < 3 ? shd : lit, null, -270 + k, bd[3] * csc * 0.22);
679      }
680    }
681    /* motes carried on the wind between the banks */
682    for (i = 0; i < 20; i++) {
683      var mf = ((t * 0.06 + rnd(i, 61)) % 1);
684      E.box(m, -460 + mf * 940, -90 - rnd(i, 62) * 170 + Math.sin(t * 0.5 + i) * 12,
685            340 + rnd(i, 63) * 300, 2.4, 2.4, 2.4,
686            P.mote || [255, 255, 255], null, -250, 1);
687    }
688  }
689
690  /* The sky over each world, built out of the same blocks as the ground under
691     it: chunky clouds drifting on their own beats, birds beating across, and
692     whatever that season's cloud is carrying, falling out of the bottom of it.
693     Everything is placed well back so it reads as sky rather than clutter. */
694  function skyworld(m, t, P, cfg) {
695    var i, k;
696    var CL = cfg.clouds || 0;
697
698    /* --- clouds. Each is a little heap of boxes so it reads as pixel art in
699       three dimensions rather than a flat sprite. --- */
700    var cloudAt = [];
701    for (i = 0; i < CL; i++) {
702      var drift = ((t * (5 + (i % 3) * 2.5) + rnd(i, 71) * 1100) % 1180) - 590;
703      var cy = -118 - rnd(i, 72) * 110;
704      var cz = 210 + rnd(i, 73) * 250;
705      var csc = 0.7 + rnd(i, 74) * 0.9;
706      cloudAt.push([drift, cy, cz, csc]);
707      var lit = P.cloudLit || [255, 255, 255];
708      var shd = P.cloudShd || [214, 222, 236];
709      /* a heap: a wide base course, a narrower one on top, a cap */
710      var body = [
711        [0, 0, 0, 26, 7, 12], [-19, 1, -2, 13, 6, 10], [20, 1, 2, 14, 6, 10],
712        [-6, -8, 0, 15, 7, 10], [11, -9, -1, 11, 6, 9], [2, -16, 0, 8, 5, 7]
713      ];
714      for (k = 0; k < body.length; k++) {
715        var bd = body[k];
716        E.box(m, drift + bd[0] * csc, cy + bd[1] * csc, cz + bd[2] * csc,
717              bd[3] * csc, bd[4] * csc, bd[5] * csc,
718              k < 3 ? shd : lit, null, -240 + k, bd[3] * csc * 0.22);
719      }
720    }
721
722    /* --- what the cloud is carrying, falling out from under it --- */
723    if (cfg.drop && cloudAt.length) {
724      var N = cfg.dropN || 30;
725      for (i = 0; i < N; i++) {
726        var src = cloudAt[i % cloudAt.length];
727        var ph = rnd(i, 75);
728        var f = ((t * (cfg.dropSpeed || 0.22) + ph) % 1);
729        var fx = src[0] + (rnd(i, 76) - 0.5) * 90;
730        var fy = src[1] + 14 + f * (Math.abs(src[1]) + 40);
731        var fz = src[2] - 40 + rnd(i, 77) * 80;
732        if (cfg.drop === "rain") {
733          /* a thread rather than a block, angled with the fall */
734          m.line([fx, fy, fz], [fx - 3, fy + 15, fz], P.dropCol || [196, 226, 240], 1.3, 0.5);
735        } else if (cfg.drop === "snow") {
736          var sw = Math.sin(t * 1.4 + ph * 9) * 16;
737          E.box(m, fx + sw, fy, fz, 1.8, 1.8, 1.8, [320, 326, 336], null, -60, 0.7);
738        } else {
739          /* petals and leaves ride the wind: a long sideways drift and a turn */
740          var wind = Math.sin(t * 0.7 + ph * 7) * 46 + f * 70;
741          var sp = t * 2.4 + ph * 9;
742          var cs2 = Math.cos(sp), sn2 = Math.sin(sp);
743          var col = (P.dropCols || [[255, 176, 203]])[i % (P.dropCols || [[255, 176, 203]]).length];
744          E.box(m, fx + wind, fy, fz + sn2 * 6,
745                3.4 * Math.abs(cs2) + 0.8, 1, 2.2, col, null, -60, 0.6);
746        }
747      }
748    }
749
750    /* --- birds, two skeins working across --- */
751    for (i = 0; i < (cfg.birds || 0); i++) {
752      var lane = i % 2;
753      var bp = ((t * (0.05 + lane * 0.018) + i * 0.09) % 1.25) - 0.12;
754      var bx = -560 + bp * 1120;
755      var by = -220 - lane * 60 + Math.sin(t * 0.5 + i) * 14;
756      var bz = 300 + lane * 120 + (i % 3) * 30;
757      var beat = Math.sin(t * 8 + i * 1.3);
758      var bc = P.birdCol || [72, 66, 74];
759      E.box(m, bx, by, bz, 3.4, 1.6, 2.2, bc, null, -230, 0.8);          /* body */
760      E.box(m, bx + 4.4, by - 0.4, bz, 1.6, 1.2, 1.4, bc, null, -230, 0.5); /* head */
761      E.box(m, bx - 4.6, by - 0.6, bz, 2.4, 1, 1.6, bc, null, -230, 0.5);   /* tail */
762      /* wings, up and down out of phase with each other */
763      E.box(m, bx, by - beat * 3.2, bz - 5.5, 2, 0.9, 4.4, bc, null, -229, 0.5);
764      E.box(m, bx, by + beat * 3.2, bz + 5.5, 2, 0.9, 4.4, bc, null, -229, 0.5);
765    }
766  }
767
768  /* Things carried down the stream. A new one sets off every couple of
769     seconds, so there is always a line of them in the water, and each one
770     runs the closing band and then the footer as a single journey.
771
772     The identity is taken from the journey, not from the clock, which is what
773     was wrong before: the slot was derived from a shorter period than the
774     journey itself, so an item changed into a different one halfway down and
775     the two bands never showed the same thing. */
776  var TRIP = 15, SPACING = 2;
777
778  function drifter(m, leg) {
779    /* Every item is numbered by when it set off, so each launch is a different
780       piece and keeps that piece the whole way down. Keying it to the journey
781       index instead gave a run of identical objects, because everything in
782       flight at once shared the same index. */
783    var jHi = Math.floor(CLOCK / SPACING);
784    var jLo = Math.max(0, Math.ceil((CLOCK - TRIP) / SPACING));
785    for (var k = jLo; k <= jHi; k++) {
786      var age = CLOCK - k * SPACING;
787      if (age < 0 || age >= TRIP) continue;
788      var n = k;                               /* the launch number */
789      var f = age / TRIP;                      /* 0 at the top, 1 at the end */
790      /* first half of the journey is the closing band, second half the foot */
791      if (leg === 0 ? f >= 0.5 : f < 0.5) continue;
792      var g = leg === 0 ? f * 2 : (f - 0.5) * 2;
793      var item = FLOTSAM[n % FLOTSAM.length];
794
795      var x = -470 + g * 940;
796      var y = -8 + Math.sin(CLOCK * 1.6 + k) * 1.4;
797      var z = 48;
798      var spin = Math.sin(CLOCK * 0.7 + k) * 0.2;
799      var cs = Math.cos(spin), sn = Math.sin(spin);
800      var S = 1.5;                             /* everything reads larger now */
801
802      (function (x, y, z, cs, sn, item, k) {
803        function b(dx, dy, dz, hx, hy, hz, col, bias, r) {
804          dx *= S; dy *= S; dz *= S;
805          E.box(m, x + dx * cs - dy * sn, y + dx * sn + dy * cs, z + dz,
806                hx * S, hy * S, hz * S, col, null,
807                bias === undefined ? 44 : bias, (r === undefined ? 0.7 : r) * S);
808        }
809        item.draw(b, CLOCK + k);
810        for (var i = 0; i < 4; i++) {
811          var wf = i / 4;
812          E.box(m, x - 16 - i * 10, -3.4, z + Math.sin(CLOCK * 2 + i + k) * 2.4,
813                6 - wf * 3.4, 0.5, 1.6 + wf, [206, 226, 236], null, 30, 0.5);
814        }
815      })(x, y, z, cs, sn, item, k);
816    }
817  }
818
819  var GOLD = [226, 188, 92], DGOLD = [150, 112, 40], WOOD = [122, 84, 52];
820  var IRON = [128, 136, 148], DIRON = [86, 92, 104], WHITE = [246, 246, 250];
821  var BLACK = [40, 38, 46], RED = [206, 62, 54], GREEN = [86, 168, 82];
822  var FLOTSAM = [
823    { id: "chest", draw: function (b) {                 /* the original */
824      b(0, 0, 0, 11, 5.5, 8, WOOD, 44, 1.2);
825      b(0, -6, 0, 11.4, 2.6, 8.4, [96, 64, 40], 46, 2.4);
826      b(-6.5, -2, 0, 1.4, 8, 8.6, IRON, 48, 0.5);
827      b(6.5, -2, 0, 1.4, 8, 8.6, IRON, 48, 0.5);
828      b(0, -3.4, -8.6, 2.4, 2.6, 0.8, GOLD, 50, 0.6);
829    } },
830    { id: "mushroom", draw: function (b) {              /* Mario */
831      b(0, 2, 0, 4.4, 4, 4, [244, 240, 226], 44, 1.4);
832      b(0, -4, 0, 8, 4.4, 7, RED, 46, 3);
833      b(-4, -5.4, -3, 2.2, 1.6, 1.6, WHITE, 48, 1);
834      b(4, -5.4, -3, 2.2, 1.6, 1.6, WHITE, 48, 1);
835      b(0, -8, 1, 2.6, 1.6, 2.6, WHITE, 48, 1);
836    } },
837    { id: "sword", draw: function (b) {                 /* Zelda */
838      b(0, -14, 0, 1.8, 12, 0.9, [214, 220, 232], 46, 0.4);
839      b(0, -1, 0, 6.5, 1.4, 1.4, GOLD, 48, 0.5);
840      b(0, 3, 0, 1.4, 3.4, 1.4, [96, 76, 120], 48, 0.5);
841      b(0, 7, 0, 2.4, 1.6, 2.4, GOLD, 48, 0.8);
842    } },
843    { id: "pokeball", draw: function (b) {              /* Pokemon */
844      b(0, -2.5, 0, 6, 3, 6, RED, 46, 3);
845      b(0, 2.5, 0, 6, 3, 6, WHITE, 46, 3);
846      b(0, 0, 0, 6.4, 0.9, 6.4, BLACK, 48, 1);
847      b(0, 0, -6.2, 2, 2, 0.8, WHITE, 50, 0.9);
848    } },
849    { id: "diamond", draw: function (b, t) {            /* Minecraft */
850      var g = 0.6 + 0.4 * Math.sin(t * 2.4);
851      b(0, 0, 0, 4.4, 3, 3, [120 + 90 * g, 230, 240], 46, 1.2);
852      b(0, -4, 0, 2.6, 2, 2, [150 + 80 * g, 240, 248], 48, 0.8);
853      b(0, 4, 0, 2.6, 2, 2, [110 + 70 * g, 214, 232], 48, 0.8);
854    } },
855    { id: "cube", draw: function (b) {                  /* Portal */
856      b(0, 0, 0, 6, 6, 6, [186, 190, 196], 46, 1);
857      b(0, 0, -6.2, 3, 3, 0.6, [244, 148, 176], 48, 1.4);
858      b(0, -6.2, 0, 3, 0.6, 3, [244, 148, 176], 48, 1.4);
859      b(6.2, 0, 0, 0.6, 3, 3, [244, 148, 176], 48, 1.4);
860    } },
861    { id: "duck", draw: function (b) {                  /* rubber duck */
862      b(0, 0, 0, 6, 4, 4.4, [246, 208, 78], 46, 2.4);
863      b(4, -5, 0, 3, 3, 3, [246, 208, 78], 48, 1.4);
864      b(7.5, -5, 0, 1.8, 1.2, 1.2, [232, 130, 48], 50, 0.5);
865      b(5.4, -6.4, -2.6, 0.7, 0.8, 0.5, BLACK, 52, 0.3);
866    } },
867    { id: "crown", draw: function (b) {                 /* roguelike staple */
868      b(0, 0, 0, 7, 1.8, 5, GOLD, 46, 0.6);
869      b(-5, -3.4, 0, 1.4, 2.4, 1.4, GOLD, 48, 0.5);
870      b(0, -4.6, 0, 1.4, 3.4, 1.4, GOLD, 48, 0.5);
871      b(5, -3.4, 0, 1.4, 2.4, 1.4, GOLD, 48, 0.5);
872      b(0, -7.6, 0, 1.6, 1.4, 1.6, RED, 50, 0.7);
873    } },
874    { id: "herb", draw: function (b) {                  /* Resident Evil */
875      b(0, 0, 0, 3.4, 4, 3, [196, 62, 58], 46, 1.4);
876      b(-3.4, -3, 0, 2.6, 1, 1.8, [86, 158, 82], 48, 0.9);
877      b(3.4, -4, 0, 2.6, 1, 1.8, [86, 158, 82], 48, 0.9);
878      b(0, -6, 0, 1, 2.4, 1, [70, 128, 66], 48, 0.4);
879    } },
880    { id: "strawhat", draw: function (b) {              /* One Piece */
881      b(0, 0, 0, 9, 1.2, 8, [232, 200, 130], 46, 3);
882      b(0, -3, 0, 4.6, 3, 4.4, [232, 200, 130], 48, 1.6);
883      b(0, -3, 0, 4.9, 1, 4.7, RED, 50, 1.6);
884    } },
885    { id: "notebook", draw: function (b) {              /* Death Note */
886      b(0, 0, 0, 6, 1.2, 8, BLACK, 46, 0.5);
887      b(0, -1.4, 0, 5.4, 0.6, 7.4, [58, 54, 62], 48, 0.5);
888      b(0, -2.1, 2, 3, 0.4, 2, [214, 210, 216], 50, 0.3);
889    } },
890    { id: "dragonball", draw: function (b, t) {         /* Dragon Ball */
891      var g = 0.6 + 0.4 * Math.sin(t * 2);
892      b(0, 0, 0, 5, 5, 5, [246, 176 + 40 * g, 70], 46, 2.4);
893      var pts = [[-2, -2], [2, -2], [0, 0], [-2, 2], [2, 2]];
894      for (var i = 0; i < 4; i++) {
895        b(pts[i][0], pts[i][1], -5.2, 0.8, 0.8, 0.5, [200, 58, 48], 50, 0.4);
896      }
897    } },
898    { id: "blades", draw: function (b) {                /* Attack on Titan */
899      b(-2, 0, 0, 1.2, 9, 0.8, [206, 214, 226], 46, 0.3);
900      b(2.4, 1, 0, 1.2, 9, 0.8, [206, 214, 226], 46, 0.3);
901      b(-2, 8, 0, 1.8, 2.4, 1.6, [70, 66, 74], 48, 0.6);
902      b(2.4, 9, 0, 1.8, 2.4, 1.6, [70, 66, 74], 48, 0.6);
903    } },
904    { id: "ramen", draw: function (b) {                 /* Naruto */
905      b(0, 0, 0, 6.5, 3.4, 6, [232, 234, 238], 46, 2);
906      b(0, -3.4, 0, 5.6, 0.8, 5, [226, 190, 120], 48, 1.8);
907      b(-1.6, -4.4, -1.6, 1.6, 0.8, 1.6, WHITE, 50, 0.8);
908      b(1.6, -4.4, 1, 1.6, 0.8, 1.6, [236, 140, 130], 50, 0.8);
909      b(3, -6, 3, 0.5, 3, 0.5, [196, 164, 110], 50, 0.3);
910    } },
911    { id: "soulgem", draw: function (b, t) {            /* Madoka */
912      var g = 0.55 + 0.45 * Math.sin(t * 2.6);
913      b(0, 0, 0, 3.4, 4.4, 3.4, [150 + 60 * g, 90, 200], 46, 1.6);
914      b(0, 4.4, 0, 4.4, 1.4, 4.4, GOLD, 48, 1.2);
915      b(0, -4.6, 0, 1.6, 1.6, 1.6, [220, 190, 250], 50, 0.7);
916    } },
917    { id: "umbrella", draw: function (b) {              /* Ghibli */
918      b(0, -4, 0, 9, 1.4, 8, [72, 96, 120], 46, 3.4);
919      b(0, -6.4, 0, 4.4, 1.6, 4, [72, 96, 120], 48, 2);
920      b(0, 2, 0, 0.9, 7, 0.9, WOOD, 46, 0.4);
921      b(-2, 8, 0, 2.4, 0.9, 0.9, WOOD, 48, 0.4);
922    } },
923    { id: "ring", draw: function (b, t) {               /* LOTR */
924      var g = 0.5 + 0.5 * Math.sin(t * 1.8);
925      var n = 12;
926      for (var i = 0; i < n; i++) {
927        var a2 = (i / n) * Math.PI * 2;
928        b(Math.cos(a2) * 5, Math.sin(a2) * 5, 0, 1.2, 1.2, 1.4,
929          [226 + 20 * g, 188 + 20 * g, 92], 46, 0.5);
930      }
931    } },
932    { id: "snitch", draw: function (b, t) {             /* Harry Potter */
933      var w = Math.sin(t * 9) * 2.6;
934      b(0, 0, 0, 3.4, 3.4, 3.4, GOLD, 46, 1.6);
935      b(-5, w, 0, 3.4, 0.7, 2.4, [244, 240, 220], 48, 1);
936      b(5, -w, 0, 3.4, 0.7, 2.4, [244, 240, 220], 48, 1);
937    } },
938    { id: "saber", draw: function (b, t) {              /* Star Wars */
939      b(0, 0, 0, 1.8, 7, 1.8, IRON, 46, 0.5);
940      b(0, -3, 0, 2.1, 1, 2.1, DIRON, 48, 0.5);
941      b(0, 1, 0, 2.1, 1, 2.1, DIRON, 48, 0.5);
942      b(0, 5.4, 0, 1.4, 1.4, 1.4, RED, 50, 0.6);
943      /* a hand's width of blade, flickering */
944      var l = 3 + Math.sin(t * 6) * 0.5;
945      b(0, -8 - l, 0, 1, l, 1, [255, 120, 100], 52, 0.4);
946    } },
947    { id: "excalibur", draw: function (b) {             /* Arthurian */
948      b(0, 4, 0, 8, 4, 7, [128, 124, 120], 44, 2.4);
949      b(0, -10, 0, 1.6, 10, 0.8, [220, 226, 236], 46, 0.4);
950      b(0, -1, 0, 5.4, 1.2, 1.2, DGOLD, 48, 0.5);
951      b(0, 2.4, 0, 1.6, 2.4, 1.6, DGOLD, 48, 0.6);
952    } },
953    { id: "wizhat", draw: function (b) {                /* the old wizard */
954      b(0, 0, 0, 8, 1.2, 7, [66, 78, 140], 46, 3);
955      b(0, -3.5, 0, 4.4, 3, 4, [66, 78, 140], 48, 1.6);
956      b(1, -7.5, 0, 2.4, 3, 2.2, [58, 68, 126], 48, 1);
957      b(0, -1.6, -6.4, 1.2, 1.2, 0.6, GOLD, 50, 0.5);
958    } },
959    { id: "bottle", draw: function (b) {                /* a message in it */
960      b(0, 0, 0, 3, 5.5, 3, [176, 208, 190], 46, 1.4);
961      b(0, -6, 0, 1.4, 2, 1.4, [176, 208, 190], 48, 0.6);
962      b(0, -8, 0, 1.6, 1, 1.6, WOOD, 50, 0.6);
963      b(0, 1, 0, 2, 3, 1.4, [236, 226, 196], 50, 0.6);
964    } },
965    { id: "sonicring", draw: function (b, t) {           /* Sonic */
966      var n = 14;
967      for (var i = 0; i < n; i++) {
968        var a2 = (i / n) * Math.PI * 2;
969        b(Math.cos(a2) * 5.5, Math.sin(a2) * 5.5, 0, 1.3, 1.3, 1.3,
970          [244, 206, 72], 46, 0.5);
971      }
972    } },
973    { id: "pacman", draw: function (b, t) {              /* Pac-Man */
974      var open = 0.5 + 0.5 * Math.abs(Math.sin(t * 3));
975      var n = 16;
976      for (var i = 0; i < n; i++) {
977        var a2 = (i / n) * Math.PI * 2;
978        /* leave the mouth out, opening and closing */
979        if (Math.abs(((a2 + Math.PI) % (Math.PI * 2)) - Math.PI) > Math.PI - open * 0.8) continue;
980        for (var rr = 2; rr <= 6; rr += 2) {
981          b(Math.cos(a2) * rr, Math.sin(a2) * rr, 0, 1.4, 1.4, 1.6,
982            [248, 220, 68], 46, 0.5);
983        }
984      }
985      b(0, 0, 0, 1.6, 1.6, 1.6, [248, 220, 68], 46, 0.5);
986    } },
987    { id: "invader", draw: function (b) {                /* Space Invaders */
988      var rows = ["0010100", "0111110", "1101011", "1111111", "0101010", "1000001"];
989      for (var r = 0; r < rows.length; r++) {
990        for (var c = 0; c < rows[r].length; c++) {
991          if (rows[r][c] !== "1") continue;
992          b((c - 3) * 1.9, (r - 2.5) * 1.9, 0, 0.95, 0.95, 1.1, [126, 226, 118], 46, 0.2);
993        }
994      }
995    } },
996    { id: "tetris", draw: function (b) {                 /* Tetris */
997      var cells = [[0, 0], [1, 0], [2, 0], [1, 1]];      /* the T piece */
998      for (var i = 0; i < cells.length; i++) {
999        b((cells[i][0] - 1) * 4, (cells[i][1] - 0.5) * 4, 0, 2, 2, 2,
1000          [156, 96, 214], 46, 0.5);
1001        b((cells[i][0] - 1) * 4, (cells[i][1] - 0.5) * 4, -2.2, 1.2, 1.2, 0.5,
1002          [196, 150, 240], 48, 0.4);
1003      }
1004    } },
1005    { id: "slime", draw: function (b, t) {               /* Dragon Quest */
1006      var w = 1 + Math.sin(t * 2.4) * 0.06;
1007      b(0, 2, 0, 6.5 * w, 3.4, 6 * w, [78, 128, 226], 46, 3);
1008      b(0, -2, 0, 4.4 * w, 2.6, 4 * w, [78, 128, 226], 46, 2.4);
1009      b(0, -5.4, 0, 1.4, 1.6, 1.4, [78, 128, 226], 46, 0.6);
1010      b(-1.8, 0, -4.4, 0.9, 1.1, 0.6, [250, 250, 255], 50, 0.4);
1011      b(1.8, 0, -4.4, 0.9, 1.1, 0.6, [250, 250, 255], 50, 0.4);
1012      b(0, 3, -4, 2.4, 0.7, 0.6, [30, 40, 90], 50, 0.3);
1013    } },
1014    { id: "materia", draw: function (b, t) {             /* Final Fantasy */
1015      var g = 0.55 + 0.45 * Math.sin(t * 2.2);
1016      b(0, 0, 0, 4.4, 4.4, 4.4, [90 + 40 * g, 220, 140], 46, 2.2);
1017      b(0, -1.4, -4, 1.4, 1.4, 0.8, [230, 255, 240], 50, 0.7);
1018    } },
1019    { id: "puzzle", draw: function (b) {                 /* Yu-Gi-Oh */
1020      for (var r = 0; r < 5; r++) {
1021        var w = 5.5 - r * 1.1;
1022        b(0, -4 + r * 2.2, 0, w, 1.1, w * 0.5, [230, 190, 70], 46, 0.5);
1023      }
1024      b(0, -6.4, 0, 1.2, 1.6, 1.2, [230, 190, 70], 48, 0.5);
1025      b(0, 0, -2.6, 1.8, 1.8, 0.6, [70, 60, 40], 50, 0.8);
1026    } },
1027    { id: "hammer", draw: function (b) {                 /* Mjolnir */
1028      b(0, -2, 0, 5, 4, 4.4, [150, 158, 170], 46, 1);
1029      b(-5.4, -2, 0, 0.9, 4.4, 4.8, [110, 118, 130], 48, 0.6);
1030      b(5.4, -2, 0, 0.9, 4.4, 4.8, [110, 118, 130], 48, 0.6);
1031      b(0, 5, 0, 1.2, 7, 1.2, [122, 84, 52], 46, 0.5);
1032      b(0, 11.6, 0, 1, 1.6, 1, [206, 200, 190], 48, 0.5);
1033    } }
1034  ];
1035
1036  /* The margins. Content sits down the middle of every band, so the far left
1037     and right of each world are the only parts never covered, and they were
1038     the emptiest. Each world gets a pair of standing pieces out there, keyed
1039     to what that world is. */
1040  function margins(m, t, P, kind) {
1041    var i;
1042    var L = -352, R = 352;               /* out past the platform edges */
1043
1044    function post(x, h, col, capCol) {
1045      /* a weathered marker post, leaning a little */
1046      var lean = Math.sin(t * 0.4 + x) * 1.6;
1047      for (i = 0; i < 5; i++) {
1048        var f = i / 5;
1049        E.box(m, x + lean * f, -f * h - h / 10, 24, 3.2 - f * 0.6, h / 10, 3.2 - f * 0.6,
1050              i % 2 ? col : capCol, null, 6, 1);
1051      }
1052      E.box(m, x + lean, -h - 4, 24, 7, 4, 2, capCol, null, 8, 1.2);
1053    }
1054    function boulder(x, r, col, top) {
1055      dome(m, x, 34, r, r * 0.8, col, top);
1056      dome(m, x + r * 1.3, 44, r * 0.55, r * 0.45, col, top);
1057    }
1058
1059    if (kind === "winter") {
1060      /* drifts banked up, and a fence line going out of frame */
1061      dome(m, L, 30, 40, 22, [304, 312, 324], [330, 336, 344]);
1062      dome(m, R, 34, 34, 19, [298, 306, 320], [326, 332, 342]);
1063      for (i = 0; i < 3; i++) {
1064        post(L + 40 + i * 26, 22, [116, 92, 70], [140, 112, 86]);
1065        post(R - 40 - i * 26, 22, [116, 92, 70], [140, 112, 86]);
1066      }
1067    } else if (kind === "jungle") {
1068      boulder(L, 30, [58, 92, 60], [86, 128, 84]);
1069      boulder(R - 40, 26, [58, 92, 60], [86, 128, 84]);
1070      for (i = 0; i < 4; i++) {
1071        E.box(m, L + 30 + i * 16, -8 - (i % 2) * 6, 40, 3, 8, 3, [64, 116, 58], null, 8, 1);
1072        E.box(m, R - 30 - i * 16, -8 - (i % 2) * 6, 40, 3, 8, 3, [64, 116, 58], null, 8, 1);
1073      }
1074    } else if (kind === "autumn" || kind === "craft" || kind === "dusk" || kind === "night") {
1075      /* dry stone, and a pair of markers down the verge */
1076      boulder(L, 26, [128, 118, 104], [156, 146, 130]);
1077      boulder(R - 30, 22, [128, 118, 104], [156, 146, 130]);
1078      post(L + 54, 26, [116, 92, 70], [140, 112, 86]);
1079      post(R - 54, 26, [116, 92, 70], [140, 112, 86]);
1080    } else {
1081      /* the green bands: bushes and a marker either side */
1082      var g1 = P.far ? P.far[0] : [110, 172, 104];
1083      var g2 = P.far ? P.far[1] : [140, 196, 132];
1084      dome(m, L, 34, 30, 22, g1, g2);
1085      dome(m, L + 44, 46, 18, 13, g1, g2);
1086      dome(m, R - 10, 36, 26, 19, g1, g2);
1087      post(L + 78, 24, [122, 96, 70], [146, 116, 88]);
1088      post(R - 60, 24, [122, 96, 70], [146, 116, 88]);
1089    }
1090    /* a low bank running out to the edge of frame on both sides, so the
1091       ground does not simply stop where the platform does */
1092    E.box(m, L - 30, 6, 30, 90, 7, 60, P.soilSide, null, -320, 0);
1093    E.box(m, L - 30, -0.4, 30, 88, 1.6, 58, P.turf, null, -318, 0);
1094    E.box(m, R + 30, 6, 30, 90, 7, 60, P.soilSide, null, -320, 0);
1095    E.box(m, R + 30, -0.4, 30, 88, 1.6, 58, P.turf, null, -318, 0);
1096  }
1097
1098  /* A squirrel working an acorn. It always has one in its paws and is always
1099     at it, and the head hangs off the same rocking value as the body rather
1100     than one of its own, so the two can never come apart. */
1101  function squirrel(m, t, x, z) {
1102    var FUR = [148, 96, 58], FUR2 = [122, 76, 44], BELLY = [214, 186, 156];
1103    var NUT = [140, 100, 58], CAP = [96, 66, 38], EYE = [30, 26, 28];
1104    var rock = Math.sin(t * 1.1) * 0.6;
1105    var nib = Math.sin(t * 13) * 0.5;
1106    var eat = ((t * 0.14) % 1);
1107    var i;
1108
1109    var eaten = Math.floor((t / 7.2) % 5);
1110    for (i = 0; i < 5; i++) {
1111      if (i < eaten) continue;
1112      var ax = x + 17 + (i % 3) * 5, az = z + 3 + Math.floor(i / 3) * 5;
1113      var ay = -2.4 - (i % 2) * 2;
1114      E.box(m, ax, ay, az, 2.2, 2, 1.8, NUT, null, 14, 0.9);
1115      E.box(m, ax, ay - 2.4, az, 2, 0.9, 1.6, CAP, null, 15, 0.6);
1116    }
1117
1118    function b(dx, dy, dz, hx, hy, hz, col, bias, r) {
1119      E.box(m, x + dx + rock * (dy / 22), -dy, z + dz, hx, hy, hz, col, null,
1120            bias === undefined ? 22 : bias, r === undefined ? 0.8 : r);
1121    }
1122    b(-2.6, 3, 2, 2.4, 3, 3.4, FUR2, 20, 1.2);
1123    b(2.6, 3, 2, 2.4, 3, 3.4, FUR2, 20, 1.2);
1124    b(0, 9, 0, 3.6, 5, 3.2, FUR, 24, 1.4);
1125    b(0, 8.6, -3, 2.6, 3.4, 0.9, BELLY, 30, 1.2);
1126    b(-1.8, 12.4 + nib * 0.4, -4, 1.1, 1.4, 1.4, FUR2, 32, 0.5);
1127    b(1.8, 12.4 + nib * 0.4, -4, 1.1, 1.4, 1.4, FUR2, 32, 0.5);
1128    var nsc = 1 - eat * 0.7;
1129    b(0, 13 + nib * 0.4, -5.2, 1.7 * nsc, 1.6 * nsc, 1.4 * nsc, NUT, 34, 0.6);
1130    b(0, 14.6 + nib * 0.4, -5.2, 1.5 * nsc, 0.7 * nsc, 1.2 * nsc, CAP, 35, 0.4);
1131    b(0, 15, -1.4, 1.8, 2, 1.8, FUR, 34, 0.7);          /* the neck */
1132    var hy = 17.6 + nib;
1133    b(0, hy, -3, 2.6, 2.4, 2.6, FUR, 36, 1.2);
1134    b(0, hy - 0.6, -5.4, 1.4, 1.2, 1.2, BELLY, 38, 0.6);
1135    b(-1.9, hy + 2.2, -2.6, 1, 1.4, 0.8, FUR2, 38, 0.5);
1136    b(1.9, hy + 2.2, -2.6, 1, 1.4, 0.8, FUR2, 38, 0.5);
1137    b(-1.4, hy + 0.4, -5, 0.7, 0.8, 0.5, EYE, 40, 0.3);
1138    b(1.4, hy + 0.4, -5, 0.7, 0.8, 0.5, EYE, 40, 0.3);
1139    for (i = 0; i < 7; i++) {
1140      var f = i / 6;
1141      var flick = Math.sin(t * 3 + f * 2.2) * (1.4 + f * 2.6);
1142      b(flick * 0.4, 5 + f * 13, 4 + Math.sin(f * 2.1) * 5,
1143        2 + f * 1.4, 1.8, 1.6 + f * 1.2, i % 2 ? FUR : FUR2, 18, 1.2);
1144    }
1145  }
1146
1147  /* ---------------------------------------------------------------- props */
1148
1149  function cabin(m, x, z, P, t) {
1150    /* walls */
1151    E.box(m, x, -26, z, 34, 26, 26, P.wall, P.wallCap, 0, 2);
1152    /* log courses, so the walls read as timber */
1153    for (var i = 0; i < 5; i++) {
1154      E.box(m, x, -8 - i * 10, z - 26.6, 34, 1.1, 0.6, P.wallLine, null, 26, 0.4);
1155    }
1156    /* gabled roof, stepped in courses the way a voxel roof would be laid */
1157    var courses = 6;
1158    for (var s2 = 0; s2 < courses; s2++) {
1159      var f2 = s2 / courses;
1160      E.box(m, x, -54 - s2 * 6, z, 40 * (1 - f2 * 0.82) + 2, 3.2, 34 - s2 * 0.6,
1161            s2 % 2 ? P.roof : P.roofDark, null, 20 + s2, 1);
1162    }
1163    /* eaves overhanging the walls */
1164    E.box(m, x, -52, z, 42, 2.2, 35, P.roofDark, null, 19, 1);
1165    /* the window, lit from inside, with a warm pool on the snow */
1166    var glow = 0.72 + Math.sin(t * 2.2) * 0.14 + Math.sin(t * 5.1) * 0.05;
1167    E.box(m, x - 11, -30, z - 28.4, 8.6, 7.6, 0.5, P.frame, null, 38, 1);
1168    E.box(m, x - 11, -30, z - 29, 7.4, 6.4, 0.5,
1169          [255, 214 + 34 * glow, 132 + 60 * glow], null, 42, 0.8);
1170    /* glazing bars */
1171    E.box(m, x - 11, -30, z - 29.4, 0.5, 6.4, 0.3, P.frame, null, 46, 0.2);
1172    E.box(m, x - 11, -30, z - 29.4, 7.4, 0.5, 0.3, P.frame, null, 46, 0.2);
1173    /* the light it spills onto the snow outside */
1174    E.extrude(m, E.disc(24, 12), -0.7, 0.1, [255, 232, 178], [255, 236, 190],
1175      function (px, py, pz) { return [x - 11 + px, pz, z - 40 + py]; }, { bias: -370 });
1176    /* door */
1177    E.box(m, x + 13, -16, z - 27, 7, 16, 1, P.door, null, 40, 1.4);
1178    E.box(m, x + 16, -16, z - 28.2, 1, 1, 0.5, P.frame, null, 44, 0.4);
1179    /* chimney and smoke, rising and spreading as it goes */
1180    E.box(m, x + 22, -74, z + 6, 5, 12, 5, P.stone, null, 30, 1);
1181    for (var k = 0; k < 8; k++) {
1182      var f = ((t * 0.22 + k * 0.125) % 1);
1183      var r = 1.6 + f * 4;
1184      /* it pales toward the sky as it thins out */
1185      var sm = 300 + f * 20;
1186      E.gem(m, r, 1, x + 22 + Math.sin(f * 5 + k) * 10, -86 - f * 52, z + 6 + Math.
1186cos(f * 4) * 5,
1187            [sm, sm + 2, sm + 6], null);
1188    }
1189  }
1190
1191  function campfire(m, x, z, t) {
1192    var i;
1193    /* a ring of stones */
1194    for (i = 0; i < 8; i++) {
1195      var a = (i / 8) * Math.PI * 2;
1196      E.box(m, x + Math.cos(a) * 15, -3, z + Math.sin(a) * 15, 3.4, 3, 3.4,
1197            [150, 148, 152], null, 20, 1.2);
1198    }
1199    /* crossed logs */
1200    E.box(m, x, -6, z, 13, 2.6, 2.6, [122, 86, 62], null, 22, 1);
1201    E.box(m, x, -6, z, 2.6, 2.6, 13, [98, 68, 48], null, 22, 1);
1202    /* flame: three tapering tiers that flicker out of step */
1203    for (i = 0; i < 4; i++) {
1204      var f = i / 4;
1205      var fl = 1 + Math.sin(t * 9 + i * 1.7) * 0.2 + Math.sin(t * 15 + i) * 0.1
1206                 + ACT.pulse * 0.9;
1207      var col = i === 0 ? [255, 216, 120] : i === 1 ? [255, 168, 58] : i === 2 ? [242, 110, 40] : [206, 66, 30];
1208      E.gem(m, (9 - f * 6) * fl, 1, x + Math.sin(t * 7 + i) * 1.4, -10 - i * 7 * fl, z,
1209            col, null);
1210    }
1211    /* embers lifting off it */
1212    for (i = 0; i < 8; i++) {
1213      var e = ((t * 0.8 + i * 0.125) % 1);
1214      E.box(m, x + Math.sin(e * 9 + i) * 12, -22 - e * 52, z + Math.cos(e * 6 + i) * 8,
1215            0.9, 0.9, 0.9, [255, 180, 90], null, 60, 0.4);
1216    }
1217    /* the light it throws on the ground */
1218    var lit = 0.5 + Math.sin(t * 2.6) * 0.1;
1219    E.extrude(m, E.disc(40, 14), -0.6, 0.2, [255, 200, 130], [255, 206, 140],
1220      function (px, py, pz) { return [x + px, pz, z + py]; }, { bias: -380 });
1221    return lit;
1222  }
1223
1224  function lantern(m, x, z, t, i, lit) {
1225    if (lit === undefined) lit = 1;
1226    var glow = (0.75 + Math.sin(t * 2 + i) * 0.2) * lit;
1227    E.box(m, x, -6, z, 2, 6, 2, [90, 76, 68], null, 20, 0.6);
1228    /* every channel scales with the glow, so an unlit lantern goes dark
1229       rather than going red */
1230    E.box(m, x, -18, z, 6, 7, 6,
1231          [44 + 211 * glow, 38 + 176 * glow, 34 + 116 * glow], null, 24, 1.6);
1232    E.box(m, x, -26, z, 7.5, 1.6, 7.5, [70, 58, 52], null, 26, 0.8);
1233  }
1234
1235  function bench(m, x, z, col, colB) {
1236    E.box(m, x, -9, z, 22, 1.8, 7, col, null, 20, 0.8);
1237    E.box(m, x, -16, z + 6.4, 22, 6, 1.6, col, null, 22, 0.8);
1238    E.box(m, x - 17, -4, z, 1.6, 5, 6, colB, null, 20, 0.5);
1239    E.box(m, x + 17, -4, z, 1.6, 5, 6, colB, null, 20, 0.5);
1240  }
1241
1242  function grassTuft(m, x, z, t, cols, h, seed) {
1243    for (var i = 0; i < 4; i++) {
1244      var sway = Math.sin(t * 1.6 + seed + i) * 2;
1245      var hh = h * (0.55 + rnd(seed + i, 3) * 0.6);
1246      E.box(m, x + (i - 1.5) * 2.4 + sway * 0.4, -hh / 2, z + (rnd(seed + i, 4) - 0.5) * 5,
1247            1, hh / 2, 1, cols[i % cols.length], null, 20, 0.3);
1248    }
1249  }
1250
1251  /* ---------------------------------------------------------------- worlds */
1252  var PAL = {
1253    person: {
1254      skin: [246, 214, 186], hair: [58, 42, 50], eye: [40, 30, 38],
1255      coat: [92, 122, 168], coatIn: [130, 158, 198], trouser: [64, 72, 96],
1256      boot: [58, 48, 56], mug: [300, 300, 308], steam: [330, 332, 338], scarf: null,
1257      tool: [138, 100, 70]
1258    }
1259  };
1260  function personPal(over) {
1261    var o = {}, k;
1262    for (k in PAL.person) if (PAL.person.hasOwnProperty(k)) o[k] = PAL.person[k];
1263    for (k in over) if (over.hasOwnProperty(k)) o[k] = over[k];
1264    return o;
1265  }
1266
1267  var WORLDS = {
1268    /* ---- cherry blossom: he stops under the trees and watches them fall ---- */
1269    sakura: function (m, t, p, seed) {
1270      /* Asleep under the trees most of the time, up for a stretch, back down. */
1271      var qk = (t % 17) / 17;
1272      ACTOR.x = -14; ACTOR.z = 46;
1273      ACTOR.act = qk < 0.62 ? "sleep" : qk < 0.86 ? "stretch" : "sleep";
1274      var P = { soil: [150, 116, 96], soilSide: [122, 92, 74], turf: [150, 196, 132] };
1275      P.far = [[255, 176, 203], [255, 200, 220]]; P.mote = [255, 196, 216];
1276      P.cloudLit=[255,245,250];P.cloudShd=[240,214,228];P.dropCols=[[255,176,203],[255,206,224],[246,150,184]];P.birdCol=[92,72,86];
1277      skyworld(m, t, P, { clouds: 0, birds: 8, drop: "petal", dropN: 34, dropSpeed: 0.18 });
1278      surroundings(m, t, P, "leaf");
1279      ground(m, P, -300, 300, -70, 70);
1280      margins(m, t, P, "spring");
1281      var pink = [[255, 176, 203], [255, 200, 220], [246, 150, 184]];
1282      blobTree(m, -190, -30, 74, 34, pink, [[122, 86, 62], [96, 66, 46]], t, 1);
1283      blobTree(m, -70, 20, 58, 26, pink, [[122, 86, 62], [96, 66, 46]], t, 4);
1284      blobTree(m, 120, -36, 84, 38, pink, [[122, 86, 62], [96, 66, 46]], t, 7);
1285      blobTree(m, 240, 10, 52, 24, pink, [[122, 86, 62], [96, 66, 46]], t, 11);
1286      bench(m, 34, 44, [154, 112, 82], [120, 84, 60]);
1287      lantern(m, -128, 40, t, 0);
1288      lantern(m, 186, 36, t, 2);
1289      for (var i = 0; i < 7; i++) {
1290        grassTuft(m, -260 + i * 84, 52, t, [[126, 186, 106], [104, 166, 88]], 8, i);
1291      }
1292      fall(m, t, pink, 44, 3.4, 22, -300, 300, 1.4);
1293      return P;
1294    },
1295
1296    /* ---- high summer: long light, tall grass, fireflies coming out ---- */
1297    summer: function (m, t, p, seed) {
1298      /* Working the meadow flower by flower: walk across, kneel to it, shoot,
1299         move on to the next. */
1300      var SPOTS = [-40, 96, 214];
1301      var CYs = 16, qs = (t % CYs) / CYs;
1302      var legS = Math.min(SPOTS.length - 1, Math.floor(qs * SPOTS.length));
1303      var wS = qs * SPOTS.length - legS;
1304      var prevS = SPOTS[(legS + SPOTS.length - 1) % SPOTS.length];
1305      if (wS < 0.35) {
1306        ACTOR.act = "walk";
1307        ACTOR.x = prevS + (SPOTS[legS] - prevS) * (wS / 0.35);
1308        ACTOR.z = 42;
1309        ACTOR.face = SPOTS[legS] >= prevS ? -Math.PI / 2 : Math.PI / 2;
1310      } else {
1311        ACTOR.act = "photo"; ACTOR.x = SPOTS[legS]; ACTOR.z = 42;
1312      }
1313      var P = { soil: [140, 118, 82], soilSide: [112, 94, 64], turf: [122, 186, 106] };
1314      P.far = [[92, 168, 96], [122, 194, 118]]; P.mote = [255, 246, 180];
1315      P.cloudLit=[255,255,255];P.cloudShd=[214,228,240];P.birdCol=[66,74,68];
1316      skyworld(m, t, P, { clouds: 9, birds: 9 });
1317      surroundings(m, t, P, "leaf");
1318      ground(m, P, -300, 300, -70, 70);
1319      margins(m, t, P, "spring");
1320      var green = [[92, 168, 96], [122, 194, 118], [70, 142, 78]];
1321      blobTree(m, -170, -26, 92, 42, green, [[110, 80, 56], [86, 60, 42]], t, 2);
1322      blobTree(m, 150, -34, 78, 36, green, [[110, 80, 56], [86, 60, 42]], t, 6);
1323      blobTree(m, 258, 14, 54, 24, green, [[110, 80, 56], [86, 60, 42]], t, 9);
1324      for (var i = 0; i < 14; i++) {
1325        grassTuft(m, -280 + i * 42, 30 + (i % 3) * 16, t,
1326                  [[142, 202, 120], [110, 178, 96], [90, 156, 80]], 13, i);
1327      }
1328      /* bushes and wildflowers through the middle of the meadow */
1329      dome(m, -40, 30, 20, 16, [78, 148, 84], [110, 182, 112]);
1330      dome(m, 6, 44, 14, 11, [86, 158, 90], [118, 190, 118]);
1331      dome(m, 62, 26, 17, 13, [72, 140, 78], [104, 176, 106]);
1332      for (i = 0; i < 18; i++) {
1333        var fx = -270 + rnd(i, 24) * 540, fz = 14 + rnd(i, 25) * 46;
1334        var fh = 9 + rnd(i, 26) * 6;
1335        var fw = Math.sin(t * 1.5 + i) * 1.4;
1336        E.box(m, fx + fw, -fh / 2, fz, 0.7, fh / 2, 0.7, [96, 162, 88], null, 20, 0.3);
1337        E.box(m, fx + fw * 1.6, -fh - 1.4, fz, 2, 2, 2,
1338              [[255, 232, 120], [255, 250, 250], [255, 176, 200]][i % 3], null, 26, 0.9);
1339      }
1340      /* fireflies, blinking out of phase */
1341      for (i = 0; i < 16; i++) {
1342        var f = rnd(i, 12);
1343        var on = 0.35 + 0.65 * Math.abs(Math.sin(t * 1.6 + f * 8));
1344        E.box(m, -260 + f * 520 + Math.sin(t * 0.7 + i) * 22,
1345              -30 - rnd(i, 13) * 50 + Math.sin(t * 1.1 + i * 2) * 9,
1346              -60 + rnd(i, 14) * 120,
1347              1.5 * on, 1.5 * on, 1.5 * on, [255, 246, 150], null, 90, 0.5);
1348      }
1349      return P;
1350    },
1351
1352    /* ---- autumn: the maples let go and he walks through the drift ---- */
1353    autumn: function (m, t, p, seed) {
1354      var P = { soil: [138, 104, 74], soilSide: [110, 82, 58], turf: [186, 150, 100] };
1355      P.far = [[214, 96, 48], [232, 150, 62]]; P.mote = [240, 190, 130];
1356      P.cloudLit=[255,246,232];P.cloudShd=[228,208,186];P.dropCols=[[214,96,48],[232,150,62],[188,68,44]];P.birdCol=[86,64,50];
1357      skyworld(m, t, P, { clouds: 8, birds: 7, drop: "leaf", dropN: 40, dropSpeed: 0.2 });
1358      surroundings(m, t, P, "leaf");
1359      ground(m, P, -300, 300, -70, 70);
1360      margins(m, t, P, "autumn");
1361      var fire = [[214, 96, 48], [232, 150, 62], [188, 68, 44]];
1362      blobTree(m, -200, -30, 80, 36, fire, [[104, 74, 52], [80, 56, 40]], t, 3);
1363      blobTree(m, -50, 24, 56, 25, [[232, 168, 72], [214, 128, 54]], [[104, 74, 52], [80, 56, 40]], t, 5);
1364      blobTree(m, 140, -34, 88, 40, fire, [[104, 74, 52], [80, 56, 40]], t, 8);
1365      /* drifts of fallen leaves banked against the ground */
1366      for (var i = 0; i < 26; i++) {
1367        var lx = -280 + rnd(i, 15) * 560, lz = 8 + rnd(i, 16) * 54;
1368        E.box(m, lx, -1.6, lz, 3.4, 0.8, 2.6,
1369              fire[i % fire.length], null, 10, 1);
1370      }
1371      squirrel(m, t, -6, 56);
1372      E.gem(m, 16, 1, 210, -10, 34, [206, 118, 54], null);
1373      E.gem(m, 12, 1, 230, -8, 40, [226, 152, 66], null);
1374      fall(m, t, fire, 40, 4, 26, -300, 300, 2.1);
1375      return P;
1376    },
1377
1378    /* ---- winter: the cabin, the fire, and a mug of hot chocolate ---- */
1379    winter: function (m, t, p, seed) {
1380      /* A turn at the fire: lay a log on, warm through, then drink. */
1381      var qw = (t % 13) / 13;
1382      ACTOR.x = 226; ACTOR.z = 26;
1383      ACTOR.act = qw < 0.22 ? "feedfire" : qw < 0.58 ? "warm" : "sip";
1384      var P = {
1385        soil: [236, 244, 255], soilSide: [198, 212, 234], turf: [300, 308, 320],
1386        wall: [126, 92, 66], wallCap: [146, 108, 78], wallLine: [98, 70, 50],
1387        roof: [268, 280, 296], roofDark: [214, 228, 248],
1388        frame: [86, 62, 46], door: [96, 68, 48], stone: [140, 138, 144]
1389      };
1390      P.mote = [300, 306, 318];
1391      P.cloudLit=[248,252,255];P.cloudShd=[206,218,236];P.birdCol=[70,80,98];
1392      skyworld(m, t, P, { clouds: 12, birds: 2, drop: "snow", dropN: 74, dropSpeed: 0.16 });
1393      surroundings(m, t, P, "pine");
1394      ground(m, P, -300, 300, -70, 70);
1395      margins(m, t, P, "winter");
1396      cabin(m, -150, -18, P, t);
1397      pineTree(m, -258, -34, 96, [46, 112, 74], [32, 86, 56], true);
1398      pineTree(m, 236, -30, 88, [46, 112, 74], [32, 86, 56], true);
1399      pineTree(m, 300, 6, 70, [46, 112, 74], [32, 86, 56], true);
1400      campfire(m, 182, 20, t);
1401      /* the log he sits on */
1402      E.box(m, 226, -7, 26, 22, 6, 6, [122, 86, 62], null, 18, 2.4);
1403      /* drifts banked against the ground */
1404      dome(m, -46, 46, 26, 13, [304, 312, 324], [330, 336, 344]);
1405      dome(m, 272, 44, 20, 10, [298, 306, 320], [326, 332, 342]);
1406      dome(m, -6, 52, 14, 7, [304, 312, 324], [330, 336, 344]);
1407      fall(m, t, [[330, 334, 340], [310, 318, 332]], 60, 2.4, 16, -300, 300, 0.8);
1408      return P;
1409    },
1410
1411    /* ---- rainforest: rain coming through the canopy ---- */
1412    jungle: function (m, t, p, seed) {
1413      /* The orchard cycle. He sets a sapling, it runs up through its stages,
1414         comes into fruit, drops one, and he eats it. Then it starts again. */
1415      var CY = 13;                       /* seconds for the whole thing */
1416      var q = (t % CY) / CY;
1417      var TX = 44, TZ = 14;              /* clear of the standing trees */
1418      var grow, apples = 0, dropF = -1;
1419      if (q < 0.1) { grow = 0.04; ACTOR.act = "plant"; ACTOR.x = TX - 40; ACTOR.z = 34; }
1420      else if (q < 0.58) {
1421        grow = Math.pow((q - 0.1) / 0.48, 0.72);
1422        ACTOR.act = "watch"; ACTOR.x = 248; ACTOR.z = 38;
1423      } else if (q < 0.74) {
1424        grow = 1; apples = (q - 0.58) / 0.16;
1425        ACTOR.act = "watch"; ACTOR.x = 248; ACTOR.z = 38;
1426      } else if (q < 0.82) {
1427        grow = 1; apples = 1; dropF = (q - 0.74) / 0.08;
1428        ACTOR.act = "watch"; ACTOR.x = TX + 34; ACTOR.z = 40;
1429      } else {
1430        grow = 1; apples = 1 - (q - 0.82) / 0.18 * 0.3;
1431        ACTOR.act = "eat"; ACTOR.x = TX + 34; ACTOR.z = 40;
1432      }
1433      var P = { soil: [96, 74, 52], soilSide: [74, 56, 40], turf: [78, 138, 74] };
1434      P.far = [[62, 138, 68], [92, 172, 92]]; P.mote = [196, 226, 200];
1435      P.cloudLit=[228,238,226];P.cloudShd=[186,204,188];P.dropCol=[196,226,240];P.birdCol=[60,78,58];
1436      skyworld(m, t, P, { clouds: 12, birds: 4, drop: "rain", dropN: 68, dropSpeed: 0.9 });
1437      surroundings(m, t, P, "leaf");
1438      ground(m, P, -300, 300, -70, 70);
1439      margins(m, t, P, "jungle");
1440      var leaf = [[62, 138, 68], [92, 172, 92], [44, 108, 54]];
1441      blobTree(m, -210, -34, 108, 44, leaf, [[92, 68, 48], [70, 50, 36]], t, 2);
1442      blobTree(m, -60, -14, 84, 34, leaf, [[92, 68, 48], [70, 50, 36]], t, 5);
1443      blobTree(m, 160, -38, 118, 48, leaf, [[92, 68, 48], [70, 50, 36]], t, 9);
1444      for (var i = 0; i < 12; i++) {
1445        grassTuft(m, -280 + i * 50, 34 + (i % 2) * 14, t,
1446                  [[96, 176, 92], [70, 142, 76]], 16, i + 3);
1447      }
1448
1449      /* the tree he set, at whatever stage it has reached */
1450      (function () {
1451        var th = 12 + grow * 96;
1452        var cr = 6 + grow * 40;
1453        /* the mound it was planted in */
1454        E.box(m, TX, -3, TZ, 9, 3, 8, [96, 74, 52], null, 12, 2);
1455        if (grow < 0.12) {
1456          /* a shoot with two seed leaves */
1457          E.box(m, TX, -6, TZ, 1, 4, 1, [96, 158, 84], null, 20, 0.4);
1458          E.box(m, TX - 3, -9, TZ, 3, 0.9, 2, [122, 186, 96], null, 22, 0.8);
1459          E.box(m, TX + 3, -9, TZ, 3, 0.9, 2, [122, 186, 96], null, 22, 0.8);
1460          return;
1461        }
1462        trunk(m, TX, TZ, th, 2 + grow * 5, [110, 80, 56], [86, 60, 42], 0);
1463        /* boughs, once there is a trunk worth having them on */
1464        if (grow > 0.45) {
1465          for (var bq = 0; bq < 3; bq++) {
1466            var ba = (bq / 3) * Math.PI * 2 + 0.6;
1467            E.box(m, TX + Math.cos(ba) * cr * 0.4, -th * 0.72, TZ + Math.sin(ba) * cr * 0.3,
1468                  cr * 0.34, 1.6, 2, [98, 70, 48], null, 14, 0.8);
1469          }
1470        }
1471        canopy(m, TX, -th - cr * 0.45, TZ, cr,
1472               [[62, 138, 68], [92, 172, 92], [44, 108, 54]], t, 4);
1473        /* fruit, hung under the canopy once it is in season */
1474        if (apples > 0.02) {
1475          for (var aq = 0; aq < 7; aq++) {
1476            if (aq / 7 > apples) break;
1477            var aa = (aq / 7) * Math.PI * 2 + 0.4;
1478            var ax2 = TX + Math.cos(aa) * cr * 0.62;
1479            var az2 = TZ + Math.sin(aa) * cr * 0.42;
1480            var ay2 = -th - cr * 0.1 + Math.sin(aq * 2.1) * 6;
1481            if (aq === 0 && dropF >= 0) {
1482              /* the one that comes down, and rolls a little when it lands */
1483              var fy = ay2 + dropF * dropF * (Math.abs(ay2) - 4);
1484              var roll = dropF > 0.92 ? (dropF - 0.92) * 220 : 0;
1485              E.box(m, ax2 + roll, Math.min(-3, fy), az2, 2.4, 2.4, 2.4,
1486                    [206, 62, 54], null, 40, 1.1);
1487              continue;
1488            }
1489            E.box(m, ax2, ay2, az2, 2.4, 2.4, 2.4, [206, 62, 54], null, 30, 1.1);
1490            E.box(m, ax2, ay2 - 3, az2, 0.5, 1.2, 0.5, [96, 128, 70], null, 30, 0.3);
1491          }
1492        }
1493      })();
1494      /* undergrowth banked between the trunks */
1495      dome(m, -140, 34, 24, 18, [52, 118, 60], [84, 158, 86]);
1496      dome(m, -14, 46, 18, 13, [58, 128, 66], [92, 168, 94]);
1497      dome(m, 92, 30, 26, 20, [48, 112, 56], [80, 152, 82]);
1498      dome(m, 240, 40, 20, 15, [54, 122, 62], [88, 162, 90]);
1499      /* vines let down out of the canopy, swinging a little */
1500      var VN = [[-210, -34, 92], [-60, -14, 70], [160, -38, 100], [120, 10, 60]];
1501      for (i = 0; i < VN.length; i++) {
1502        var vx = VN[i][0] + (i % 2 ? 26 : -26), vz = VN[i][1] + 12, vh = VN[i][2];
1503        for (var k2 = 0; k2 < 9; k2++) {
1504          var kf = k2 / 9;
1505          var swing = Math.sin(t * 0.9 + i + kf * 2) * 5 * kf;
1506          E.box(m, vx + swing, -vh + kf * vh * 0.8, vz, 0.9, vh * 0.045, 0.9,
1507                [64, 116, 58], null, 20, 0.3);
1508          if (k2 % 2 === 0) {
1509            E.box(m, vx + swing + (k2 % 4 ? 3 : -3), -vh + kf * vh * 0.8, vz,
1510                  3, 1, 2, [92, 172, 92], null, 22, 0.8);
1511          }
1512        }
1513      }
1514      /* rain, drawn as fast falling threads */
1515      for (i = 0; i < 60; i++) {
1516        var f = rnd(i, 17);
1517        var ry = -((t * 150 + f * 220) % 220);
1518        var rx = -300 + rnd(i, 18) * 600;
1519        var rz = -70 + rnd(i, 19) * 140;
1520        m.line([rx, ry, rz], [rx - 3, ry + 16, rz], [206, 232, 240], 1.1, 0.4);
1521      }
1522      return P;
1523    },
1524
1525    /* ---- a koi pond: he crouches at the edge and feeds them ---- */
1526    pond: function (m, t, p, seed) {
1527      var P = { soil: [148, 118, 96], soilSide: [120, 94, 76], turf: [136, 190, 122] };
1528      underwater(m, t, p);
1529      P.far = [[168, 206, 150], [200, 224, 180]]; P.mote = [214, 236, 244];
1530      P.cloudLit=[255,250,252];P.cloudShd=[226,232,240];P.dropCols=[[255,186,210],[255,214,228]];P.birdCol=[86,80,90];
1531      skyworld(m, t, P, { clouds: 3, birds: 8, drop: "petal", dropN: 18, dropSpeed: 0.15 });
1532      surroundings(m, t, P, "leaf");
1533      /* Banks at either end only. The middle of the plot is open water the
1534         whole way across, rather than a channel cut through a lawn. */
1535      ground(m, P, -300, -190, -70, 70);
1536      ground(m, P, 190, 300, -70, 70);
1537      /* a lip along the back so the water reads as held rather than floating */
1538      E.box(m, 0, 4, 62, 196, 5, 9, P.soilSide, null, -220, 0);
1539      E.box(m, 0, -1.6, 62, 196, 1.6, 9, P.turf, null, -218, 0);
1540      /* the surface, and the light moving across it */
1541      E.box(m, 0, 6, 0, 194, 1.4, 66, [126, 190, 214], null, -210, 0);
1542      for (var wq = 0; wq < 26; wq++) {
1543        var wx2 = -186 + ((wq * 21 + t * 26) % 372);
1544        E.box(m, wx2, 4.6, -50 + (wq % 6) * 20, 8 + (wq % 3) * 5, 0.5, 1.4,
1545              [176, 224, 236], null, -206, 0.5);
1546      }
1547      /* stones set along both banks */
1548      for (var i = 0; i < 12; i++) {
1549        var bx2 = i < 6 ? -190 + 8 : 190 - 8;
1550        E.box(m, bx2, -3, -46 + (i % 6) * 19, 7, 4, 6, [162, 158, 154], null, 14, 2);
1551      }
1552      /* koi, turning under the surface */
1553      for (i = 0; i < 5; i++) {
1554        var ka = t * (0.3 + i * 0.07) + i * 1.6;
1555        var kr = 52 + i * 22;
1556        var kx = Math.cos(ka) * kr, kz = 6 + Math.sin(ka) * kr * 0.34;
1557        var col = i % 2 ? [246, 130, 92] : [252, 246, 244];
1558        E.box(m, kx, -2, kz, 6.5, 1.4, 3, col, null, -370, 1.2);
1559        E.box(m, kx - Math.cos(ka) * 7, -2, kz - Math.sin(ka) * 3.5, 2.6, 1.1, 2.2,
1560              col, null, -371, 0.9);
1561      }
1562      /* ripples where he is dropping food */
1563      for (i = 0; i < 3; i++) {
1564        var rp = ((t * 0.5 + i * 0.333) % 1);
1565        var rrr = 6 + rp * 26;
1566        var prev = null;
1567        for (var k = 0; k <= 20; k++) {
1568          var ang = (k / 20) * Math.PI * 2;
1569          var pt = [-46 + Math.cos(ang) * rrr, -1.2, 16 + Math.sin(ang) * rrr * 0.5];
1570          if (prev) m.line(prev, pt, [214, 240, 250], 1.1, 0.5 * (1 - rp));
1571          prev = pt;
1572        }
1573      }
1574      blobTree(m, -220, -34, 72, 32, [[255, 176, 203], [255, 200, 220]],
1575               [[122, 86, 62], [96, 66, 46]], t, 3);
1576      blobTree(m, 214, -30, 64, 28, [[132, 190, 120], [162, 210, 148]],
1577               [[110, 80, 56], [86, 60, 42]], t, 8);
1578      for (i = 0; i < 8; i++) grassTuft(m, -270 + i * 76, 58, t, [[122, 186, 106], [98, 162, 90]], 9, i);
1579      lantern(m, 150, 52, t, 1);
1580      return P;
1581    },
1582
1583    /* ---- the workshop: a bench under a tree, where the work gets made ---- */
1584    workshop: function (m, t, p, seed) {
1585      var P = { soil: [146, 116, 92], soilSide: [118, 92, 72], turf: [150, 190, 138] };
1586      P.far = [[132, 190, 120], [162, 210, 148]]; P.mote = [226, 238, 224];
1587      P.cloudLit=[255,252,250];P.cloudShd=[228,228,236];P.birdCol=[80,76,84];
1588      skyworld(m, t, P, { clouds: 8, birds: 6 });
1589      surroundings(m, t, P, "leaf");
1590      ground(m, P, -300, 300, -70, 70);
1591      margins(m, t, P, "spring");
1592      /* the bench, and a stool he sits on */
1593      E.box(m, 0, -22, -4, 46, 2.4, 18, [156, 116, 82], null, 20, 1);
1594      E.box(m, -40, -11, -4, 2.6, 11, 2.6, [124, 90, 64], null, 18, 0.8);
1595      E.box(m, 40, -11, -4, 2.6, 11, 2.6, [124, 90, 64], null, 18, 0.8);
1596      E.box(m, 18, -8, 22, 9, 8, 9, [132, 96, 68], null, 18, 1.4);
1597      /* a screen on the bench, its content flickering as it redraws */
1598      var lit = 0.7 + Math.sin(t * 3.1) * 0.12 + Math.sin(t * 7.7) * 0.06;
1599      E.box(m, -18, -34, -8, 13, 9.5, 1.4, [46, 40, 54], null, 26, 1);
1600      E.box(m, -18, -34, -9.4, 11.4, 8, 0.5, [90 * lit + 40, 150 * lit + 40, 210 * lit + 30], null, 30, 0.6);
1601      for (var r = 0; r < 4; r++) {
1602        var lw = 3 + ((Math.floor(t * 3) + r * 3) % 5) * 1.5;
1603        E.box(m, -18 - 4.5 + lw / 2, -38 + r * 2.4, -10, lw / 2, 0.4, 0.3,
1604              [220, 240, 255], null, 34, 0.2);
1605      }
1606      /* parts laid out along the bench */
1607      E.box(m, 4, -26, -6, 4, 2, 4, [214, 96, 124], null, 26, 1);
1608      E.box(m, 13, -25.6, -2, 3, 1.6, 3, [124, 168, 214], null, 26, 0.8);
1609      E.box(m, 32, -26, -4, 5, 2, 5, [232, 176, 92], null, 26, 1.2);
1610      /* a lamp on a stalk, throwing a pool on the bench */
1611      E.box(m, -38, -30, 2, 1, 8, 1, [96, 88, 96], null, 22, 0.4);
1612      E.box(m, -38, -39, 2, 4, 3, 4, [255, 226, 150], null, 26, 1.4);
1613      blobTree(m, -180, -34, 82, 36, [[132, 190, 120], [162, 210, 148], [110, 172, 104]],
1614               [[110, 80, 56], [86, 60, 42]], t, 5);
1615      blobTree(m, 208, -30, 66, 30, [[255, 190, 210], [255, 210, 226]],
1616               [[122, 86, 62], [96, 66, 46]], t, 2);
1617      for (var i = 0; i < 8; i++) grassTuft(m, -280 + i * 78, 54, t, [[130, 190, 112], [104, 166, 94]], 9, i);
1618      return P;
1619    },
1620
1621    /* ---- a bamboo grove, where he takes tea ---- */
1622    bamboo: function (m, t, p, seed) {
1623      var P = { soil: [140, 122, 88], soilSide: [112, 96, 68], turf: [168, 196, 128] };
1624      P.far = [[150, 190, 100], [176, 208, 128]]; P.mote = [232, 240, 210];
1625      P.cloudLit=[252,254,246];P.cloudShd=[224,232,214];P.dropCols=[[150,190,100],[176,208,128]];P.birdCol=[74,84,66];
1626      skyworld(m, t, P, { clouds: 7, birds: 5, drop: "leaf", dropN: 16, dropSpeed: 0.16 });
1627      surroundings(m, t, P, "leaf");
1628      ground(m, P, -300, 300, -70, 70);
1629      margins(m, t, P, "spring");
1630      /* culms, each swaying on its own beat, with joints up the stem */
1631      for (var i = 0; i < 16; i++) {
1632        var bx = -290 + rnd(i, 31) * 580;
1633        var bz = -60 + rnd(i, 32) * 100;
1634        var bh = 90 + rnd(i, 33) * 70;
1635        var lean = Math.sin(t * 0.6 + i) * 5;
1636        var segs = 7;
1637        for (var k = 0; k < segs; k++) {
1638          var f = k / segs;
1639          E.box(m, bx + lean * f * f, -f * bh - bh / (segs * 2), bz,
1640                2.6 - f * 0.7, bh / (segs * 2), 2.6 - f * 0.7,
1641                k % 2 ? [136, 176, 90] : [116, 158, 76], null, 10, 0.8);
1642          E.box(m, bx + lean * f * f, -f * bh - bh / segs, bz,
1643                3 - f * 0.7, 0.5, 3 - f * 0.7, [92, 132, 62], null, 12, 0.4);
1644        }
1645        /* a few leaves off the top */
1646        for (k = 0; k < 4; k++) {
1647          var la = k * 1.6 + i;
1648          E.box(m, bx + lean + Math.cos(la) * 9, -bh - 4 - k * 3, bz + Math.sin(la) * 6,
1649                6, 0.8, 1.6, [150, 190, 100], null, 14, 0.6);
1650        }
1651      }
1652      /* a mat and a low table for the tea */
1653      E.box(m, 0, -1.4, 26, 26, 1.4, 16, [206, 186, 142], null, -20, 1);
1654      E.box(m, 22, -7, 22, 8, 5, 7, [148, 110, 78], null, 18, 1.2);
1655      E.box(m, 22, -13, 22, 3, 1.6, 3, [244, 244, 240], null, 26, 1);
1656      for (i = 0; i < 4; i++) {
1657        var sp2 = ((t * 0.6 + i * 0.25) % 1);
1658        E.box(m, 22 + Math.sin(sp2 * 6 + i) * 2.6, -16 - sp2 * 14, 22,
1659              0.9 - sp2 * 0.4, 0.9 - sp2 * 0.4, 0.9 - sp2 * 0.4, [330, 332, 338], null, 30, 0.4);
1660      }
1661      var stone = [[-150, 40], [-90, 52], [120, 46], [190, 38]];
1662      for (i = 0; i < stone.length; i++) {
1663        E.box(m, stone[i][0], -3, stone[i][1], 8, 3, 6, [166, 160, 152], null, 12, 2);
1664      }
1665      return P;
1666    },
1667
1668    /* ---- the same water as the band above, carrying on down. No banks
1669            across the front and no platform edge, so the two read as one
1670            body rather than two worlds stacked. ---- */
1671    openwater: function (m, t, p, seed) {
1672      var P = { soil: [148, 118, 96], soilSide: [120, 94, 76], turf: [136, 190, 122],
1673                cloudLit: [255, 250, 252], cloudShd: [226, 232, 240],
1674                dropCols: [[255, 186, 210], [255, 214, 228]], birdCol: [86, 80, 90] };
1675      skyworld(m, t, P, { clouds: 2, birds: 6, drop: "petal", dropN: 16, dropSpeed: 0.15 });
1676      var i;
1677
1678      /* the water body, running off both sides of the frame */
1679      E.box(m, 0, 96, 0, 430, 104, 96, [46, 92, 116], null, -300, 0);
1680      /* No surface here. This band sits under the one the pond above already
1681         has, and drawing a second waterline is exactly what made the two read
1682         as separate bodies rather than one. */
1683      /* light coming down through it */
1684      for (i = 0; i < 12; i++) {
1685        var lx = -400 + i * 70 + Math.sin(t * 0.5 + i) * 14;
1686        m.line([lx, 10, -40 + (i % 3) * 34], [lx + 26, 108, 10 + (i % 2) * 30],
1687               [176, 224, 236], 2.4, 0.1 + 0.05 * Math.sin(t * 1.3 + i));
1688      }
1689      /* weed on the bed */
1690      for (i = 0; i < 20; i++) {
1691        var vx = -410 + rnd(i, 81) * 820, vz = -60 + rnd(i, 82) * 120;
1692        var vh = 16 + rnd(i, 83) * 26;
1693        var lean = Math.sin(t * 0.7 + i) * 6;
1694        for (var k = 0; k < 5; k++) {
1695          var f = k / 5;
1696          E.box(m, vx + lean * f * f, 118 - f * vh - vh / 10, vz,
1697                2 - f * 0.7, vh / 10, 2 - f * 0.7,
1698                k % 2 ? [64, 138, 96] : [48, 116, 78], null, -60, 0.6);
1699        }
1700      }
1701      for (i = 0; i < 9; i++) {
1702        var sr = 8 + rnd(i, 84) * 11;
1703        E.box(m, -390 + rnd(i, 85) * 780, 116 - sr * 0.5, -50 + rnd(i, 86) * 100,
1704              sr, sr * 0.5, sr * 0.7, [104, 108, 104], null, -70, sr * 0.4);
1705      }
1706      /* the koi, working the whole width */
1707      for (i = 0; i < 9; i++) {
1708        var a = t * (0.15 + i * 0.03) + i * 1.1;
1709        var rr = 70 + i * 34;
1710        var kx = Math.cos(a) * rr;
1711        var kz = Math.sin(a) * rr * 0.3;
1712        var ky = 34 + i * 8 + Math.sin(t * 0.8 + i) * 7;
1713        var col = i % 3 === 0 ? [252, 246, 244] : i % 3 === 1 ? [246, 130, 92] : [236, 176, 90];
1714        var tail = Math.sin(t * 4 + i) * 4;
1715        E.box(m, kx, ky, kz, 9, 3.4, 4.4, col, null, -40, 1.6);
1716        E.box(m, kx - Math.cos(a) * 11, ky, kz - Math.sin(a) * 4 + tail, 4, 2.4, 3, col, null, -41, 1.2);
1717        E.box(m, kx - Math.cos(a) * 15, ky, kz - Math.sin(a) * 6 + tail * 1.6,
1718              2.6, 3.4, 1.6, col, null, -42, 0.8);
1719        E.box(m, kx + Math.cos(a) * 7, ky - 1, kz + Math.sin(a) * 2.4 - 3.4,
1720              1.4, 1.4, 0.8, [32, 34, 40], null, -38, 0.4);
1721      }
1722      /* bubbles */
1723      for (i = 0; i < 16; i++) {
1724        var bf = ((t * 0.3 + rnd(i, 87)) % 1);
1725        E.box(m, -300 + (i % 8) * 84 + Math.sin(bf * 8 + i) * 9,
1726              116 - bf * 100, -30 + (i % 5) * 16,
1727              1 + bf * 1.6, 1 + bf * 1.6, 1 + bf * 1.6, [186, 226, 238], null, -30, 0.8);
1728      }
1729      /* shafts coming down from the surface overhead */
1730      for (i = 0; i < 12; i++) {
1731        var sx2 = -400 + i * 70 + Math.sin(t * 0.4 + i) * 12;
1732        m.line([sx2, -30, -40 + (i % 3) * 34], [sx2 + 30, 190, 10 + (i % 2) * 30],
1733               [176, 224, 236], 2.6, 0.09);
1734      }
1735      return P;
1736    },
1737
1738    /* ---- the craft floor: a wheel throwing a pot, a kiln firing, and the
1739            shelf it all ends up on. For the band about how the work is made. */
1740    craft: function (m, t, p, seed) {
1741      /* One loaf, all the way through: rolled out on the board, shaped,
1742         carried in on the peel, baked while the oven works, drawn back out
1743         and set on the shelf. Then the next one. */
1744      var P = { soil: [138, 112, 92], soilSide: [112, 90, 72], turf: [176, 158, 132],
1745                cloudLit: [255, 250, 248], cloudShd: [230, 220, 222], mote: [246, 226, 236],
1746                tool: [150, 122, 92], loaf: [206, 156, 96] };
1747      skyworld(m, t, P, { clouds: 7, birds: 5 });
1748      surroundings(m, t, P, "leaf");
1749      ground(m, P, -300, 300, -70, 70);
1750      margins(m, t, P, "craft");
1751      var i;
1752
1753      var CY = 15;
1754      var q = (t % CY) / CY;
1755      var BX = -170, OX = 150;               /* the board, and the oven */
1756      var doughR = 0, onBoard = 0, onPeel = 0, inOven = 0, shelfExtra = 0;
1757
1758      if (q < 0.26) {                        /* rolling it out */
1759        ACTOR.act = "roll"; ACTOR.x = BX; ACTOR.z = 42;
1760        doughR = 0.4 + (q / 0.26) * 0.6; onBoard = 1;
1761      } else if (q < 0.36) {                 /* shaped, and taken up */
1762        ACTOR.act = "roll"; ACTOR.x = BX; ACTOR.z = 42;
1763        doughR = 1; onBoard = 1;
1764      } else if (q < 0.46) {                 /* carried in on the peel */
1765        ACTOR.act = "peel"; ACTOR.x = OX - 66; ACTOR.z = 40; onPeel = 1;
1766      } else if (q < 0.74) {                 /* baking */
1767        ACTOR.act = "warm"; ACTOR.x = OX - 58; ACTOR.z = 44; inOven = 1;
1768      } else if (q < 0.86) {                 /* drawn back out */
1769        ACTOR.act = "peel"; ACTOR.x = OX - 66; ACTOR.z = 40; onPeel = 1;
1770      } else {                               /* set on the shelf */
1771        ACTOR.act = "roll"; ACTOR.x = BX; ACTOR.z = 42; shelfExtra = 1;
1772      }
1773      P.onPeel = onPeel;
1774
1775      /* --- the board he works on --- */
1776      E.box(m, BX, -22, 30, 34, 2.4, 16, [156, 122, 88], null, 18, 1);
1777      E.box(m, BX - 28, -11, 30, 2.6, 11, 2.6, [124, 94, 64], null, 16, 0.8);
1778      E.box(m, BX + 28, -11, 30, 2.6, 11, 2.6, [124, 94, 64], null, 16, 0.8);
1779      /* flour, a jar and a bowl at the back of it */
1780      E.box(m, BX - 20, -26, 36, 5, 2, 5, [236, 230, 216], null, 22, 1.4);
1781      E.box(m, BX + 18, -28, 36, 4, 4, 4, [186, 196, 196], null, 22, 1.4);
1782      if (onBoard) {
1783        /* the dough, spreading as it is worked */
1784        E.box(m, BX, -25.4, 26, 5 + doughR * 6, 1.6 - doughR * 0.5, 4 + doughR * 3,
1785              [232, 214, 178], null, 26, 1.4);
1786      }
1787
1788      /* --- the oven --- */
1789      E.box(m, OX, -34, 20, 34, 34, 30, [128, 108, 96], null, 0, 3);
1790      E.box(m, OX, -72, 20, 26, 6, 24, [104, 86, 76], null, 4, 2);
1791      E.box(m, OX, -86, 20, 7, 9, 7, [104, 86, 76], null, 6, 2);
1792      /* the mouth, brighter while there is something in it */
1793      var burn = (inOven ? 0.85 : 0.5) + Math.sin(t * 2.8) * 0.16 + Math.sin(t * 6.1) * 0.08;
1794      E.box(m, OX, -30, -11, 12, 10, 1.4, [92, 70, 62], null, 28, 1.4);
1795      E.box(m, OX, -30, -12, 10, 8, 0.8,
1796            [255, 150 + 90 * burn, 60 + 50 * burn], { emit: 1, glow: 14 * burn }, 30, 1.2);
1797      if (inOven) {
1798        /* the loaf sitting in the mouth, darkening as it bakes */
1799        var bake = (q - 0.46) / 0.28;
1800        E.box(m, OX, -28, -8, 4, 2.2 + bake * 0.8, 3.2,
1801              [232 - bake * 30, 200 - bake * 46, 150 - bake * 54], null, 34, 1.6);
1802      }
1803      for (i = 0; i < 6; i++) {               /* heat off the stack */
1804        var hf = ((t * 0.5 + i * 0.166) % 1);
1805        E.gem(m, 2 + hf * 5, 1, OX + Math.sin(hf * 6 + i) * 9, -96 - hf * 54, 20,
1806              [236 + hf * 14, 226 + hf * 16, 220 + hf * 20], null);
1807      }
1808      /* firewood stacked against it */
1809      for (i = 0; i < 6; i++) {
1810        E.box(m, OX + 30, -4 - (i % 3) * 6, 44 + Math.floor(i / 3) * 8,
1811              9, 3, 3, i % 2 ? [122, 92, 62] : [104, 78, 52], null, 14, 1.4);
1812      }
1813
1814      /* --- the shelf, filling up through the day --- */
1815      E.box(m, -20, -54, 52, 58, 2, 12, [140, 114, 90], null, 10, 1);
1816      E.box(m, -20, -30, 52, 58, 2, 12, [140, 114, 90], null, 10, 1);
1817      var loaves = 4 + shelfExtra;
1818      for (i = 0; i < loaves; i++) {
1819        var sx = -62 + i * 22;
1820        E.box(m, sx, -60, 52, 7, 4, 5, [212 - (i % 3) * 14, 168 - (i % 3) * 12, 108],
1821              null, 14, 2.4);
1822        if (i < 3) {
1823          E.box(m, sx, -36, 52, 6.5, 3.6, 4.6, [206 - (i % 2) * 12, 160, 104],
1824                null, 14, 2.2);
1825        }
1826      }
1827      /* a basket of them at the foot */
1828      E.box(m, 58, -6, 44, 12, 6, 9, [158, 126, 82], null, 12, 2);
1829      E.box(m, 58, -13, 44, 9, 3, 6, [212, 170, 112], null, 16, 2.4);
1830
1831      for (i = 0; i < 9; i++) {
1832        grassTuft(m, -270 + i * 66, 60, t, [[150, 136, 112], [128, 116, 96]], 7, i);
1833      }
1834      return P;
1835    },
1836
1837    /* ---- dusk: lanterns lit along the path, the day closing ---- */
1838    dusk: function (m, t, p, seed) {
1839      var P = { soil: [128, 96, 78], soilSide: [102, 76, 60], turf: [166, 132, 104] };
1840      P.wide = 1;
1841      P.far = [[214, 138, 96], [232, 172, 118]]; P.mote = [255, 206, 150];
1842      P.cloudLit=[255,232,206];P.cloudShd=[226,186,158];P.birdCol=[92,66,58];
1843      skyworld(m, t, P, { clouds: 8, birds: 8 });
1844      surroundings(m, t, P, "leaf");
1845      ground(m, P, -430, 430, -70, 70);
1846      margins(m, t, P, "dusk");
1847      stream(m, t, true, p);
1848      drifter(m, 0);
1849      var warm = [[214, 138, 96], [232, 172, 118], [188, 110, 78]];
1850      blobTree(m, -200, -30, 72, 32, warm, [[92, 66, 48], [70, 50, 36]], t, 4);
1851      blobTree(m, 190, -32, 82, 36, warm, [[92, 66, 48], [70, 50, 36]], t, 7);
1852      /* Six lanterns, lit one at a time. He walks to the next, reaches up to
1853         it, it comes on, and he moves along. Once the path is lit they go
1854         out together and he starts at the near end again. */
1855      var LN = 6, CYd = 19;
1856      var qd = (t % CYd) / CYd;
1857      var slot = Math.min(LN - 1, Math.floor(qd * (LN + 0.6)));
1858      var within = qd * (LN + 0.6) - slot;
1859      var lx0 = -230 + slot * 92;
1860      if (within < 0.55) {
1861        /* on his way, between the last one and this */
1862        var from = -230 + Math.max(0, slot - 1) * 92;
1863        ACTOR.act = "walk";
1864        ACTOR.x = from + (lx0 - from) * (within / 0.55) - 22;
1865        ACTOR.z = 56;
1866        ACTOR.face = lx0 >= from ? -Math.PI / 2 : Math.PI / 2;
1867      } else {
1868        ACTOR.act = "light"; ACTOR.x = lx0 - 22; ACTOR.z = 52;
1869      }
1870      for (var i = 0; i < LN; i++) {
1871        /* lit once he has been to it, and only while the path is still filling */
1872        var on = qd < 0.94 && (i < slot || (i === slot && within > 0.7));
1873        lantern(m, -230 + i * 92, 44, t, i, on ? 1 : 0.12);
1874      }
1875      for (i = 0; i < 8; i++) {
1876        grassTuft(m, -250 + i * 72, 20, t, [[150, 122, 88], [128, 102, 72]], 8, i);
1877      }
1878      return P;
1879    },
1880
1881    /* ---- night: the year closes out under the stars ---- */
1882    night: function (m, t, p, seed) {
1883      /* the dusk ground after dark, not a different ground */
1884      var P = { soil: [92, 70, 58], soilSide: [72, 54, 44], turf: [116, 94, 76] };
1885      P.mote = [214, 226, 255];
1886      P.cloudLit=[188,196,224];P.cloudShd=[142,150,182];P.birdCol=[52,56,78];
1887      skyworld(m, t, P, { clouds: 7, birds: 2 });
1888      surroundings(m, t, P, "pine");
1889      ground(m, P, -430, 430, -70, 70);
1890      margins(m, t, P, "night");
1891      stream(m, t, false, p);
1892      drifter(m, 1);
1893      pineTree(m, -230, -30, 88, [34, 66, 74], [24, 48, 56], false);
1894      pineTree(m, 220, -28, 78, [34, 66, 74], [24, 48, 56], false);
1895      lantern(m, -60, 36, t, 1);
1896      lantern(m, 90, 40, t, 3);
1897      campfire(m, -290, 22, t);
1898      E.box(m, -250, -7, 30, 20, 6, 6, [92, 70, 56], null, 18, 2.4);
1899      for (var i = 0; i < 22; i++) {
1900        var tw = 0.4 + 0.6 * Math.abs(Math.sin(t * 1.2 + i));
1901        E.box(m, -280 + rnd(i, 21) * 560, -110 - rnd(i, 22) * 60, -60 + rnd(i, 23) * 120,
1902              1.4 * tw, 1.4 * tw, 1.4 * tw, [226, 236, 255], null, 90, 0.5);
1903      }
1904      return P;
1905    }
1906  };
1907
1908  /* who he is in each world, and what he is doing there. He stays in one
1909     place and repeats the action; nothing here is driven by the scroll. */
1910  /* A world can hand back an act and a place to stand, so a scene and the
1911     figure in it run one sequence together rather than two loops side by
1912     side. Cleared before every build. */
1913  var ACTOR = { act: null, x: 0, z: 0, face: 0 };
1914
1915  /* Absolute seconds, the same number in every world on any given frame. Each
1916     mount's own `t` starts at its first painted frame, so two bands can never
1917     agree on it; anything that has to be handed from one band to the next is
1918     timed off this instead. */
1919  var CLOCK = 0;
1920
1921  /* --- shared frame budget ------------------------------------------------
1922     Eleven worlds, the sky and the product models all draw on the same thread,
1923     so no one of them can tell on its own whether the page is keeping up. They
1924     report into this instead: it holds a rolling average of how long a world
1925     frame costs and hands back a quality level everything scales to. On a
1926     machine that is coping nothing changes; on one that is not, resolution and
1927     rate come down together and the page stays smooth instead of stuttering. */
1928  /* Wide screens make each full-width canvas much more expensive even when a
1929     single world frame looks cheap in isolation. Start desktop at the same
1930     30fps / near-1x quality tier the adaptive budget would eventually choose,
1931     and do not immediately climb back to 60fps before combined page cost is
1932     visible. Mobile keeps the original full-quality behaviour. */
1933  var desktopBudget = window.matchMedia && window.matchMedia("(min-width: 900px)").matches;
1934  var BUDGET = {
1935    avg: 6,
1936    level: desktopBudget ? 0.82 : 1,
1937    maxLevel: desktopBudget ? 0.82 : 1,
1938    checked: 0
1939  };
1940  function budgetReport(ms) {
1941    BUDGET.avg += (ms - BUDGET.avg) * 0.08;
1942    BUDGET.checked++;
1943    if (BUDGET.checked < 20) return;              /* let it settle first */
1944    /* a world frame over about 9ms means the page cannot hold 60 with this
1945       many of them; over 16 it cannot hold 30 either */
1946    if (BUDGET.avg > 15 && BUDGET.level > 0.55) BUDGET.level -= 0.05;
1947    else if (BUDGET.avg > 9 && BUDGET.level > 0.75) BUDGET.level -= 0.02;
1948    else if (BUDGET.avg < 5 && BUDGET.level < BUDGET.maxLevel) {
1949      BUDGET.level = Math.min(BUDGET.maxLevel, BUDGET.level + 0.01);
1950    }
1951  }
1952  window.CLX_BUDGET = BUDGET;
1953
1954  /* One observer gates every world. Previously all twelve mounts called
1955     getBoundingClientRect on every animation frame, even several screens away,
1956     which made desktop scrolling fight needless layout reads. */
1957  var worldObserver = null;
1958  if ("IntersectionObserver" in window) {
1959    worldObserver = new IntersectionObserver(function (entries) {
1960      for (var oi = 0; oi < entries.length; oi++) {
1961        entries[oi].target._clxWorldActive = entries[oi].isIntersecting;
1962      }
1963    }, { rootMargin: "120px 0px" });
1964  }
1965
1966  /* worlds nobody stands in */
1967  var UNMANNED = { openwater: 1 };
1968
1969  var CAST = {
1970    sakura:   { pal: {},                                                              act: "sleep", x: -14,  z: 46 },
1971    pond:     { pal: { coat: [110, 148, 190], coatIn: [148, 182, 216], tool: [138, 100, 70] }, act: "fish", x: -196, z: 46 },
1972    workshop: { pal: { coat: [96, 110, 148], coatIn: [132, 148, 186] },                act: "code",  x: 14,   z: 24 },
1973    bamboo:   { pal: { coat: [126, 152, 96], coatIn: [162, 186, 126] },                act: "tea",   x: -206, z: 34 },
1974    summer:   { pal: { coat: [236, 238, 244], coatIn: [214, 220, 232], cam: [58, 60, 70] }, act: "photo", x: 214, z: 42 },
1975    autumn:   { pal: { coat: [176, 96, 62], coatIn: [206, 132, 88], scarf: [214, 96, 48],
1976                       tool: [138, 100, 70] },                                        act: "rake",  x: 24,   z: 34 },
1977    winter:   { pal: { coat: [78, 96, 142], coatIn: [110, 130, 178], scarf: [214, 96, 48] },
1978                                                                                      act: "sip",   x: 226,  z: 26 },
1979    jungle:   { pal: { coat: [86, 132, 106], coatIn: [116, 164, 134] },                act: "watch", x: 248,  z: 38 },
1980    craft:    { pal: { coat: [242, 238, 230], coatIn: [214, 206, 192], tool: [150, 122, 92] }, act: "roll", x: -170, z: 42 },
1981    dusk:     { pal: { coat: [154, 96, 82], coatIn: [188, 128, 108] },                 act: "light", x: -262, z: 40 },
1982    night:    { pal: { coat: [62, 74, 116], coatIn: [92, 106, 152], scarf: [214, 96, 48] },
1983                                                                                      act: "warm",  x: -250, z: 30 }
1984  };
1985
1986  /* how far down the frame each world sits, so scenes with depth below the
1987     ground line still fit */
1988  /* The open water band sits high in its frame on purpose: the surface has to
1989     reach the top edge, or the empty sky above it shows as a stripe of page
1990     between this water and the pond it is supposed to be continuing. */
1991  var OY = { pond: 0.02, openwater: -0.46, dusk: 0.26, night: 0.26 };
1992
1993  /* --------------------------------------------------------------- mounting */
1994  function mount(host, kind, idx) {
1995    var build = WORLDS[kind];
1996    if (!build) return;
1997    var cast = CAST[kind] || CAST.sakura;
1998
1999    var wrap = document.createElement("div");
2000    wrap.className = "world";
2001    wrap.setAttribute("aria-hidden", "true");
2002    wrap._clxWorldActive = !worldObserver;
2003    if (worldObserver) worldObserver.observe(wrap);
2004    var cv = document.createElement("canvas");
2005    wrap.appendChild(cv);
2006    host.insertBefore(wrap, host.firstChild);
2007    var ctx = cv.getContext("2d");
2008    var W = 0, H = 0, dpr = 1;
2009
2010    function size(r) {
2011      r = r || wrap.getBoundingClientRect();
2012      if (!r.width || !r.height) return false;
2013      if (Math.round(r.width) === W && Math.round(r.height) === H &&
2014          Math.abs(dpr - Math.min(window.devicePixelRatio || 1, 1.5) * BUDGET.level) < 0.06) return true;
2015      dpr = Math.min(window.devicePixelRatio || 1, 1.5) * BUDGET.level;
2016      W = Math.round(r.width); H = Math.round(r.height);
2017      cv.width = Math.round(W * dpr); cv.height = Math.round(H * dpr);
2018      cv.style.width = W + "px"; cv.style.height = H + "px";
2019      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
2020      return true;
2021    }
2022
2023    function nameTag(pt, alpha) {
2024      if (!pt) return;
2025      var label = "WuYuXiang";
2026      ctx.save();
2027      ctx.globalAlpha = alpha;
2028      ctx.font = '600 12px "Share Tech Mono", ui-monospace, monospace';
2029      var tw = ctx.measureText(label).width;
2030      var pw = tw + 26, ph = 20;
2031      var bx = pt[0] - pw / 2, by = pt[1] - ph - 12;
2032      /* the leader down to his head */
2033      ctx.strokeStyle = "rgba(30,26,34,0.35)";
2034      ctx.lineWidth = 1;
2035      ctx.beginPath(); ctx.moveTo(pt[0], by + ph); ctx.lineTo(pt[0], pt[1] - 2); ctx.stroke();
2036      /* plate */
2037      ctx.fillStyle = "rgba(255,255,255,0.9)";
2038      ctx.strokeStyle = "rgba(30,26,34,0.22)";
2039      if (ctx.roundRect) { ctx.beginPath(); ctx.roundRect(bx, by, pw, ph, 6); }
2040      else { ctx.beginPath(); ctx.rect(bx, by, pw, ph); }
2041      ctx.fill(); ctx.stroke();
2042      /* a live dot, so the tag reads as a marker rather than a caption */
2043      ctx.fillStyle = "#e75a7c";
2044      ctx.beginPath(); ctx.arc(bx + 9, by + ph / 2, 2.6, 0, Math.PI * 2); ctx.fill();
2045      ctx.fillStyle = "#2b1f29";
2046      ctx.textBaseline = "middle";
2047      ctx.fillText(label, bx + 16, by + ph / 2 + 0.5);
2048      ctx.restore();
2049    }
2050
2051    /* a click anywhere on the strip lands in the scene */
2052    var hit = { pulse: 0, x: 0 };
2053    wrap.style.pointerEvents = "auto";
2054    wrap.style.cursor = "pointer";
2055    wrap.addEventListener("click", function (ev) {
2056      var r2 = wrap.getBoundingClientRect();
2057      if (!r2.width) return;
2058      /* screen x across the strip maps onto the scene's own width */
2059      hit.x = ((ev.clientX - r2.left) / r2.width - 0.5) * 620;
2060      hit.pulse = 1;
2061    });
2062
2063    var t0 = null;
2064    function frame(now) {
2065      var r = wrap.getBoundingClientRect();
2066      var vh = window.innerHeight || 800;
2067      if (r.bottom < -60 || r.top > vh + 60) return;
2068      if (!size(r)) return;
2069      if (t0 === null) t0 = now;
2070      var t = (now - t0) / 1000;
2071      CLOCK = now / 1000;
2072
2073      /* p: how far this band has travelled up the screen, 0 arriving, 1 gone */
2074      var p = 1 - Math.max(0, Math.min(1, (r.top + r.height * 0.5) / (vh * 0.9)));
2075      var travel = (p - 0.5) * 2;   /* how far through the band we are */
2076
2077      hit.pulse = Math.max(0, hit.pulse - 0.012);
2078      ACT.pulse = hit.pulse; ACT.x = hit.x;
2079
2080      ctx.clearRect(0, 0, W, H);
2081      ACTOR.act = null; ACTOR.face = 0;
2082      var m = new E.Model();
2083      build(m, t, p, idx);
2084
2085      if (!UNMANNED[kind]) {
2086        person(m, ACTOR.act ? ACTOR.x : cast.x, ACTOR.act ? ACTOR.z : cast.z,
2087               t, personPal(cast.pal), ACTOR.act || cast.act,
2088               ACTOR.act ? ACTOR.face : 0);
2089      }
2090
2091      /* Fixed camera. Turning it with the scroll meant two worlds meeting at a
2092         band boundary sat at different angles and scales, so their ground
2093         lines could never line up. Scroll drives what happens inside a scene
2094         instead, through p, not where it is viewed from. */
2095      var yaw = Math.sin(t * 0.11) * 0.09;
2096      var pitch = 0.2;
2097      var scale = Math.max(0.34, W / 780);
2098      E.render(ctx, m, W, H, yaw, pitch, scale,
2099               H * (OY[kind] === undefined ? 0.3 : OY[kind]));
2100      /* the name plate is gone: it followed him everywhere and pulled the
2101         eye off whatever he was actually doing */
2102    }
2103
2104    if (reduce) { size(); frame(0); return; }
2105    /* These are background scenery, so they run at half rate. It halves the
2106       cost of rebuilding and depth-sorting a few hundred faces per world and
2107       is not noticeable on a diorama that only drifts. */
2108    var last = -1e9;
2109    (function loop(now) {
2110      requestAnimationFrame(loop);
2111      now = now || 0;
2112      if (document.hidden) return;                /* nothing to see */
2113      if (!wrap._clxWorldActive) return;          /* off-screen worlds stay idle */
2114      /* Sixty while the page is coping, thirty when it is starting to
2115         struggle, twenty when it plainly is not. The budget below already
2116         measures what a world frame costs and was written against 60 all
2117         along; the rate was simply never allowed up there. 15 rather than
2118         16.67 so a frame arriving a shade early is not thrown away, which
2119         would halve the rate rather than trim it. */
2120      if (now - last < (BUDGET.level > 0.85 ? 15 : BUDGET.level > 0.7 ? 24 : 50)) return;
2121      last = now;
2122      var t1 = (window.performance && performance.now) ? performance.now() : 0;
2123      frame(now);
2124      if (t1) budgetReport(performance.now() - t1);
2125    })();
2126  }
2127
2128  function boot() {
2129    /* every band on the home page gets a world, including the ones that
2130       were still flat colour: the strip, the closing call and the footer */
2131    /* every band gets its own scene, none of them repeated */
2132    var picks = [
2133      [".hero", "pond"],
2134      [".strip", "openwater"],
2135      ["#products", "workshop"],
2136      ["#stack", "bamboo"],
2137      ["#about", "craft"],
2138      ["#extensions", "sakura"],
2139      ["#apps", "summer"],
2140      ["#websites", "autumn"],
2141      ["#ai", "winter"],
2142      ["#subproducts", "jungle"],
2143      [".cta", "dusk"],
2144      [".footer", "night"]
2145    ];
2146    var n = 0;
2147    for (var i = 0; i < picks.length; i++) {
2148      var el = document.querySelector(picks[i][0]);
2149      if (!el) continue;
2150      if (getComputedStyle(el).position === "static") el.style.position = "relative";
2151      mount(el, picks[i][1], n++);
2152    }
2153  }
2154
2155  /* the flight builds on the same scenes, so it needs to reach them */
2156  window.CLX_WORLDS = {
2157    worlds: WORLDS, cast: CAST, person: person, pal: personPal,
2158    actor: ACTOR, unmanned: UNMANNED,
2159    setClock: function (v) { CLOCK = v; }
2160  };
2161
2162  if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot);
2163  else boot();
2164})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.