oddlyalive 0.2.0-alpha.2

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.
Files changed (86) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/CONTRIBUTING.md +62 -0
  3. package/LICENSE +21 -0
  4. package/README.md +158 -0
  5. package/SECURITY.md +29 -0
  6. package/assets/photoreal/PROVENANCE.md +19 -0
  7. package/assets/photoreal/baseball.png +0 -0
  8. package/assets/photoreal/basketball.png +0 -0
  9. package/assets/photoreal/crystal.png +0 -0
  10. package/assets/photoreal/kicking-cleat.png +0 -0
  11. package/assets/photoreal/letter-charm-square.png +0 -0
  12. package/assets/photoreal/letter-charm.png +0 -0
  13. package/assets/photoreal/sneaker.png +0 -0
  14. package/assets/photoreal/soccer-ball.png +0 -0
  15. package/bin/oddlyalive.js +283 -0
  16. package/docs/ARCHITECTURE.md +55 -0
  17. package/docs/DEMO-VIDEOS.md +122 -0
  18. package/docs/QUICKSTART.md +115 -0
  19. package/docs/ROADMAP.md +33 -0
  20. package/examples/ball-lab/PROVENANCE.md +4 -0
  21. package/examples/ball-lab/app.js +33 -0
  22. package/examples/ball-lab/index.html +56 -0
  23. package/examples/ball-lab/scene.json +68 -0
  24. package/examples/crystal-mobile/PROVENANCE.md +4 -0
  25. package/examples/crystal-mobile/app.js +26 -0
  26. package/examples/crystal-mobile/index.html +56 -0
  27. package/examples/crystal-mobile/scene.json +69 -0
  28. package/examples/football-kick/PROVENANCE.md +4 -0
  29. package/examples/football-kick/app.js +33 -0
  30. package/examples/football-kick/index.html +56 -0
  31. package/examples/football-kick/scene.json +44 -0
  32. package/examples/gallery.css +379 -0
  33. package/examples/index.html +109 -0
  34. package/examples/shared/example.css +320 -0
  35. package/examples/shared/player.js +85 -0
  36. package/examples/shoe-splash/PROVENANCE.md +4 -0
  37. package/examples/shoe-splash/app.js +26 -0
  38. package/examples/shoe-splash/index.html +56 -0
  39. package/examples/shoe-splash/scene.json +38 -0
  40. package/examples/string-touch/PROVENANCE.md +11 -0
  41. package/examples/string-touch/app.js +79 -0
  42. package/examples/string-touch/index.html +70 -0
  43. package/examples/string-touch/scene.json +73 -0
  44. package/examples/string-touch/styles.css +273 -0
  45. package/package.json +82 -0
  46. package/schemas/scene.schema.json +336 -0
  47. package/scripts/render-demo-videos.sh +77 -0
  48. package/scripts/serve.js +88 -0
  49. package/scripts/update-visual-fixtures.mjs +60 -0
  50. package/scripts/verify-demo-videos.mjs +138 -0
  51. package/skills/oddlyalive/SKILL.md +68 -0
  52. package/skills/oddlyalive/agents/openai.yaml +4 -0
  53. package/skills/oddlyalive/references/object-models.md +25 -0
  54. package/skills/oddlyalive/references/recipes.md +27 -0
  55. package/skills/oddlyalive/references/scene-schema.md +84 -0
  56. package/src/crystal-renderer.js +338 -0
  57. package/src/gesture.js +58 -0
  58. package/src/index.js +44 -0
  59. package/src/rigid-balls.js +304 -0
  60. package/src/rigid-renderer.js +760 -0
  61. package/src/rigid-scene.js +223 -0
  62. package/src/scene-registry.js +17 -0
  63. package/src/scene.js +206 -0
  64. package/src/state-hash.js +17 -0
  65. package/src/strands.js +773 -0
  66. package/src/surface-wave.js +209 -0
  67. package/src/svg-renderer.js +348 -0
  68. package/src/wave-renderer.js +463 -0
  69. package/src/wave-scene.js +163 -0
  70. package/tests/cli.test.js +101 -0
  71. package/tests/determinism.test.js +57 -0
  72. package/tests/fixtures/visual-baselines.json +27 -0
  73. package/tests/rigid-balls.test.js +77 -0
  74. package/tests/scene.test.js +78 -0
  75. package/tests/surface-wave.test.js +39 -0
  76. package/tests/visual-fixtures.test.js +43 -0
  77. package/videos/oddlyalive-demos/BRIEF.md +40 -0
  78. package/videos/oddlyalive-demos/DEMO-PLAN.md +13 -0
  79. package/videos/oddlyalive-demos/README.md +45 -0
  80. package/videos/oddlyalive-demos/app.js +212 -0
  81. package/videos/oddlyalive-demos/hyperframes.json +9 -0
  82. package/videos/oddlyalive-demos/index.html +246 -0
  83. package/videos/oddlyalive-demos/meta.json +5 -0
  84. package/videos/oddlyalive-demos/package-lock.json +506 -0
  85. package/videos/oddlyalive-demos/package.json +19 -0
  86. package/videos/oddlyalive-demos/prepare-assets.mjs +16 -0
@@ -0,0 +1,56 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="description" content="OddlyAlive football kick impulse example" />
7
+ <title>Football Kick — OddlyAlive</title>
8
+ <link rel="stylesheet" href="../shared/example.css" />
9
+ </head>
10
+ <body class="theme-kick">
11
+ <main>
12
+ <header>
13
+ <a class="wordmark" href="../"><span class="mark"></span>OddlyAlive</a>
14
+ <nav>
15
+ <a href="../ball-lab/">← Ball Lab</a>
16
+ <a href="../shoe-splash/">Shoe Splash →</a>
17
+ </nav>
18
+ </header>
19
+ <section class="stage">
20
+ <div class="poster">
21
+ <div class="poster-copy">
22
+ <p class="eyebrow">04 / CONTACT IMPULSE</p>
23
+ <h1>KICK IT.<br /><em>DON’T KEYFRAME IT.</em></h1>
24
+ <p class="description">
25
+ One timed impulse supplies force and spin. Gravity, restitution
26
+ and turf friction write every frame that follows.
27
+ </p>
28
+ </div>
29
+ <p class="corner-note">ONE IMPULSE<br />NO AUTHORED ARC<br />DETERMINISTIC</p>
30
+ <svg
31
+ id="motion"
32
+ class="motion"
33
+ viewBox="0 0 960 540"
34
+ role="img"
35
+ aria-label="A football kicked across a field toward a goal"
36
+ ></svg>
37
+ <div class="folio">OA—004</div>
38
+ <div id="loading" class="loading">SOLVING WORLD…</div>
39
+ </div>
40
+ </section>
41
+ <footer>
42
+ <div class="controls">
43
+ <button id="toggle" type="button">PAUSE</button>
44
+ <input id="scrubber" type="range" min="0" value="0" aria-label="Timeline" />
45
+ </div>
46
+ <dl class="metrics">
47
+ <div><dt>solver</dt><dd>rigid circles</dd></div>
48
+ <div><dt>step</dt><dd>240 Hz</dd></div>
49
+ <div><dt id="metric-name">impulse</dt><dd id="metric">—</dd></div>
50
+ <div><dt>fingerprint</dt><dd id="fingerprint">—</dd></div>
51
+ </dl>
52
+ </footer>
53
+ </main>
54
+ <script type="module" src="./app.js"></script>
55
+ </body>
56
+ </html>
@@ -0,0 +1,44 @@
1
+ {
2
+ "$schema": "../../schemas/scene.schema.json",
3
+ "version": 1,
4
+ "type": "rigid-balls",
5
+ "name": "OddlyAlive — Football Kick",
6
+ "seed": 27,
7
+ "canvas": { "width": 960, "height": 540 },
8
+ "timing": { "duration": 6.4, "fps": 60, "substeps": 4, "preRoll": 0 },
9
+ "world": {
10
+ "gravity": 920,
11
+ "airDrag": 0.08,
12
+ "floorY": 430,
13
+ "left": 70,
14
+ "right": 910,
15
+ "groundFriction": 2.4
16
+ },
17
+ "bodies": [
18
+ {
19
+ "id": "ball",
20
+ "kind": "football",
21
+ "label": "FOOTBALL",
22
+ "x": 180,
23
+ "y": 392,
24
+ "radius": 38,
25
+ "mass": 0.43,
26
+ "restitution": 0.62,
27
+ "friction": 0.58,
28
+ "vx": 0,
29
+ "vy": 0,
30
+ "angle": 0,
31
+ "angularVelocity": 0,
32
+ "color": "#f6f3e9"
33
+ }
34
+ ],
35
+ "impulses": [
36
+ {
37
+ "time": 0.62,
38
+ "body": "ball",
39
+ "impulseX": 300,
40
+ "impulseY": -235,
41
+ "angularImpulse": 4.6
42
+ }
43
+ ]
44
+ }
@@ -0,0 +1,379 @@
1
+ :root {
2
+ color-scheme: dark;
3
+ font-family:
4
+ Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
5
+ background: #121211;
6
+ color: #f3efe7;
7
+ }
8
+
9
+ * {
10
+ box-sizing: border-box;
11
+ }
12
+
13
+ body {
14
+ width: min(1180px, calc(100vw - 40px));
15
+ margin: 0 auto;
16
+ background:
17
+ radial-gradient(circle at 50% 12%, #302b24, transparent 38rem),
18
+ #121211;
19
+ }
20
+
21
+ body > header,
22
+ body > footer {
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: space-between;
26
+ min-height: 76px;
27
+ color: #8f8a81;
28
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
29
+ font-size: 9px;
30
+ letter-spacing: 0.13em;
31
+ }
32
+
33
+ a {
34
+ color: inherit;
35
+ text-decoration: none;
36
+ }
37
+
38
+ .wordmark {
39
+ display: inline-flex;
40
+ gap: 10px;
41
+ align-items: center;
42
+ color: #f3efe7;
43
+ font-family:
44
+ Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
45
+ font-size: 14px;
46
+ font-weight: 750;
47
+ letter-spacing: -0.02em;
48
+ }
49
+
50
+ .mark {
51
+ width: 13px;
52
+ height: 13px;
53
+ border: 2px solid #ee6540;
54
+ border-radius: 50%;
55
+ box-shadow: inset 4px 0 0 #ee6540;
56
+ }
57
+
58
+ .hero {
59
+ padding: 74px 0 80px;
60
+ border-top: 1px solid #302e2a;
61
+ border-bottom: 1px solid #302e2a;
62
+ }
63
+
64
+ .hero > p,
65
+ .card-copy p,
66
+ .card-next p {
67
+ margin: 0 0 12px;
68
+ color: #8f8a81;
69
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
70
+ font-size: 9px;
71
+ font-weight: 700;
72
+ letter-spacing: 0.13em;
73
+ }
74
+
75
+ h1 {
76
+ margin: 0;
77
+ max-width: 100%;
78
+ font-size: clamp(48px, 7vw, 96px);
79
+ font-weight: 900;
80
+ letter-spacing: -0.075em;
81
+ line-height: 0.84;
82
+ }
83
+
84
+ h1 em {
85
+ color: #ee6540;
86
+ font-style: normal;
87
+ }
88
+
89
+ .hero-meta {
90
+ display: flex;
91
+ gap: 26px;
92
+ margin-top: 34px;
93
+ color: #b3aea4;
94
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
95
+ font-size: 9px;
96
+ letter-spacing: 0.1em;
97
+ }
98
+
99
+ .grid {
100
+ display: grid;
101
+ grid-template-columns: repeat(2, minmax(0, 1fr));
102
+ gap: 14px;
103
+ padding: 14px 0 0;
104
+ }
105
+
106
+ .card {
107
+ position: relative;
108
+ min-height: 360px;
109
+ overflow: hidden;
110
+ border: 1px solid #3b3832;
111
+ border-radius: 4px;
112
+ background: #ede5d3;
113
+ color: #20201d;
114
+ transition:
115
+ transform 180ms ease,
116
+ border-color 180ms ease;
117
+ }
118
+
119
+ a.card:hover {
120
+ z-index: 2;
121
+ border-color: #ee6540;
122
+ transform: translateY(-3px);
123
+ }
124
+
125
+ .preview {
126
+ position: absolute;
127
+ inset: 0;
128
+ }
129
+
130
+ .card-copy {
131
+ position: absolute;
132
+ right: 28px;
133
+ bottom: 25px;
134
+ left: 28px;
135
+ z-index: 3;
136
+ }
137
+
138
+ .card-copy p {
139
+ margin-bottom: 7px;
140
+ color: #716c62;
141
+ font-size: 8px;
142
+ }
143
+
144
+ .card-copy h2,
145
+ .card-next h2 {
146
+ margin: 0 0 8px;
147
+ font-size: clamp(28px, 3.3vw, 46px);
148
+ letter-spacing: -0.055em;
149
+ line-height: 0.95;
150
+ }
151
+
152
+ .card-copy span,
153
+ .card-next span {
154
+ color: #716c62;
155
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
156
+ font-size: 9px;
157
+ }
158
+
159
+ .strings {
160
+ background: linear-gradient(135deg, #efe7d5, #ded5c3);
161
+ }
162
+
163
+ .strings span {
164
+ position: relative;
165
+ display: inline-block;
166
+ width: 1px;
167
+ height: 210px;
168
+ margin-left: 14%;
169
+ background: #2c2a25;
170
+ transform-origin: top;
171
+ }
172
+
173
+ .strings span:nth-child(1) {
174
+ transform: rotate(-19deg);
175
+ }
176
+
177
+ .strings span:nth-child(2) {
178
+ transform: rotate(-10deg);
179
+ }
180
+
181
+ .strings span:nth-child(3) {
182
+ transform: rotate(3deg);
183
+ }
184
+
185
+ .strings span:nth-child(4) {
186
+ transform: rotate(11deg);
187
+ }
188
+
189
+ .strings span:nth-child(5) {
190
+ transform: rotate(20deg);
191
+ }
192
+
193
+ .strings span::after {
194
+ position: absolute;
195
+ bottom: 0;
196
+ left: -4px;
197
+ content: "●";
198
+ color: #ee6540;
199
+ font-size: 8px;
200
+ }
201
+
202
+ .crystals {
203
+ display: flex;
204
+ gap: 10%;
205
+ justify-content: center;
206
+ padding-top: 38px;
207
+ background: #f2ebdc;
208
+ }
209
+
210
+ .crystals i {
211
+ position: relative;
212
+ width: 1px;
213
+ height: 170px;
214
+ background: #34312a;
215
+ }
216
+
217
+ .crystals i::after {
218
+ position: absolute;
219
+ bottom: -25px;
220
+ left: -13px;
221
+ width: 27px;
222
+ height: 36px;
223
+ background: #83c5be;
224
+ clip-path: polygon(50% 0, 100% 28%, 76% 82%, 50% 100%, 22% 82%, 0 28%);
225
+ content: "";
226
+ }
227
+
228
+ .crystals i:nth-child(2)::after,
229
+ .crystals i:nth-child(5)::after {
230
+ background: #e29578;
231
+ }
232
+
233
+ .crystals i:nth-child(3)::after {
234
+ background: #b8c0ff;
235
+ }
236
+
237
+ .balls {
238
+ background: #e8dfcd;
239
+ }
240
+
241
+ .balls i {
242
+ position: absolute;
243
+ display: block;
244
+ border: 2px solid #22211e;
245
+ border-radius: 50%;
246
+ }
247
+
248
+ .baseball {
249
+ top: 72px;
250
+ left: 16%;
251
+ width: 54px;
252
+ height: 54px;
253
+ background: #f4efe4;
254
+ }
255
+
256
+ .basketball {
257
+ top: 42px;
258
+ left: 42%;
259
+ width: 88px;
260
+ height: 88px;
261
+ background:
262
+ linear-gradient(90deg, transparent 48%, #222 48% 52%, transparent 52%),
263
+ #e66b2d;
264
+ }
265
+
266
+ .football {
267
+ top: 94px;
268
+ right: 13%;
269
+ width: 68px;
270
+ height: 68px;
271
+ background:
272
+ radial-gradient(circle, #20201d 0 18%, transparent 19%),
273
+ #f6f3e9;
274
+ }
275
+
276
+ .kick {
277
+ background:
278
+ linear-gradient(0deg, #769a58 0 28%, transparent 28%),
279
+ #dbe5c9;
280
+ }
281
+
282
+ .kick i {
283
+ position: absolute;
284
+ top: 80px;
285
+ left: 42%;
286
+ width: 72px;
287
+ height: 72px;
288
+ border: 2px solid #20201d;
289
+ border-radius: 50%;
290
+ background:
291
+ radial-gradient(circle, #20201d 0 18%, transparent 19%),
292
+ #f6f3e9;
293
+ }
294
+
295
+ .kick span {
296
+ position: absolute;
297
+ right: 70px;
298
+ bottom: 100px;
299
+ width: 118px;
300
+ height: 170px;
301
+ border: 5px solid #f3efdf;
302
+ border-bottom: 0;
303
+ }
304
+
305
+ .wave {
306
+ background:
307
+ linear-gradient(0deg, #80c7d4 0 42%, transparent 42%),
308
+ #e7efe9;
309
+ }
310
+
311
+ .wave i {
312
+ position: absolute;
313
+ top: 75px;
314
+ left: 43%;
315
+ width: 130px;
316
+ height: 50px;
317
+ border: 2px solid #20201d;
318
+ border-radius: 60% 18% 36% 24%;
319
+ background: #f4efe3;
320
+ transform: rotate(-12deg);
321
+ }
322
+
323
+ .wave span {
324
+ position: absolute;
325
+ right: 8%;
326
+ bottom: 40%;
327
+ left: 8%;
328
+ height: 18px;
329
+ border-top: 3px solid #1e6f7b;
330
+ border-radius: 50%;
331
+ }
332
+
333
+ .card-next {
334
+ display: flex;
335
+ align-items: flex-end;
336
+ justify-content: space-between;
337
+ padding: 28px;
338
+ border-style: dashed;
339
+ background: #191817;
340
+ color: #f3efe7;
341
+ }
342
+
343
+ .card-next b {
344
+ color: #302e2a;
345
+ font-size: 150px;
346
+ letter-spacing: -0.09em;
347
+ line-height: 0.72;
348
+ }
349
+
350
+ body > footer {
351
+ padding: 22px 0;
352
+ }
353
+
354
+ body > footer strong {
355
+ color: #ee6540;
356
+ font-weight: 700;
357
+ }
358
+
359
+ @media (max-width: 760px) {
360
+ body {
361
+ width: min(100% - 20px, 1180px);
362
+ }
363
+
364
+ .hero {
365
+ padding: 52px 0;
366
+ }
367
+
368
+ .hero-meta {
369
+ flex-wrap: wrap;
370
+ }
371
+
372
+ .grid {
373
+ grid-template-columns: 1fr;
374
+ }
375
+
376
+ .card {
377
+ min-height: 320px;
378
+ }
379
+ }
@@ -0,0 +1,109 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta
7
+ name="description"
8
+ content="OddlyAlive deterministic physics example gallery"
9
+ />
10
+ <title>OddlyAlive Examples — Make anything feel alive</title>
11
+ <link rel="stylesheet" href="./gallery.css" />
12
+ </head>
13
+ <body>
14
+ <header>
15
+ <a class="wordmark" href="./">
16
+ <span class="mark"></span>
17
+ OddlyAlive
18
+ </a>
19
+ <span>OPEN SOURCE · ALPHA 02</span>
20
+ </header>
21
+
22
+ <main>
23
+ <section class="hero">
24
+ <p>PHYSICS RECIPES FOR OBJECT MOTION</p>
25
+ <h1>DON’T ANIMATE IT.<br /><em>GIVE IT A WORLD.</em></h1>
26
+ <div class="hero-meta">
27
+ <span>5 BUILT-IN EXAMPLES</span>
28
+ <span>3 SOLVER FAMILIES</span>
29
+ <span>100% LOCAL</span>
30
+ </div>
31
+ </section>
32
+
33
+ <section class="grid" aria-label="OddlyAlive examples">
34
+ <a class="card card-string" href="./string-touch/">
35
+ <div class="preview strings">
36
+ <span></span><span></span><span></span><span></span><span></span>
37
+ </div>
38
+ <div class="card-copy">
39
+ <p>01 · STRAND XPBD</p>
40
+ <h2>String Touch</h2>
41
+ <span>Pressure, friction and staggered peel →</span>
42
+ </div>
43
+ </a>
44
+
45
+ <a class="card card-crystal" href="./crystal-mobile/">
46
+ <div class="preview crystals">
47
+ <i></i><i></i><i></i><i></i><i></i>
48
+ </div>
49
+ <div class="card-copy">
50
+ <p>02 · WEIGHTED STRANDS</p>
51
+ <h2>Crystal Mobile</h2>
52
+ <span>Heavy payloads catch and overswing →</span>
53
+ </div>
54
+ </a>
55
+
56
+ <a class="card card-ball" href="./ball-lab/">
57
+ <div class="preview balls">
58
+ <i class="baseball"></i>
59
+ <i class="basketball"></i>
60
+ <i class="football"></i>
61
+ </div>
62
+ <div class="card-copy">
63
+ <p>03 · RIGID BODIES</p>
64
+ <h2>Ball Lab</h2>
65
+ <span>Three materials. Three different lives →</span>
66
+ </div>
67
+ </a>
68
+
69
+ <a class="card card-kick" href="./football-kick/">
70
+ <div class="preview kick">
71
+ <i></i>
72
+ <span></span>
73
+ </div>
74
+ <div class="card-copy">
75
+ <p>04 · CONTACT IMPULSE</p>
76
+ <h2>Football Kick</h2>
77
+ <span>Impulse, spin, bounce and roll →</span>
78
+ </div>
79
+ </a>
80
+
81
+ <a class="card card-wave" href="./shoe-splash/">
82
+ <div class="preview wave">
83
+ <i></i>
84
+ <span></span>
85
+ </div>
86
+ <div class="card-copy">
87
+ <p>05 · COUPLED SYSTEM</p>
88
+ <h2>Shoe Splash</h2>
89
+ <span>Rigid prop meets a damped wave surface →</span>
90
+ </div>
91
+ </a>
92
+
93
+ <article class="card card-next">
94
+ <div>
95
+ <p>NEXT PHYSICAL FAMILY</p>
96
+ <h2>Walking rigs</h2>
97
+ <span>Articulated bodies + inverse kinematics</span>
98
+ </div>
99
+ <b>06</b>
100
+ </article>
101
+ </section>
102
+ </main>
103
+
104
+ <footer>
105
+ <span>AI DESCRIBES THE SETUP.</span>
106
+ <strong>PHYSICS CREATES THE MOTION.</strong>
107
+ </footer>
108
+ </body>
109
+ </html>