@homie-rocks/studio 0.30.1 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +111 -1
- package/README.md +361 -22
- package/assets/animate.ts +16 -6
- package/assets/assets.ts +14 -3
- package/assets/render-page.ts +10 -2
- package/assets/safety.mjs +7 -1
- package/bin/homie-studio.mjs +142 -123
- package/lib/art-cli.mjs +61 -18
- package/lib/art-tools.mjs +53 -10
- package/lib/asset-check.mjs +249 -46
- package/lib/asset-import.mjs +11 -5
- package/lib/asset-manifest.mjs +128 -51
- package/lib/build.mjs +239 -72
- package/lib/characters.mjs +119 -20
- package/lib/check.mjs +110 -7
- package/lib/chrome.mjs +11 -0
- package/lib/clips.mjs +54 -0
- package/lib/cloudflare.mjs +127 -8
- package/lib/codex.mjs +6 -0
- package/lib/collision.mjs +245 -0
- package/lib/decisions.mjs +2 -2
- package/lib/demo.mjs +16 -4
- package/lib/deploy-state.mjs +166 -0
- package/lib/dev.mjs +430 -0
- package/lib/directory.mjs +116 -5
- package/lib/doctor.mjs +143 -16
- package/lib/feedback.mjs +3 -2
- package/lib/handoff.mjs +2 -6
- package/lib/library.mjs +1 -1
- package/lib/mcp-tools.mjs +36 -35
- package/lib/mcp.mjs +1 -1
- package/lib/net.mjs +81 -2
- package/lib/node-version.mjs +40 -0
- package/lib/office.mjs +6 -6
- package/lib/optimise.mjs +39 -4
- package/lib/parts-build.mjs +169 -0
- package/lib/parts-cli.mjs +104 -0
- package/lib/parts-store.mjs +342 -0
- package/lib/parts-tools.mjs +92 -0
- package/lib/parts.mjs +693 -0
- package/lib/perf.mjs +131 -31
- package/lib/port-check.mjs +40 -6
- package/lib/preview.mjs +77 -0
- package/lib/routes.mjs +132 -0
- package/lib/scaffold.mjs +88 -22
- package/lib/shoot.mjs +307 -0
- package/lib/site.mjs +1 -2
- package/lib/stage.mjs +118 -0
- package/lib/studio.mjs +0 -66
- package/lib/style-board.mjs +49 -6
- package/lib/template-history.json +101 -0
- package/lib/trailer.mjs +47 -0
- package/lib/typecheck.mjs +107 -0
- package/mcp/ui/lineup.js +3 -3
- package/mcp/ui/rights.js +1 -3
- package/netplay/NETPLAY.md +424 -16
- package/netplay/netplay.ts +756 -40
- package/package.json +10 -1
- package/parts/PARTS.md +163 -0
- package/port/index.ts +8 -2
- package/port/personas.ts +275 -0
- package/port/powerups.ts +357 -0
- package/port/probe.ts +38 -2
- package/port/room.ts +103 -19
- package/site/SITE.md +102 -20
- package/starters/ember-vale/index.html +1 -1
- package/starters/ember-vale/src/main.ts +3 -1
- package/starters/gem-rush/index.html +1 -1
- package/starters/gem-rush/src/main.ts +13 -6
- package/starters/gem-rush-3d/assets/RIGHTS.md +0 -24
- package/starters/gem-rush-3d/assets/manifest.json +0 -24
- package/starters/gem-rush-3d/index.html +1 -1
- package/starters/gem-rush-3d/src/main.ts +15 -5
- package/starters/hero-rush-3d/assets/RIGHTS.md +0 -19
- package/starters/hero-rush-3d/assets/manifest.json +0 -19
- package/starters/hero-rush-3d/index.html +1 -1
- package/starters/hero-rush-3d/src/main.ts +15 -5
- package/worker/arrival.mjs +4 -1
- package/worker/discover.mjs +11 -25
- package/worker/index.mjs +142 -62
- package/worker/license.mjs +45 -41
- package/worker/office-page.mjs +0 -4
- package/worker/office-schema.mjs +6 -0
- package/worker/office.mjs +19 -23
- package/worker/pages.mjs +141 -23
- package/worker/parts.mjs +234 -0
- package/worker/room.mjs +157 -14
- package/worker/schema.mjs +6 -8
- package/worker/seats.mjs +71 -0
- package/worker/site.mjs +51 -53
- package/worker/version.mjs +1 -1
- package/lib/remix-assets.mjs +0 -84
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
}
|
|
30
30
|
],
|
|
31
31
|
"license": {
|
|
32
|
-
"remix": "include",
|
|
33
32
|
"kind": "cc0",
|
|
34
33
|
"spdx": "CC0-1.0",
|
|
35
34
|
"owner": "Kenney",
|
|
@@ -132,7 +131,6 @@
|
|
|
132
131
|
}
|
|
133
132
|
],
|
|
134
133
|
"license": {
|
|
135
|
-
"remix": "include",
|
|
136
134
|
"kind": "cc0",
|
|
137
135
|
"spdx": "CC0-1.0",
|
|
138
136
|
"owner": "Kenney",
|
|
@@ -236,7 +234,6 @@
|
|
|
236
234
|
}
|
|
237
235
|
],
|
|
238
236
|
"license": {
|
|
239
|
-
"remix": "include",
|
|
240
237
|
"kind": "cc0",
|
|
241
238
|
"spdx": "CC0-1.0",
|
|
242
239
|
"owner": "Kenney",
|
|
@@ -341,7 +338,6 @@
|
|
|
341
338
|
}
|
|
342
339
|
],
|
|
343
340
|
"license": {
|
|
344
|
-
"remix": "include",
|
|
345
341
|
"kind": "cc0",
|
|
346
342
|
"spdx": "CC0-1.0",
|
|
347
343
|
"owner": "Kenney",
|
|
@@ -446,7 +442,6 @@
|
|
|
446
442
|
}
|
|
447
443
|
],
|
|
448
444
|
"license": {
|
|
449
|
-
"remix": "include",
|
|
450
445
|
"kind": "cc0",
|
|
451
446
|
"spdx": "CC0-1.0",
|
|
452
447
|
"owner": "Kenney",
|
|
@@ -552,7 +547,6 @@
|
|
|
552
547
|
}
|
|
553
548
|
],
|
|
554
549
|
"license": {
|
|
555
|
-
"remix": "include",
|
|
556
550
|
"kind": "cc0",
|
|
557
551
|
"spdx": "CC0-1.0",
|
|
558
552
|
"owner": "Kenney",
|
|
@@ -658,7 +652,6 @@
|
|
|
658
652
|
}
|
|
659
653
|
],
|
|
660
654
|
"license": {
|
|
661
|
-
"remix": "include",
|
|
662
655
|
"kind": "cc0",
|
|
663
656
|
"spdx": "CC0-1.0",
|
|
664
657
|
"owner": "Kenney",
|
|
@@ -765,7 +758,6 @@
|
|
|
765
758
|
}
|
|
766
759
|
],
|
|
767
760
|
"license": {
|
|
768
|
-
"remix": "include",
|
|
769
761
|
"kind": "cc0",
|
|
770
762
|
"spdx": "CC0-1.0",
|
|
771
763
|
"owner": "Kenney",
|
|
@@ -870,7 +862,6 @@
|
|
|
870
862
|
}
|
|
871
863
|
],
|
|
872
864
|
"license": {
|
|
873
|
-
"remix": "include",
|
|
874
865
|
"kind": "cc0",
|
|
875
866
|
"spdx": "CC0-1.0",
|
|
876
867
|
"owner": "Kenney",
|
|
@@ -962,7 +953,6 @@
|
|
|
962
953
|
}
|
|
963
954
|
],
|
|
964
955
|
"license": {
|
|
965
|
-
"remix": "include",
|
|
966
956
|
"kind": "cc0",
|
|
967
957
|
"spdx": "CC0-1.0",
|
|
968
958
|
"owner": "Kenney",
|
|
@@ -1054,7 +1044,6 @@
|
|
|
1054
1044
|
}
|
|
1055
1045
|
],
|
|
1056
1046
|
"license": {
|
|
1057
|
-
"remix": "include",
|
|
1058
1047
|
"kind": "cc0",
|
|
1059
1048
|
"spdx": "CC0-1.0",
|
|
1060
1049
|
"owner": "Kenney",
|
|
@@ -1144,7 +1133,6 @@
|
|
|
1144
1133
|
}
|
|
1145
1134
|
],
|
|
1146
1135
|
"license": {
|
|
1147
|
-
"remix": "include",
|
|
1148
1136
|
"kind": "cc0",
|
|
1149
1137
|
"spdx": "CC0-1.0",
|
|
1150
1138
|
"owner": "Kenney",
|
|
@@ -1236,7 +1224,6 @@
|
|
|
1236
1224
|
}
|
|
1237
1225
|
],
|
|
1238
1226
|
"license": {
|
|
1239
|
-
"remix": "include",
|
|
1240
1227
|
"kind": "cc0",
|
|
1241
1228
|
"spdx": "CC0-1.0",
|
|
1242
1229
|
"owner": "Kenney",
|
|
@@ -1328,7 +1315,6 @@
|
|
|
1328
1315
|
}
|
|
1329
1316
|
],
|
|
1330
1317
|
"license": {
|
|
1331
|
-
"remix": "include",
|
|
1332
1318
|
"kind": "cc0",
|
|
1333
1319
|
"spdx": "CC0-1.0",
|
|
1334
1320
|
"owner": "Kenney",
|
|
@@ -1420,7 +1406,6 @@
|
|
|
1420
1406
|
}
|
|
1421
1407
|
],
|
|
1422
1408
|
"license": {
|
|
1423
|
-
"remix": "include",
|
|
1424
1409
|
"kind": "cc0",
|
|
1425
1410
|
"spdx": "CC0-1.0",
|
|
1426
1411
|
"owner": "Kenney",
|
|
@@ -1512,7 +1497,6 @@
|
|
|
1512
1497
|
}
|
|
1513
1498
|
],
|
|
1514
1499
|
"license": {
|
|
1515
|
-
"remix": "include",
|
|
1516
1500
|
"kind": "cc0",
|
|
1517
1501
|
"spdx": "CC0-1.0",
|
|
1518
1502
|
"owner": "Kenney",
|
|
@@ -1605,7 +1589,6 @@
|
|
|
1605
1589
|
}
|
|
1606
1590
|
],
|
|
1607
1591
|
"license": {
|
|
1608
|
-
"remix": "include",
|
|
1609
1592
|
"kind": "cc0",
|
|
1610
1593
|
"spdx": "CC0-1.0",
|
|
1611
1594
|
"owner": "Kenney",
|
|
@@ -1696,7 +1679,6 @@
|
|
|
1696
1679
|
}
|
|
1697
1680
|
],
|
|
1698
1681
|
"license": {
|
|
1699
|
-
"remix": "include",
|
|
1700
1682
|
"kind": "cc0",
|
|
1701
1683
|
"spdx": "CC0-1.0",
|
|
1702
1684
|
"owner": "Kenney",
|
|
@@ -1788,7 +1770,6 @@
|
|
|
1788
1770
|
}
|
|
1789
1771
|
],
|
|
1790
1772
|
"license": {
|
|
1791
|
-
"remix": "include",
|
|
1792
1773
|
"kind": "cc0",
|
|
1793
1774
|
"spdx": "CC0-1.0",
|
|
1794
1775
|
"owner": "Kenney",
|
|
@@ -1880,7 +1861,6 @@
|
|
|
1880
1861
|
}
|
|
1881
1862
|
],
|
|
1882
1863
|
"license": {
|
|
1883
|
-
"remix": "include",
|
|
1884
1864
|
"kind": "cc0",
|
|
1885
1865
|
"spdx": "CC0-1.0",
|
|
1886
1866
|
"owner": "Kenney",
|
|
@@ -1973,7 +1953,6 @@
|
|
|
1973
1953
|
}
|
|
1974
1954
|
],
|
|
1975
1955
|
"license": {
|
|
1976
|
-
"remix": "include",
|
|
1977
1956
|
"kind": "cc0",
|
|
1978
1957
|
"spdx": "CC0-1.0",
|
|
1979
1958
|
"owner": "Kenney",
|
|
@@ -2066,7 +2045,6 @@
|
|
|
2066
2045
|
}
|
|
2067
2046
|
],
|
|
2068
2047
|
"license": {
|
|
2069
|
-
"remix": "include",
|
|
2070
2048
|
"kind": "cc0",
|
|
2071
2049
|
"spdx": "CC0-1.0",
|
|
2072
2050
|
"owner": "Kenney",
|
|
@@ -2158,7 +2136,6 @@
|
|
|
2158
2136
|
}
|
|
2159
2137
|
],
|
|
2160
2138
|
"license": {
|
|
2161
|
-
"remix": "include",
|
|
2162
2139
|
"kind": "cc0",
|
|
2163
2140
|
"spdx": "CC0-1.0",
|
|
2164
2141
|
"owner": "Kenney",
|
|
@@ -2250,7 +2227,6 @@
|
|
|
2250
2227
|
}
|
|
2251
2228
|
],
|
|
2252
2229
|
"license": {
|
|
2253
|
-
"remix": "include",
|
|
2254
2230
|
"kind": "cc0",
|
|
2255
2231
|
"spdx": "CC0-1.0",
|
|
2256
2232
|
"owner": "Kenney",
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<title>Gem Rush 3D</title>
|
|
7
7
|
<link rel="icon" href="data:,">
|
|
8
8
|
<style>
|
|
9
|
-
html, body { margin: 0; height: 100%; background: #cfe8f5; overflow: hidden; overscroll-behavior: none; -webkit-user-select: none; user-select: none; }
|
|
9
|
+
html, body { margin: 0; height: 100%; background: #cfe8f5; overflow: hidden; overscroll-behavior: none; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
|
|
10
10
|
canvas { position: fixed; inset: 0; width: 100%; height: 100%; display: block; touch-action: none; }
|
|
11
11
|
/* The HUD is drawn over the world and never takes a touch: every touch reaches the world's canvas. */
|
|
12
12
|
#hud { pointer-events: none; }
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
43
43
|
// The one model loader every studio game uses: checks each file, decodes meshopt and WebP, copies for placing.
|
|
44
44
|
import { createModels, instancedCopies, placeCopy, repaint, stylize, type Copies } from '@homie-rocks/studio/assets';
|
|
45
|
-
import { createNetplay, Roster, q, lerp, capMove, PALETTE, AI_MARK, type RoleChange, type RoundInfo, type RoundResult, type Skill, type Slot, type Snapshot } from '@homie-rocks/studio/netplay';
|
|
45
|
+
import { createNetplay, guardGestures, Roster, q, lerp, capMove, PALETTE, AI_MARK, type RoleChange, type RoundInfo, type RoundResult, type Skill, type Slot, type Snapshot } from '@homie-rocks/studio/netplay';
|
|
46
46
|
// The port toolkit: its probe (what `homie-studio port check` and `perf` read, and sandbox + audio shims), and name
|
|
47
47
|
// labels that never pile up (port/view.ts).
|
|
48
48
|
import { createBubbles, createLabels, exposePort, paintBubbles, type BubbleIn, type BubbleOut, type LabelIn, type LabelOut } from '@homie-rocks/studio/port';
|
|
@@ -110,7 +110,7 @@ interface Body extends Knock { slot: number; seat: number | null; name: string;
|
|
|
110
110
|
interface Gem { id: number; x: number; y: number }
|
|
111
111
|
/** Slow state, on the keyed state channel (net.state('zone', ...)), not in the 20 Hz snapshot. */
|
|
112
112
|
interface Zone { n: number; x: number; y: number; r: number; until: number }
|
|
113
|
-
interface Ckpt { round: RoundInfo; bodies: Body[]; gems: Gem[]; roster: Slot[]; tick: number; gemSeq: number }
|
|
113
|
+
interface Ckpt { round: RoundInfo; bodies: Body[]; gems: Gem[]; roster: Slot[]; tick: number; gemSeq: number; occ?: [number, number][] }
|
|
114
114
|
|
|
115
115
|
/* --------------------------------------------------------------- the net */
|
|
116
116
|
/**
|
|
@@ -121,6 +121,9 @@ interface Ckpt { round: RoundInfo; bodies: Body[]; gems: Gem[]; roster: Slot[];
|
|
|
121
121
|
const MOVEMENT: 'owner' | 'host' = (() => { try { return new URLSearchParams(location.search).get('movement') === 'host' ? 'host' : 'owner'; } catch { return 'owner'; } })();
|
|
122
122
|
// caps: its bots read the dial ('skill'), and its Roster takes an AI's slot for it ('agents': the join passes p.agent).
|
|
123
123
|
const net = createNetplay<Snap, Avatar, Ckpt>({ game: 'gem-rush-3d', maxPlayers: MAX_SLOTS, movement: MOVEMENT, snapshotHz: 20, inputHz: 20, checkpointMs: 1000, caps: ['skill', 'agents'], checkpoint: () => checkpoint() });
|
|
124
|
+
// A touch game guards its own page (NETPLAY.md section 24): a long press on the canvas never selects text or raises
|
|
125
|
+
// the copy/paste callout on a phone, and a touch on it never pans or zooms the page.
|
|
126
|
+
guardGestures({ touch: 'canvas' });
|
|
124
127
|
/** The Roster keeps the server's AI seats (revision 6): the policy is read whenever it fills. */
|
|
125
128
|
const policy = () => net.policy;
|
|
126
129
|
|
|
@@ -240,7 +243,9 @@ function becomeHost(e: RoleChange<Snap, Ckpt>): void {
|
|
|
240
243
|
startRound((e.round?.n ?? 0) + 1);
|
|
241
244
|
}
|
|
242
245
|
// Whoever is connected now is who plays: seats that left during the gap become bots.
|
|
243
|
-
const peers = net.offline ? [{ seat: 0, name: 'You' }] : [...net.peers.values()].filter((p) => p.seat !== null).map((p) => ({ seat: p.seat, name: p.name, agent: p.agent ?? null }));
|
|
246
|
+
const peers = net.offline ? [{ seat: 0, name: 'You' }] : [...net.peers.values()].filter((p) => p.seat !== null).map((p) => ({ seat: p.seat, name: p.name, agent: p.agent ?? null, occ: p.occ ?? null }));
|
|
247
|
+
// `occ` (NETPLAY.md section 25): which stay in its seat each peer's is, so a seat number that changed hands while
|
|
248
|
+
// nobody was hosting is claimed afresh instead of inheriting the last player's body in silence.
|
|
244
249
|
const { claimed } = roster.reconcile(peers);
|
|
245
250
|
syncBodiesFromRoster();
|
|
246
251
|
for (const s of claimed) { const b = bodies.get(s.slot); if (b && b.seat !== mySeat()) hostMoved(b); }
|
|
@@ -257,7 +262,7 @@ function becomeHost(e: RoleChange<Snap, Ckpt>): void {
|
|
|
257
262
|
function restore(e: RoleChange<Snap, Ckpt>): void {
|
|
258
263
|
const ck = e.ckpt?.d ?? null;
|
|
259
264
|
if (ck) {
|
|
260
|
-
roster = Roster.from(ck.roster, { min: MIN_SLOTS, max: MAX_SLOTS, botName, policy });
|
|
265
|
+
roster = Roster.from(ck.roster, { min: MIN_SLOTS, max: MAX_SLOTS, botName, policy }, ck.occ ?? null);
|
|
261
266
|
bodies = new Map(ck.bodies.map((b) => [b.slot, { ...b }]));
|
|
262
267
|
gems = ck.gems.map((g) => ({ ...g }));
|
|
263
268
|
gemSeq = ck.gemSeq;
|
|
@@ -298,6 +303,8 @@ function checkpoint(): Ckpt {
|
|
|
298
303
|
bodies: [...bodies.values()].map((b) => ({ ...b })),
|
|
299
304
|
gems: gems.map((g) => ({ ...g })),
|
|
300
305
|
roster: roster.toJSON(),
|
|
306
|
+
// Whose stay each body is, beside the roster: how the next host tells a player who came back from a new one.
|
|
307
|
+
occ: roster.occupants(),
|
|
301
308
|
tick,
|
|
302
309
|
gemSeq,
|
|
303
310
|
};
|
|
@@ -310,7 +317,7 @@ net.on('role', (e) => {
|
|
|
310
317
|
net.on('join', (p) => {
|
|
311
318
|
if (!hosting || p.seat === null) return;
|
|
312
319
|
// An AI takes a seat kept for AI, a person never does (revision 6: the Roster needs p.agent for that).
|
|
313
|
-
const c = roster.claim(p.seat, p.name, p.agent ? { role: p.agent.role, hands: p.agent.hands } : null);
|
|
320
|
+
const c = roster.claim(p.seat, p.name, p.agent ? { role: p.agent.role, hands: p.agent.hands } : null, p.occ ?? null);
|
|
314
321
|
if (!c) return; // full: they watch
|
|
315
322
|
syncBodiesFromRoster();
|
|
316
323
|
const b = bodies.get(c.slot.slot);
|
|
@@ -2095,6 +2102,9 @@ exposePort(net, {
|
|
|
2095
2102
|
self: () => (me.has && mySeat() !== null ? { x: me.x, y: me.y } : null),
|
|
2096
2103
|
size: R_AV,
|
|
2097
2104
|
score: () => { const seat = mySeat(); const b = hosting ? [...bodies.values()].find((x) => x.seat === seat) : [...drawn.values()].find((x) => x.seat === seat); return b ? b.score : null; },
|
|
2105
|
+
// The renderer's own counters, by the names `homie-studio perf` and the playtest read on the port probe (measured
|
|
2106
|
+
// scene cost while playing; port/probe.ts PortExtra). Nobody dies in this game, so there is no `alive` to say.
|
|
2107
|
+
extra: { drawCalls: () => renderer.info.render.calls, triangles: () => renderer.info.render.triangles },
|
|
2098
2108
|
});
|
|
2099
2109
|
|
|
2100
2110
|
void net.ready.then(() => {
|
|
@@ -15,7 +15,6 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
15
15
|
- **What:** character (Players/Barbarian): `public/models/barbarian.glb`
|
|
16
16
|
- **From:** from the Homie starter library: KayKit Adventurers, barbarian (https://github.com/KayKit-Game-Assets/KayKit-Character-Pack-Adventures-1.0); library
|
|
17
17
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
18
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
19
18
|
- **Notes:** From the Homie starter library (kaykit-adventurers); the pack's own licence text: licenses/kaykit-adventurers.txt. `game new` fetches the file, makes the character phone-sized and bakes its clips; the repository holds no models.
|
|
20
19
|
|
|
21
20
|
### barrel
|
|
@@ -23,7 +22,6 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
23
22
|
- **What:** prop (Places/Barrel): `public/models/barrel.glb`
|
|
24
23
|
- **From:** from the Homie starter library: KayKit Medieval Hexagon, barrel (https://github.com/KayKit-Game-Assets/KayKit-Medieval-Hexagon-Pack-1.0); library
|
|
25
24
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
26
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
27
25
|
- **Notes:** From the Homie starter library (kaykit-medieval-hexagon); the pack's own licence text: licenses/kaykit-medieval-hexagon.txt. `game new` fetches the file; the repository holds no models.
|
|
28
26
|
|
|
29
27
|
### chest
|
|
@@ -31,7 +29,6 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
31
29
|
- **What:** prop (Places/Treasure Chest): `public/models/chest.glb`
|
|
32
30
|
- **From:** from the Homie starter library: KayKit Dungeon Remastered, chest-gold (https://github.com/KayKit-Game-Assets/KayKit-Dungeon-Remastered-1.0); library
|
|
33
31
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
34
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
35
32
|
- **Notes:** From the Homie starter library (kaykit-dungeon); the pack's own licence text: licenses/kaykit-dungeon.txt. `game new` fetches the file; the repository holds no models.
|
|
36
33
|
|
|
37
34
|
### coin
|
|
@@ -39,7 +36,6 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
39
36
|
- **What:** prop (Items/Coin): `public/models/coin.glb`
|
|
40
37
|
- **From:** from the Homie starter library: KayKit Dungeon Remastered, coin (https://github.com/KayKit-Game-Assets/KayKit-Dungeon-Remastered-1.0); library
|
|
41
38
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
42
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
43
39
|
- **Notes:** From the Homie starter library (kaykit-dungeon); the pack's own licence text: licenses/kaykit-dungeon.txt. `game new` fetches the file; the repository holds no models.
|
|
44
40
|
|
|
45
41
|
### crate
|
|
@@ -47,7 +43,6 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
47
43
|
- **What:** prop (Places/Crate): `public/models/crate.glb`
|
|
48
44
|
- **From:** from the Homie starter library: KayKit Medieval Hexagon, crate-a-big (https://github.com/KayKit-Game-Assets/KayKit-Medieval-Hexagon-Pack-1.0); library
|
|
49
45
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
50
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
51
46
|
- **Notes:** From the Homie starter library (kaykit-medieval-hexagon); the pack's own licence text: licenses/kaykit-medieval-hexagon.txt. `game new` fetches the file; the repository holds no models.
|
|
52
47
|
|
|
53
48
|
### grove
|
|
@@ -55,7 +50,6 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
55
50
|
- **What:** prop (Places/Grove): `public/models/grove.glb`
|
|
56
51
|
- **From:** from the Homie starter library: KayKit Medieval Hexagon, trees-a-medium (https://github.com/KayKit-Game-Assets/KayKit-Medieval-Hexagon-Pack-1.0); library
|
|
57
52
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
58
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
59
53
|
- **Notes:** From the Homie starter library (kaykit-medieval-hexagon); the pack's own licence text: licenses/kaykit-medieval-hexagon.txt. `game new` fetches the file; the repository holds no models.
|
|
60
54
|
|
|
61
55
|
### knight
|
|
@@ -63,7 +57,6 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
63
57
|
- **What:** character (Players/Knight): `public/models/knight.glb`
|
|
64
58
|
- **From:** from the Homie starter library: KayKit Adventurers, knight (https://github.com/KayKit-Game-Assets/KayKit-Character-Pack-Adventures-1.0); library
|
|
65
59
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
66
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
67
60
|
- **Notes:** From the Homie starter library (kaykit-adventurers); the pack's own licence text: licenses/kaykit-adventurers.txt. `game new` fetches the file, makes the character phone-sized and bakes its clips; the repository holds no models.
|
|
68
61
|
|
|
69
62
|
### mage
|
|
@@ -71,7 +64,6 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
71
64
|
- **What:** character (Players/Mage): `public/models/mage.glb`
|
|
72
65
|
- **From:** from the Homie starter library: KayKit Adventurers, mage (https://github.com/KayKit-Game-Assets/KayKit-Character-Pack-Adventures-1.0); library
|
|
73
66
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
74
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
75
67
|
- **Notes:** From the Homie starter library (kaykit-adventurers); the pack's own licence text: licenses/kaykit-adventurers.txt. `game new` fetches the file, makes the character phone-sized and bakes its clips; the repository holds no models.
|
|
76
68
|
|
|
77
69
|
### pine
|
|
@@ -79,7 +71,6 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
79
71
|
- **What:** prop (Places/Pine): `public/models/pine.glb`
|
|
80
72
|
- **From:** from the Homie starter library: KayKit Medieval Hexagon, tree-single-a (https://github.com/KayKit-Game-Assets/KayKit-Medieval-Hexagon-Pack-1.0); library
|
|
81
73
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
82
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
83
74
|
- **Notes:** From the Homie starter library (kaykit-medieval-hexagon); the pack's own licence text: licenses/kaykit-medieval-hexagon.txt. `game new` fetches the file; the repository holds no models.
|
|
84
75
|
|
|
85
76
|
### pine-round
|
|
@@ -87,7 +78,6 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
87
78
|
- **What:** prop (Places/Round Pine): `public/models/pine-round.glb`
|
|
88
79
|
- **From:** from the Homie starter library: KayKit Medieval Hexagon, tree-single-b (https://github.com/KayKit-Game-Assets/KayKit-Medieval-Hexagon-Pack-1.0); library
|
|
89
80
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
90
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
91
81
|
- **Notes:** From the Homie starter library (kaykit-medieval-hexagon); the pack's own licence text: licenses/kaykit-medieval-hexagon.txt. `game new` fetches the file; the repository holds no models.
|
|
92
82
|
|
|
93
83
|
### rock
|
|
@@ -95,7 +85,6 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
95
85
|
- **What:** prop (Places/Rock): `public/models/rock.glb`
|
|
96
86
|
- **From:** from the Homie starter library: KayKit Medieval Hexagon, rock-single-e (https://github.com/KayKit-Game-Assets/KayKit-Medieval-Hexagon-Pack-1.0); library
|
|
97
87
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
98
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
99
88
|
- **Notes:** From the Homie starter library (kaykit-medieval-hexagon); the pack's own licence text: licenses/kaykit-medieval-hexagon.txt. `game new` fetches the file; the repository holds no models.
|
|
100
89
|
|
|
101
90
|
### rocks
|
|
@@ -103,7 +92,6 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
103
92
|
- **What:** prop (Places/Rocks): `public/models/rocks.glb`
|
|
104
93
|
- **From:** from the Homie starter library: KayKit Medieval Hexagon, rock-single-c (https://github.com/KayKit-Game-Assets/KayKit-Medieval-Hexagon-Pack-1.0); library
|
|
105
94
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
106
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
107
95
|
- **Notes:** From the Homie starter library (kaykit-medieval-hexagon); the pack's own licence text: licenses/kaykit-medieval-hexagon.txt. `game new` fetches the file; the repository holds no models.
|
|
108
96
|
|
|
109
97
|
### rogue
|
|
@@ -111,7 +99,6 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
111
99
|
- **What:** character (Players/Rogue): `public/models/rogue.glb`
|
|
112
100
|
- **From:** from the Homie starter library: KayKit Adventurers, rogue (https://github.com/KayKit-Game-Assets/KayKit-Character-Pack-Adventures-1.0); library
|
|
113
101
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
114
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
115
102
|
- **Notes:** From the Homie starter library (kaykit-adventurers); the pack's own licence text: licenses/kaykit-adventurers.txt. `game new` fetches the file, makes the character phone-sized and bakes its clips; the repository holds no models.
|
|
116
103
|
|
|
117
104
|
### rogue-hooded
|
|
@@ -119,7 +106,6 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
119
106
|
- **What:** character (Players/Hooded Rogue): `public/models/rogue-hooded.glb`
|
|
120
107
|
- **From:** from the Homie starter library: KayKit Adventurers, rogue-hooded (https://github.com/KayKit-Game-Assets/KayKit-Character-Pack-Adventures-1.0); library
|
|
121
108
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
122
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
123
109
|
- **Notes:** From the Homie starter library (kaykit-adventurers); the pack's own licence text: licenses/kaykit-adventurers.txt. `game new` fetches the file, makes the character phone-sized and bakes its clips; the repository holds no models.
|
|
124
110
|
|
|
125
111
|
### skeleton-mage
|
|
@@ -127,7 +113,6 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
127
113
|
- **What:** creature (Creatures/Skeleton Mage): `public/models/skeleton-mage.glb`
|
|
128
114
|
- **From:** from the Homie starter library: KayKit Skeletons, skeleton-mage (https://github.com/KayKit-Game-Assets/KayKit-Character-Pack-Skeletons-1.0); library
|
|
129
115
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
130
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
131
116
|
- **Notes:** From the Homie starter library (kaykit-skeletons); the pack's own licence text: licenses/kaykit-skeletons.txt. `game new` fetches the file, makes the character phone-sized and bakes its clips; the repository holds no models.
|
|
132
117
|
|
|
133
118
|
### skeleton-minion
|
|
@@ -135,7 +120,6 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
135
120
|
- **What:** creature (Creatures/Skeleton Minion): `public/models/skeleton-minion.glb`
|
|
136
121
|
- **From:** from the Homie starter library: KayKit Skeletons, skeleton-minion (https://github.com/KayKit-Game-Assets/KayKit-Character-Pack-Skeletons-1.0); library
|
|
137
122
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
138
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
139
123
|
- **Notes:** From the Homie starter library (kaykit-skeletons); the pack's own licence text: licenses/kaykit-skeletons.txt. `game new` fetches the file, makes the character phone-sized and bakes its clips; the repository holds no models.
|
|
140
124
|
|
|
141
125
|
### skeleton-rogue
|
|
@@ -143,7 +127,6 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
143
127
|
- **What:** creature (Creatures/Skeleton Rogue): `public/models/skeleton-rogue.glb`
|
|
144
128
|
- **From:** from the Homie starter library: KayKit Skeletons, skeleton-rogue (https://github.com/KayKit-Game-Assets/KayKit-Character-Pack-Skeletons-1.0); library
|
|
145
129
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
146
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
147
130
|
- **Notes:** From the Homie starter library (kaykit-skeletons); the pack's own licence text: licenses/kaykit-skeletons.txt. `game new` fetches the file, makes the character phone-sized and bakes its clips; the repository holds no models.
|
|
148
131
|
|
|
149
132
|
### skeleton-warrior
|
|
@@ -151,7 +134,6 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
151
134
|
- **What:** creature (Creatures/Skeleton Warrior): `public/models/skeleton-warrior.glb`
|
|
152
135
|
- **From:** from the Homie starter library: KayKit Skeletons, skeleton-warrior (https://github.com/KayKit-Game-Assets/KayKit-Character-Pack-Skeletons-1.0); library
|
|
153
136
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
154
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
155
137
|
- **Notes:** From the Homie starter library (kaykit-skeletons); the pack's own licence text: licenses/kaykit-skeletons.txt. `game new` fetches the file, makes the character phone-sized and bakes its clips; the repository holds no models.
|
|
156
138
|
|
|
157
139
|
### torch
|
|
@@ -159,5 +141,4 @@ Read on the dates shown. Terms change: read the live page again before publishin
|
|
|
159
141
|
- **What:** prop (Places/Torch): `public/models/torch.glb`
|
|
160
142
|
- **From:** from the Homie starter library: KayKit Dungeon Remastered, torch-lit (https://github.com/KayKit-Game-Assets/KayKit-Dungeon-Remastered-1.0); library
|
|
161
143
|
- **Licence:** CC0 1.0 (public domain) (https://creativecommons.org/publicdomain/zero/1.0/); owner: KayKit
|
|
162
|
-
- **A remix:** a remix gets this file (fetched from this studio's site, checked by SHA-256)
|
|
163
144
|
- **Notes:** From the Homie starter library (kaykit-dungeon); the pack's own licence text: licenses/kaykit-dungeon.txt. `game new` fetches the file; the repository holds no models.
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
}
|
|
30
30
|
],
|
|
31
31
|
"license": {
|
|
32
|
-
"remix": "include",
|
|
33
32
|
"kind": "cc0",
|
|
34
33
|
"spdx": "CC0-1.0",
|
|
35
34
|
"owner": "KayKit",
|
|
@@ -118,7 +117,6 @@
|
|
|
118
117
|
}
|
|
119
118
|
],
|
|
120
119
|
"license": {
|
|
121
|
-
"remix": "include",
|
|
122
120
|
"kind": "cc0",
|
|
123
121
|
"spdx": "CC0-1.0",
|
|
124
122
|
"owner": "KayKit",
|
|
@@ -186,7 +184,6 @@
|
|
|
186
184
|
}
|
|
187
185
|
],
|
|
188
186
|
"license": {
|
|
189
|
-
"remix": "include",
|
|
190
187
|
"kind": "cc0",
|
|
191
188
|
"spdx": "CC0-1.0",
|
|
192
189
|
"owner": "KayKit",
|
|
@@ -254,7 +251,6 @@
|
|
|
254
251
|
}
|
|
255
252
|
],
|
|
256
253
|
"license": {
|
|
257
|
-
"remix": "include",
|
|
258
254
|
"kind": "cc0",
|
|
259
255
|
"spdx": "CC0-1.0",
|
|
260
256
|
"owner": "KayKit",
|
|
@@ -322,7 +318,6 @@
|
|
|
322
318
|
}
|
|
323
319
|
],
|
|
324
320
|
"license": {
|
|
325
|
-
"remix": "include",
|
|
326
321
|
"kind": "cc0",
|
|
327
322
|
"spdx": "CC0-1.0",
|
|
328
323
|
"owner": "KayKit",
|
|
@@ -390,7 +385,6 @@
|
|
|
390
385
|
}
|
|
391
386
|
],
|
|
392
387
|
"license": {
|
|
393
|
-
"remix": "include",
|
|
394
388
|
"kind": "cc0",
|
|
395
389
|
"spdx": "CC0-1.0",
|
|
396
390
|
"owner": "KayKit",
|
|
@@ -458,7 +452,6 @@
|
|
|
458
452
|
}
|
|
459
453
|
],
|
|
460
454
|
"license": {
|
|
461
|
-
"remix": "include",
|
|
462
455
|
"kind": "cc0",
|
|
463
456
|
"spdx": "CC0-1.0",
|
|
464
457
|
"owner": "KayKit",
|
|
@@ -547,7 +540,6 @@
|
|
|
547
540
|
}
|
|
548
541
|
],
|
|
549
542
|
"license": {
|
|
550
|
-
"remix": "include",
|
|
551
543
|
"kind": "cc0",
|
|
552
544
|
"spdx": "CC0-1.0",
|
|
553
545
|
"owner": "KayKit",
|
|
@@ -636,7 +628,6 @@
|
|
|
636
628
|
}
|
|
637
629
|
],
|
|
638
630
|
"license": {
|
|
639
|
-
"remix": "include",
|
|
640
631
|
"kind": "cc0",
|
|
641
632
|
"spdx": "CC0-1.0",
|
|
642
633
|
"owner": "KayKit",
|
|
@@ -704,7 +695,6 @@
|
|
|
704
695
|
}
|
|
705
696
|
],
|
|
706
697
|
"license": {
|
|
707
|
-
"remix": "include",
|
|
708
698
|
"kind": "cc0",
|
|
709
699
|
"spdx": "CC0-1.0",
|
|
710
700
|
"owner": "KayKit",
|
|
@@ -772,7 +762,6 @@
|
|
|
772
762
|
}
|
|
773
763
|
],
|
|
774
764
|
"license": {
|
|
775
|
-
"remix": "include",
|
|
776
765
|
"kind": "cc0",
|
|
777
766
|
"spdx": "CC0-1.0",
|
|
778
767
|
"owner": "KayKit",
|
|
@@ -840,7 +829,6 @@
|
|
|
840
829
|
}
|
|
841
830
|
],
|
|
842
831
|
"license": {
|
|
843
|
-
"remix": "include",
|
|
844
832
|
"kind": "cc0",
|
|
845
833
|
"spdx": "CC0-1.0",
|
|
846
834
|
"owner": "KayKit",
|
|
@@ -908,7 +896,6 @@
|
|
|
908
896
|
}
|
|
909
897
|
],
|
|
910
898
|
"license": {
|
|
911
|
-
"remix": "include",
|
|
912
899
|
"kind": "cc0",
|
|
913
900
|
"spdx": "CC0-1.0",
|
|
914
901
|
"owner": "KayKit",
|
|
@@ -996,7 +983,6 @@
|
|
|
996
983
|
}
|
|
997
984
|
],
|
|
998
985
|
"license": {
|
|
999
|
-
"remix": "include",
|
|
1000
986
|
"kind": "cc0",
|
|
1001
987
|
"spdx": "CC0-1.0",
|
|
1002
988
|
"owner": "KayKit",
|
|
@@ -1084,7 +1070,6 @@
|
|
|
1084
1070
|
}
|
|
1085
1071
|
],
|
|
1086
1072
|
"license": {
|
|
1087
|
-
"remix": "include",
|
|
1088
1073
|
"kind": "cc0",
|
|
1089
1074
|
"spdx": "CC0-1.0",
|
|
1090
1075
|
"owner": "KayKit",
|
|
@@ -1170,7 +1155,6 @@
|
|
|
1170
1155
|
}
|
|
1171
1156
|
],
|
|
1172
1157
|
"license": {
|
|
1173
|
-
"remix": "include",
|
|
1174
1158
|
"kind": "cc0",
|
|
1175
1159
|
"spdx": "CC0-1.0",
|
|
1176
1160
|
"owner": "KayKit",
|
|
@@ -1254,7 +1238,6 @@
|
|
|
1254
1238
|
}
|
|
1255
1239
|
],
|
|
1256
1240
|
"license": {
|
|
1257
|
-
"remix": "include",
|
|
1258
1241
|
"kind": "cc0",
|
|
1259
1242
|
"spdx": "CC0-1.0",
|
|
1260
1243
|
"owner": "KayKit",
|
|
@@ -1341,7 +1324,6 @@
|
|
|
1341
1324
|
}
|
|
1342
1325
|
],
|
|
1343
1326
|
"license": {
|
|
1344
|
-
"remix": "include",
|
|
1345
1327
|
"kind": "cc0",
|
|
1346
1328
|
"spdx": "CC0-1.0",
|
|
1347
1329
|
"owner": "KayKit",
|
|
@@ -1427,7 +1409,6 @@
|
|
|
1427
1409
|
}
|
|
1428
1410
|
],
|
|
1429
1411
|
"license": {
|
|
1430
|
-
"remix": "include",
|
|
1431
1412
|
"kind": "cc0",
|
|
1432
1413
|
"spdx": "CC0-1.0",
|
|
1433
1414
|
"owner": "KayKit",
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<title>Hero Rush 3D</title>
|
|
7
7
|
<link rel="icon" href="data:,">
|
|
8
8
|
<style>
|
|
9
|
-
html, body { margin: 0; height: 100%; background: #cfe8f5; overflow: hidden; overscroll-behavior: none; -webkit-user-select: none; user-select: none; }
|
|
9
|
+
html, body { margin: 0; height: 100%; background: #cfe8f5; overflow: hidden; overscroll-behavior: none; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
|
|
10
10
|
canvas { position: fixed; inset: 0; width: 100%; height: 100%; display: block; touch-action: none; }
|
|
11
11
|
/* The HUD is drawn over the world and never takes a touch: every touch reaches the world's canvas. */
|
|
12
12
|
#hud { pointer-events: none; }
|
|
@@ -70,7 +70,7 @@ import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js
|
|
|
70
70
|
import { createModels, instancedCopies, placeCopy, repaint, stylize, type Copies } from '@homie-rocks/studio/assets';
|
|
71
71
|
// Characters that move well: clips from the skeleton's clip library, blended, layered and tuned (Game Lab tunables).
|
|
72
72
|
import { crowd, loadCharacter, type Character } from '@homie-rocks/studio/animate';
|
|
73
|
-
import { createNetplay, Roster, q, lerp, capMove, skillPreset, PALETTE, AI_MARK, type RoleChange, type RoundInfo, type RoundResult, type Skill, type Slot, type Snapshot } from '@homie-rocks/studio/netplay';
|
|
73
|
+
import { createNetplay, guardGestures, Roster, q, lerp, capMove, skillPreset, PALETTE, AI_MARK, type RoleChange, type RoundInfo, type RoundResult, type Skill, type Slot, type Snapshot } from '@homie-rocks/studio/netplay';
|
|
74
74
|
// The port toolkit: its probe (what `homie-studio port check` and `perf` read, and sandbox + audio shims), and name
|
|
75
75
|
// labels that never pile up (port/view.ts).
|
|
76
76
|
import { createBubbles, createLabels, exposePort, paintBubbles, type BubbleIn, type BubbleOut, type LabelIn, type LabelOut } from '@homie-rocks/studio/port';
|
|
@@ -153,7 +153,7 @@ interface Gem { id: number; x: number; y: number }
|
|
|
153
153
|
/** Slow state, on the keyed state channel (net.state('zone', ...)), not in the 20 Hz snapshot. */
|
|
154
154
|
interface Zone { n: number; x: number; y: number; r: number; until: number }
|
|
155
155
|
/** `firstRound`: each person's first whole round here (seat, round), so a promoted host eases the same round. */
|
|
156
|
-
interface Ckpt { round: RoundInfo; bodies: Body[]; gems: Gem[]; roster: Slot[]; tick: number; gemSeq: number; firstRound?: [number, number][] }
|
|
156
|
+
interface Ckpt { round: RoundInfo; bodies: Body[]; gems: Gem[]; roster: Slot[]; tick: number; gemSeq: number; occ?: [number, number][]; firstRound?: [number, number][] }
|
|
157
157
|
|
|
158
158
|
/* --------------------------------------------------------------- the net */
|
|
159
159
|
/**
|
|
@@ -165,6 +165,9 @@ const MOVEMENT: 'owner' | 'host' = (() => { try { return new URLSearchParams(loc
|
|
|
165
165
|
// caps: its bots read the dial ('skill'), and its Roster takes an AI's slot for it ('agents': the join passes p.agent).
|
|
166
166
|
// arrival 'game': the play page's arrival card stays until this game says it is playable (THE ARRIVAL, by the HUD).
|
|
167
167
|
const net = createNetplay<Snap, Avatar, Ckpt>({ game: 'hero-rush-3d', maxPlayers: MAX_SLOTS, movement: MOVEMENT, snapshotHz: 20, inputHz: 20, checkpointMs: 1000, caps: ['skill', 'agents'], arrival: 'game', checkpoint: () => checkpoint() });
|
|
168
|
+
// A touch game guards its own page (NETPLAY.md section 24): a long press on the canvas never selects text or raises
|
|
169
|
+
// the copy/paste callout on a phone, and a touch on it never pans or zooms the page.
|
|
170
|
+
guardGestures({ touch: 'canvas' });
|
|
168
171
|
/** The Roster keeps the server's AI seats (revision 6): the policy is read whenever it fills. */
|
|
169
172
|
const policy = () => net.policy;
|
|
170
173
|
|
|
@@ -290,7 +293,9 @@ function becomeHost(e: RoleChange<Snap, Ckpt>): void {
|
|
|
290
293
|
welcome(seat);
|
|
291
294
|
}
|
|
292
295
|
// Whoever is connected now is who plays: seats that left during the gap become bots.
|
|
293
|
-
const peers = net.offline ? [{ seat: 0, name: 'You' }] : [...net.peers.values()].filter((p) => p.seat !== null).map((p) => ({ seat: p.seat, name: p.name, agent: p.agent ?? null }));
|
|
296
|
+
const peers = net.offline ? [{ seat: 0, name: 'You' }] : [...net.peers.values()].filter((p) => p.seat !== null).map((p) => ({ seat: p.seat, name: p.name, agent: p.agent ?? null, occ: p.occ ?? null }));
|
|
297
|
+
// `occ` (NETPLAY.md section 25): which stay in its seat each peer's is, so a seat number that changed hands while
|
|
298
|
+
// nobody was hosting is claimed afresh instead of inheriting the last player's body in silence.
|
|
294
299
|
const { claimed } = roster.reconcile(peers);
|
|
295
300
|
syncBodiesFromRoster();
|
|
296
301
|
for (const s of claimed) welcome(s.seat);
|
|
@@ -308,7 +313,7 @@ function becomeHost(e: RoleChange<Snap, Ckpt>): void {
|
|
|
308
313
|
function restore(e: RoleChange<Snap, Ckpt>): void {
|
|
309
314
|
const ck = e.ckpt?.d ?? null;
|
|
310
315
|
if (ck) {
|
|
311
|
-
roster = Roster.from(ck.roster, { min: MIN_SLOTS, max: MAX_SLOTS, botName, policy });
|
|
316
|
+
roster = Roster.from(ck.roster, { min: MIN_SLOTS, max: MAX_SLOTS, botName, policy }, ck.occ ?? null);
|
|
312
317
|
bodies = new Map(ck.bodies.map((b) => [b.slot, { ...b }]));
|
|
313
318
|
gems = ck.gems.map((g) => ({ ...g }));
|
|
314
319
|
gemSeq = ck.gemSeq;
|
|
@@ -352,6 +357,8 @@ function checkpoint(): Ckpt {
|
|
|
352
357
|
bodies: [...bodies.values()].map((b) => ({ ...b })),
|
|
353
358
|
gems: gems.map((g) => ({ ...g })),
|
|
354
359
|
roster: roster.toJSON(),
|
|
360
|
+
// Whose stay each body is, beside the roster: how the next host tells a player who came back from a new one.
|
|
361
|
+
occ: roster.occupants(),
|
|
355
362
|
tick,
|
|
356
363
|
gemSeq,
|
|
357
364
|
firstRound: [...firstRound],
|
|
@@ -365,7 +372,7 @@ net.on('role', (e) => {
|
|
|
365
372
|
net.on('join', (p) => {
|
|
366
373
|
if (!hosting || p.seat === null) return;
|
|
367
374
|
// An AI takes a seat kept for AI, a person never does (revision 6: the Roster needs p.agent for that).
|
|
368
|
-
const c = roster.claim(p.seat, p.name, p.agent ? { role: p.agent.role, hands: p.agent.hands } : null);
|
|
375
|
+
const c = roster.claim(p.seat, p.name, p.agent ? { role: p.agent.role, hands: p.agent.hands } : null, p.occ ?? null);
|
|
369
376
|
if (!c) return; // full: they watch
|
|
370
377
|
welcome(p.seat);
|
|
371
378
|
syncBodiesFromRoster();
|
|
@@ -2576,6 +2583,9 @@ exposePort(net, {
|
|
|
2576
2583
|
self: () => (me.has && mySeat() !== null ? { x: me.x, y: me.y } : null),
|
|
2577
2584
|
size: R_AV,
|
|
2578
2585
|
score: () => { const seat = mySeat(); const b = hosting ? [...bodies.values()].find((x) => x.seat === seat) : [...drawn.values()].find((x) => x.seat === seat); return b ? b.score : null; },
|
|
2586
|
+
// The renderer's own counters, by the names `homie-studio perf` and the playtest read on the port probe (measured
|
|
2587
|
+
// scene cost while playing; port/probe.ts PortExtra). Nobody dies in this game, so there is no `alive` to say.
|
|
2588
|
+
extra: { drawCalls: () => renderer.info.render.calls, triangles: () => renderer.info.render.triangles },
|
|
2579
2589
|
});
|
|
2580
2590
|
|
|
2581
2591
|
void net.ready.then(() => {
|