Spaces:
Running
Running
Phase 2: playable 3D cooperative race and live SpacetimeDB lobby
Browse files- README.md +71 -10
- assets/index-BB9hiUlN.css +1 -0
- assets/index-Caz9y1Qv.js +0 -0
- index.html +3 -19
README.md
CHANGED
|
@@ -1,10 +1,71 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: Singularity
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
-
sdk: static
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Singularity
|
| 3 |
+
emoji: 🪐
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: blue
|
| 6 |
+
sdk: static
|
| 7 |
+
app_file: index.html
|
| 8 |
+
pinned: false
|
| 9 |
+
short_description: Six minds. One body. A multiplayer physics race.
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# Singularity
|
| 13 |
+
|
| 14 |
+
A browser-playable cooperative physics race. Up to **four teams of six players** pilot shared bodies across a floating obstacle course. Each player controls the head, left arm, right arm, torso, left leg, or right leg. Coordinate forces, carry cargo, avoid a sweeper, and finish faster than the other teams.
|
| 15 |
+
|
| 16 |
+
**Play:** https://huggingface.co/spaces/sankalphs/singularity
|
| 17 |
+
**Source:** https://github.com/Sathvikar01/singularity
|
| 18 |
+
|
| 19 |
+
## Play
|
| 20 |
+
|
| 21 |
+
Create a callsign and room code, then share the invite link. Pick the same team to share a body or another team to compete. The first player hosts and starts the countdown. Empty roles remain neutral; pilots can switch to unoccupied parts using **1–6** or the role cards during a race. For a small crew, assign legs first and switch a pilot to an arm for cargo.
|
| 22 |
+
|
| 23 |
+
- **WASD / arrows:** force on your assigned body part. W moves forward along the course, independent of camera angle.
|
| 24 |
+
- **Space:** arms grab nearby cargo while held; legs hop when grounded.
|
| 25 |
+
- **Three ordered objectives:** cross the narrow bridge, carry the gold cube into the mint delivery zone, cross the finish after the sweeper.
|
| 26 |
+
- **Falls:** checkpoint recovery and a three-second penalty. Ten-minute match limit.
|
| 27 |
+
- **Solo practice:** assisted legs and balance; Space grabs with either arm. Practice is unranked.
|
| 28 |
+
|
| 29 |
+
Desktop keyboard is recommended; narrow screens include touch controls. Teams race as separate colored bodies on the same course without inter-team collision.
|
| 30 |
+
|
| 31 |
+
## Architecture
|
| 32 |
+
|
| 33 |
+
Three.js renders procedural 3D assets, lighting, shadows, fog, and constrained articulated bodies. A pure fixed-step position-based physics solver is shared between practice and the **authoritative SpacetimeDB TypeScript module**. The server alone advances ranked simulations, validates roles and checkpoints, sets clocks, ends matches, and writes leaderboard rows. Clients submit bounded inputs; they cannot submit body poses, progress, or completion times. Idle inputs expire after 500 ms. Disconnects release role slots and migrate host ownership. Empty rooms are removed; result rows persist independently.
|
| 34 |
+
|
| 35 |
+
Generated SpacetimeDB bindings subscribe to public tables `room`, `player`, `team`, `result`. The `tick` scheduler is private. Final times always use server timestamps, including penalties; running UI clocks are estimates using the visitor's clock.
|
| 36 |
+
|
| 37 |
+
This initial competitive course uses a custom constrained-body solver and procedural Three.js assets. Unreal/Blender are not runtime requirements. It does not include skeletal animation, voice chat, matchmaking, or player-account authentication. Public callsigns are self-selected.
|
| 38 |
+
|
| 39 |
+
## Develop and verify
|
| 40 |
+
|
| 41 |
+
Requires Node 22.6+ (24 recommended), npm, and SpacetimeDB CLI 2.9+.
|
| 42 |
+
|
| 43 |
+
```sh
|
| 44 |
+
npm ci
|
| 45 |
+
npm ci --prefix spacetimedb
|
| 46 |
+
npm test
|
| 47 |
+
npm run dev
|
| 48 |
+
npm run build
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
The default client connects to `singularity-relay-sankalphs` on Maincloud. For another backend, copy `.env.example` to `.env.local` and set both values before starting Vite/building. These are public settings, not secrets.
|
| 52 |
+
|
| 53 |
+
```sh
|
| 54 |
+
spacetime start --listen-addr 127.0.0.1:3100 --data-dir .spacetime
|
| 55 |
+
spacetime publish singularity-test --module-path spacetimedb --server http://127.0.0.1:3100
|
| 56 |
+
spacetime generate --lang typescript --out-dir src/module_bindings --module-path spacetimedb
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
With Vite running and Chrome installed, `node tests/browser.mjs` verifies cloud lobbies with two browser clients. `node tests/multiplayer.mjs` verifies complete simultaneous races against the local test database. Full completion tests run locally to avoid polluting the public leaderboard.
|
| 60 |
+
|
| 61 |
+
## Deploy
|
| 62 |
+
|
| 63 |
+
```sh
|
| 64 |
+
spacetime login
|
| 65 |
+
spacetime publish singularity-relay-sankalphs --module-path spacetimedb --server maincloud
|
| 66 |
+
npm run build
|
| 67 |
+
python scripts/deploy.py "Deploy verified game build"
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
The script uses existing Hugging Face credentials and uploads the static production build. SpacetimeDB Cloud provides persistence. No paid graphics server is required. An offline backend is visibly reported; no fake online players or local ranked results are substituted.
|
| 71 |
+
|
assets/index-BB9hiUlN.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
@import"https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@500;600;700;800;900&family=DM+Sans:wght@400;500;600;700&display=swap";:root{font-family:DM Sans,sans-serif;color:#eef4ee;background:#12212b;font-synthesis:none;--mint:#aff2d6;--muted:#a4b1b9;--panel:rgba(18,31,41,.88);--line:rgba(206,226,226,.15);--coral:#ff8875}*{box-sizing:border-box}body{margin:0;overflow:hidden}button,input,select{font:inherit}button{cursor:pointer;color:inherit}button:focus-visible,input:focus-visible,select:focus-visible{outline:2px solid var(--mint);outline-offset:4px}button{border:1px solid var(--line);background:#263844;border-radius:6px;padding:12px 17px;transition:background .2s,transform .2s}button:hover{background:#354c58;transform:translateY(-1px)}button.primary{background:var(--mint);color:#142b28;border:0;font-weight:700}button.primary:hover{background:#d5ffe8}button:disabled{opacity:.5;cursor:wait}#world{position:absolute;inset:0}#world canvas{display:block;width:100%;height:100%}.vignette{position:absolute;inset:0;pointer-events:none;background:linear-gradient(90deg,rgba(10,23,30,.92),transparent 60%),linear-gradient(0deg,rgba(10,23,30,.7),transparent 25%)}header{height:88px;position:absolute;top:0;left:0;right:0;padding:0 36px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--line);background:#101c2599;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);z-index:3}.brand{display:flex;align-items:center;gap:13px;font-size:22px;font-weight:700;letter-spacing:-1px}.brand-icon{height:32px;width:32px;border:2px solid var(--mint);border-radius:50%;position:relative}.brand-icon:after{content:"";position:absolute;inset:7px -5px;border:2px solid var(--mint);border-radius:50%;transform:rotate(-45deg)}.brand span:last-child{color:var(--mint)}nav{display:flex;align-items:center;gap:9px}nav button{font-size:12px;background:transparent;border:0;color:var(--muted)}.status{display:flex;align-items:center;gap:8px;font-size:10px;letter-spacing:1.5px;color:var(--mint);text-transform:uppercase}.dot{width:6px;height:6px;border-radius:50%;background:var(--mint);box-shadow:0 0 10px #aff2d64a}.intro{position:absolute;top:19%;left:5%;width:370px;z-index:2}.eyebrow{font-size:10px;font-weight:700;letter-spacing:2.8px;color:var(--mint);display:flex;align-items:center;gap:12px}.eyebrow:before{content:"";width:24px;height:1px;background:var(--mint)}h1{font-family:Barlow Condensed,Impact,sans-serif;font-size:clamp(65px,6.4vw,105px);line-height:.93;letter-spacing:-1.5px;font-weight:800;margin:24px 0}h1 em{font-style:normal;color:var(--mint)}.intro p{font-size:13px;color:var(--muted);line-height:1.9;max-width:290px}.actions{display:flex;gap:10px;margin-top:26px}.actions button{font-size:12px}.tiny{font-size:10px;color:#8ca0ac;line-height:1.8}.intro .tiny{margin-top:17px}.course-card{position:absolute;right:28px;top:116px;width:236px;background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:22px;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.section-label{font-size:9px;letter-spacing:2px;color:#9eb0ba;text-transform:uppercase}.course-title{font-size:18px;font-weight:600;margin:10px 0 4px}.course-meta{font-size:10px;color:var(--muted)}.stage{display:flex;align-items:center;gap:12px;padding-top:21px;opacity:.5}.stage.current,.stage.complete{opacity:1}.stage.current .stage-num{background:var(--mint);color:#20322c}.stage.complete .stage-num{color:var(--mint)}.stage-num{width:27px;height:27px;border:1px solid var(--line);border-radius:50%;display:grid;place-items:center;font-size:10px}.stage b{display:block;font-size:11px;font-weight:500}.stage small{font-size:9px;color:var(--muted)}.course-footer{border-top:1px solid var(--line);margin-top:23px;padding-top:14px;display:flex;justify-content:space-between;font-size:9px;color:var(--muted)}.course-footer strong{color:var(--mint);font-weight:500}.role-dock{position:absolute;left:36px;right:36px;bottom:28px;z-index:3}.dock-heading{display:flex;justify-content:space-between;align-items:center;margin-bottom:13px}.dock-heading b{font-size:10px;letter-spacing:1.6px;font-weight:500}.dock-heading span{font-size:10px;color:var(--muted)}.roles{display:grid;grid-template-columns:repeat(6,1fr);gap:9px}.role{position:relative;text-align:left;padding:14px 16px;background:#1a2a34eb;-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);border:1px solid var(--line);min-height:91px;border-radius:8px}.role.active{border-color:var(--mint);background:#415f58d1}.role-icon{color:var(--mint);font-size:23px;line-height:1.1}.role b{display:block;font-size:11px;margin-top:9px;font-weight:500}.role small{display:block;font-size:9px;color:var(--muted);margin-top:5px}.role kbd{position:absolute;right:12px;top:12px;background:transparent;font-size:9px;color:#859da6}.hud{position:absolute;top:116px;left:36px;display:none}.hud .timer{font-family:Barlow Condensed,sans-serif;font-size:57px;letter-spacing:2px;line-height:1.2}.hud .pill{display:inline-block;padding:6px 9px;border:1px solid var(--line);border-radius:4px;font-size:9px;color:var(--mint);margin-top:8px;background:var(--panel)}.instruction{position:absolute;left:50%;bottom:160px;transform:translate(-50%);background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:12px 20px;font-size:11px;white-space:nowrap;display:none;-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px)}kbd{display:inline-block;padding:3px 6px;background:#3c515c;border-radius:3px;font-size:10px;color:#f1f5f1}.playing .intro{display:none}.playing .vignette{background:linear-gradient(0deg,rgba(10,23,30,.6),transparent 25%)}.playing .hud,.playing .instruction{display:block}.toast{position:absolute;left:50%;top:100px;transform:translate(-50%);padding:13px 20px;background:#294139;border:1px solid var(--mint);border-radius:8px;font-size:12px;z-index:20;max-width:90%;text-align:center}.toast:empty{display:none}dialog{background:#172a35;color:#f1f6f1;border:1px solid #4b655e;border-radius:15px;padding:32px;width:min(540px,92vw);box-shadow:0 30px 100px #0009}dialog::backdrop{background:#07121bcc;-webkit-backdrop-filter:blur(7px);backdrop-filter:blur(7px)}dialog h2{font-family:Barlow Condensed,sans-serif;font-size:38px;margin:10px 0}dialog p{font-size:12px;line-height:1.8;color:var(--muted)}dialog .close{position:absolute;top:14px;right:14px;background:transparent;border:0;padding:5px 10px}label.field{display:block;font-size:10px;color:var(--muted);margin-top:17px}input,select{width:100%;padding:12px;margin-top:7px;background:#213743;border:1px solid var(--line);border-radius:5px;color:#fff}.form-row{display:grid;grid-template-columns:1fr 1fr;gap:14px}.members{font-size:12px;line-height:2;color:var(--mint);margin-top:12px}.leader-row{display:grid;grid-template-columns:25px 1fr 80px;gap:10px;border-bottom:1px solid var(--line);padding:12px 0;font-size:12px}.leader-row small{font-size:9px;color:var(--muted)}.big-result{font-family:Barlow Condensed,sans-serif;font-size:70px;color:var(--mint)}#standings{margin-top:14px;font-size:11px;line-height:2;color:var(--mint)}.countdown{position:absolute;inset:0;pointer-events:none;display:grid;place-items:center;font-family:Barlow Condensed,sans-serif;font-size:130px;color:var(--mint);text-shadow:0 4px 40px #000}.touch{display:none;position:absolute;bottom:155px;left:16px;gap:5px;z-index:4}.touch button{padding:13px;touch-action:none;-webkit-user-select:none;user-select:none}.touch .grab{margin-left:20px}.error{color:#ffb0a3;font-size:11px;line-height:1.7}#sound{font-size:15px}.back{margin-top:12px;background:var(--panel);font-size:10px;padding:7px 11px}@media(max-width:1000px){.intro{left:4%;width:300px}.course-card{width:200px;right:20px}.roles{gap:5px}.role{padding:12px 10px}.role-dock{left:20px;right:20px}.role small{font-size:8px}.instruction{font-size:10px}}@media(max-width:700px){header{height:65px;padding:0 18px}.brand{font-size:18px}.status{display:none}nav{gap:0}nav button{padding:8px}.intro{top:15%;left:7%;width:86%}h1{font-size:65px}.intro p{max-width:260px}.course-card{display:none}.role-dock{bottom:14px;left:12px;right:12px}.roles{grid-template-columns:repeat(3,1fr)}.role{min-height:62px;padding:8px 10px}.role-icon{font-size:16px;float:left;margin-right:7px}.role b{font-size:10px;margin:0}.role small{margin-top:7px}.role kbd,.dock-heading span{display:none}.dock-heading{margin-bottom:8px}.hud{top:88px;left:20px}.instruction{bottom:210px;max-width:94%;white-space:normal;line-height:1.8;text-align:center}.playing .touch{display:flex}.playing .instruction{display:none}.vignette{background:linear-gradient(90deg,#0a171ee0,transparent)}.toast{top:75px}dialog{padding:24px}}@media(max-height:700px)and (min-width:701px){.intro{top:110px}h1{font-size:55px;margin:16px 0}.intro p{font-size:11px;line-height:1.6;margin:10px 0}.intro .tiny{display:none}.actions{margin-top:16px}.course-card{padding:18px}.stage{padding-top:16px}}h1 em{white-space:nowrap}.intro .tiny{max-width:350px}.stage b{line-height:1.5}
|
assets/index-Caz9y1Qv.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
index.html
CHANGED
|
@@ -1,19 +1,3 @@
|
|
| 1 |
-
<!doctype html>
|
| 2 |
-
<
|
| 3 |
-
|
| 4 |
-
<meta charset="utf-8" />
|
| 5 |
-
<meta name="viewport" content="width=device-width" />
|
| 6 |
-
<title>My static Space</title>
|
| 7 |
-
<link rel="stylesheet" href="style.css" />
|
| 8 |
-
</head>
|
| 9 |
-
<body>
|
| 10 |
-
<div class="card">
|
| 11 |
-
<h1>Welcome to your static Space!</h1>
|
| 12 |
-
<p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
|
| 13 |
-
<p>
|
| 14 |
-
Also don't forget to check the
|
| 15 |
-
<a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
|
| 16 |
-
</p>
|
| 17 |
-
</div>
|
| 18 |
-
</body>
|
| 19 |
-
</html>
|
|
|
|
| 1 |
+
<!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/><meta name="theme-color" content="#101b24"/><meta name="description" content="Six minds. One body. A cooperative physics race across a floating obstacle course."/><title>Singularity — Six minds. One body.</title> <script type="module" crossorigin src="/assets/index-Caz9y1Qv.js"></script>
|
| 2 |
+
<link rel="stylesheet" crossorigin href="/assets/index-BB9hiUlN.css">
|
| 3 |
+
</head><body><div id="app"></div></body></html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|