push from SNAPKITTYWEST/gitworld
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +10 -0
- .github/workflows/pages.yml +43 -0
- .gitignore +4 -0
- Launch-GitWorld.cmd +21 -0
- README.md +29 -0
- dist/index.html +122 -0
- dist/main.js +167 -0
- dist/phase2-unreal/ReferenceSource/NeonReflections/3087.webp +3 -0
- dist/phase2-unreal/ReferenceSource/NeonReflections/3089.webp +3 -0
- dist/phase2-unreal/ReferenceSource/NeonReflections/3090.webp +3 -0
- dist/phase2-unreal/ReferenceSource/NeonReflections/3092.jpg +3 -0
- dist/phase2-unreal/ReferenceSource/Opening/3099.mp4 +3 -0
- dist/phase2-unreal/ReferenceSource/README.md +7 -0
- dist/phase2-unreal/ReferenceSource/manifest.json +34 -0
- dist/shared.js +90 -0
- dist/src/main.ts +107 -0
- dist/src/shared.js +90 -0
- dist/src/ui-reality.js +210 -0
- dist/src/world-architect.js +451 -0
- dist/styles.css +108 -0
- dist/ui-reality.js +210 -0
- dist/world-architect.js +451 -0
- index.html +122 -0
- launch.html +37 -0
- package.json +12 -0
- phase2-unreal/.gitignore +10 -0
- phase2-unreal/Compute/CMakeLists.txt +10 -0
- phase2-unreal/Compute/GitWorldBuildPass.cu +65 -0
- phase2-unreal/Compute/GitWorldBuildPass.h +16 -0
- phase2-unreal/Compute/README.md +12 -0
- phase2-unreal/Config/DefaultEngine.ini +44 -0
- phase2-unreal/Config/DefaultGame.ini +2 -0
- phase2-unreal/Config/DefaultInput.ini +17 -0
- phase2-unreal/Content/RealityScene/README.md +9 -0
- phase2-unreal/Content/VR/README.md +12 -0
- phase2-unreal/EditorScripts/import_gitworld_opening.py +110 -0
- phase2-unreal/EditorScripts/import_gitworld_reference_scene.py +115 -0
- phase2-unreal/GitWorldUnreal.uproject +23 -0
- phase2-unreal/README.md +42 -0
- phase2-unreal/RealityCapture/README.md +36 -0
- phase2-unreal/RealityCapture/Tools/reconstruct.ps1 +75 -0
- phase2-unreal/ReferenceSource/NeonReflections/3087.webp +3 -0
- phase2-unreal/ReferenceSource/NeonReflections/3089.webp +3 -0
- phase2-unreal/ReferenceSource/NeonReflections/3090.webp +3 -0
- phase2-unreal/ReferenceSource/NeonReflections/3092.jpg +3 -0
- phase2-unreal/ReferenceSource/Opening/3099.mp4 +3 -0
- phase2-unreal/ReferenceSource/README.md +7 -0
- phase2-unreal/ReferenceSource/manifest.json +34 -0
- phase2-unreal/Source/GitWorldUnreal.Target.cs +12 -0
- phase2-unreal/Source/GitWorldUnreal/GitWorldUnreal.Build.cs +46 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,13 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
dist/phase2-unreal/ReferenceSource/NeonReflections/3087.webp filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
dist/phase2-unreal/ReferenceSource/NeonReflections/3089.webp filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
dist/phase2-unreal/ReferenceSource/NeonReflections/3090.webp filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
dist/phase2-unreal/ReferenceSource/NeonReflections/3092.jpg filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
dist/phase2-unreal/ReferenceSource/Opening/3099.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
phase2-unreal/ReferenceSource/NeonReflections/3087.webp filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
phase2-unreal/ReferenceSource/NeonReflections/3089.webp filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
phase2-unreal/ReferenceSource/NeonReflections/3090.webp filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
phase2-unreal/ReferenceSource/NeonReflections/3092.jpg filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
phase2-unreal/ReferenceSource/Opening/3099.mp4 filter=lfs diff=lfs merge=lfs -text
|
.github/workflows/pages.yml
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: GitWorld Pages
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [main]
|
| 6 |
+
workflow_dispatch:
|
| 7 |
+
|
| 8 |
+
permissions:
|
| 9 |
+
contents: read
|
| 10 |
+
pages: write
|
| 11 |
+
id-token: write
|
| 12 |
+
|
| 13 |
+
concurrency:
|
| 14 |
+
group: pages
|
| 15 |
+
cancel-in-progress: true
|
| 16 |
+
|
| 17 |
+
jobs:
|
| 18 |
+
build:
|
| 19 |
+
runs-on: ubuntu-latest
|
| 20 |
+
steps:
|
| 21 |
+
- name: Checkout
|
| 22 |
+
uses: actions/checkout@v4
|
| 23 |
+
- name: Setup Node
|
| 24 |
+
uses: actions/setup-node@v4
|
| 25 |
+
with:
|
| 26 |
+
node-version: 22
|
| 27 |
+
- name: Build browser game
|
| 28 |
+
run: npm run build
|
| 29 |
+
- name: Upload Pages artifact
|
| 30 |
+
uses: actions/upload-pages-artifact@v3
|
| 31 |
+
with:
|
| 32 |
+
path: dist
|
| 33 |
+
|
| 34 |
+
deploy:
|
| 35 |
+
environment:
|
| 36 |
+
name: github-pages
|
| 37 |
+
url: ${{ steps.deployment.outputs.page_url }}
|
| 38 |
+
runs-on: ubuntu-latest
|
| 39 |
+
needs: build
|
| 40 |
+
steps:
|
| 41 |
+
- name: Deploy to GitHub Pages
|
| 42 |
+
id: deployment
|
| 43 |
+
uses: actions/deploy-pages@v4
|
.gitignore
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
node_modules/
|
| 2 |
+
.DS_Store
|
| 3 |
+
Thumbs.db
|
| 4 |
+
*.log
|
Launch-GitWorld.cmd
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
setlocal
|
| 3 |
+
|
| 4 |
+
set "PROJECT=%~dp0phase2-unreal\GitWorldUnreal.uproject"
|
| 5 |
+
set "EDITOR="
|
| 6 |
+
|
| 7 |
+
for %%P in (
|
| 8 |
+
"%ProgramFiles%\Epic Games\UE_5.6\Engine\Binaries\Win64\UnrealEditor.exe"
|
| 9 |
+
"%ProgramFiles%\Epic Games\UE_5.5\Engine\Binaries\Win64\UnrealEditor.exe"
|
| 10 |
+
"%ProgramFiles%\Epic Games\UE_5.4\Engine\Binaries\Win64\UnrealEditor.exe"
|
| 11 |
+
"%ProgramFiles%\Epic Games\UE_5.3\Engine\Binaries\Win64\UnrealEditor.exe"
|
| 12 |
+
"%ProgramFiles%\Epic Games\UE_5.2\Engine\Binaries\Win64\UnrealEditor.exe"
|
| 13 |
+
) do if not defined EDITOR if exist "%%~P" set "EDITOR=%%~P"
|
| 14 |
+
|
| 15 |
+
if defined EDITOR (
|
| 16 |
+
start "GitWorld Unreal" "%EDITOR%" "%PROJECT%" -game -vr
|
| 17 |
+
exit /b 0
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
start "GitWorld Unreal" "%PROJECT%"
|
| 21 |
+
exit /b 0
|
README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GitWorld — browser-playable repository world
|
| 2 |
+
|
| 3 |
+
GitWorld is a static browser game where the simulated repository `SNAPKITTYWEST / pax-coder` becomes a walkable cyberpunk city. Code is represented by buildings and files, commits are construction events, issues are quests, pull requests are gates, and the three named swarm agents are the construction crew.
|
| 4 |
+
|
| 5 |
+
## Play online
|
| 6 |
+
|
| 7 |
+
[Open the GitWorld game on GitHub Pages](https://snapkittywest.github.io/gitworld/)
|
| 8 |
+
|
| 9 |
+
The opening scene, `3099.mp4`, and the four Neon Reflections scene plates are packaged into the Pages artifact. No Unreal installation is required to play the browser build.
|
| 10 |
+
|
| 11 |
+
## Run
|
| 12 |
+
|
| 13 |
+
```bash
|
| 14 |
+
npm test
|
| 15 |
+
npm run build
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
After the build, open `dist/index.html` from a local static file viewer. The app uses no backend, runtime API, or CDN dependency; its local opening video and scene plates are copied into `dist/phase2-unreal/ReferenceSource/`. `src/main.ts` is intentionally browser-compatible TypeScript-flavored JavaScript so the zero-dependency build can copy it directly to `dist/main.js`.
|
| 19 |
+
|
| 20 |
+
## Controls
|
| 21 |
+
|
| 22 |
+
- `WASD` or arrow keys: walk
|
| 23 |
+
- `Enter` / `E`: inspect the nearest district
|
| 24 |
+
- `T`: open the terminal phone
|
| 25 |
+
- `1`–`8`: equip a tool
|
| 26 |
+
- `Esc`: close an overlay
|
| 27 |
+
- Touch arrows: move on small screens
|
| 28 |
+
|
| 29 |
+
All repository values are simulated and labeled in the interface.
|
dist/index.html
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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">
|
| 6 |
+
<meta name="theme-color" content="#061422">
|
| 7 |
+
<meta name="description" content="GitWorld: walk inside a simulated repository.">
|
| 8 |
+
<title>GitWorld — the world you walk into</title>
|
| 9 |
+
<link rel="stylesheet" href="./styles.css">
|
| 10 |
+
</head>
|
| 11 |
+
<body>
|
| 12 |
+
<section id="opening-scene" class="opening-scene" aria-label="GitWorld opening scene">
|
| 13 |
+
<video id="opening-video" class="opening-video" playsinline muted preload="auto"></video>
|
| 14 |
+
<div class="opening-vignette" aria-hidden="true"></div>
|
| 15 |
+
<div class="opening-copy">
|
| 16 |
+
<span class="section-kicker">VRCHAT WORLD · NEON REFLECTIONS</span>
|
| 17 |
+
<h1>GITWORLD</h1>
|
| 18 |
+
<p>Walk into the code. Every repository is a living world.</p>
|
| 19 |
+
<div class="opening-meta"><span><i></i> OPENING SCENE READY</span><span>LOCAL BROWSER RENDER</span><span>VR-STYLE VIEW</span></div>
|
| 20 |
+
<div class="opening-actions"><button class="opening-enter" data-action="enter-game">ENTER THE WORLD <b>→</b></button><button class="opening-sound" data-action="toggle-opening-sound">SOUND OFF</button></div>
|
| 21 |
+
<small id="opening-status">Loading 3099.mp4…</small>
|
| 22 |
+
</div>
|
| 23 |
+
</section>
|
| 24 |
+
<div id="game-shell" class="game-shell" data-view="city">
|
| 25 |
+
<div class="ambient ambient-one" aria-hidden="true"></div>
|
| 26 |
+
<div class="ambient ambient-two" aria-hidden="true"></div>
|
| 27 |
+
<header class="topbar">
|
| 28 |
+
<div class="brand-lockup"><span class="brand-mark">⌘</span><span><strong>GitWorld</strong><small>THE WORLD YOU WALK INTO</small></span></div>
|
| 29 |
+
<div class="repo-search-wrap"><label for="repo-search">Local repo search</label><input id="repo-search" type="search" placeholder="Search the city…" autocomplete="off"><kbd>⌘ K</kbd></div>
|
| 30 |
+
<div class="topbar-actions"><span class="simulated-pill"><i></i> BROWSER-LOCAL SIMULATION</span><button class="icon-button" data-action="open-terminal" aria-label="Open terminal phone">▣</button></div>
|
| 31 |
+
</header>
|
| 32 |
+
|
| 33 |
+
<aside class="left-rail panel-glass" aria-label="Welcome to GitWorld">
|
| 34 |
+
<div class="panel-title"><span class="section-kicker">WELCOME TO</span><h1>GITWORLD</h1><i></i></div>
|
| 35 |
+
<p class="welcome-copy">A virtual world where GitHub repositories are living places. Walk in. Explore. Watch agents build in real time.</p>
|
| 36 |
+
<p class="welcome-copy muted">Code is no longer abstract.<br><strong>It’s physical.</strong></p>
|
| 37 |
+
<div class="rail-divider"></div>
|
| 38 |
+
<div class="component-heading"><span>CORE COMPONENTS</span><small>THE CITY LEGEND</small></div>
|
| 39 |
+
<ul class="component-list">
|
| 40 |
+
<li><b>◉</b><span>Repositories are <strong>Worlds</strong></span></li>
|
| 41 |
+
<li><b>♙</b><span>Agents are <strong>Builders</strong></span></li>
|
| 42 |
+
<li><b>✦</b><span>Commits are <strong>Events</strong></span></li>
|
| 43 |
+
<li><b>↔</b><span>Pull Requests are <strong>Doors</strong></span></li>
|
| 44 |
+
<li><b>!</b><span>Issues are <strong>Quests</strong></span></li>
|
| 45 |
+
<li><b>⌘</b><span>Code is <strong>Physical</strong></span></li>
|
| 46 |
+
</ul>
|
| 47 |
+
<div class="how-it-works">
|
| 48 |
+
<div class="component-heading"><span>HOW IT WORKS</span><small>YOUR LOOP</small></div>
|
| 49 |
+
<ol><li>Walk inside a repository</li><li>Explore code as a world</li><li>Watch agents build</li><li>Inspect software objects</li><li>Build · test · deploy</li></ol>
|
| 50 |
+
</div>
|
| 51 |
+
</aside>
|
| 52 |
+
|
| 53 |
+
<main class="world-stage" aria-label="Playable GitWorld city">
|
| 54 |
+
<div class="stage-heading"><div><span class="section-kicker">ENTER REPOSITORY</span><h2>SNAPKITTYWEST <b>/ pax-coder</b></h2></div><div class="stage-state"><span id="repo-visibility">Public</span><span id="repo-branch">BRANCH / main</span></div></div>
|
| 55 |
+
<div id="world-viewport" class="world-viewport" tabindex="0" aria-label="Walkable repository city. Use WASD or arrow keys to move."></div>
|
| 56 |
+
<section id="reality-deck" class="reality-deck" aria-label="Browser reality capture scene references">
|
| 57 |
+
<div class="reality-deck-heading"><span><i class="live-dot"></i> REALITY DECK</span><small>NEON REFLECTIONS · BROWSER SCENE PLATES</small></div>
|
| 58 |
+
<div class="reality-deck-grid">
|
| 59 |
+
<video class="reality-plate" data-reference-image="NeonReflections/3089.webp" muted playsinline aria-label="Neon reflections social hub reference"></video>
|
| 60 |
+
<video class="reality-plate" data-reference-image="NeonReflections/3087.webp" muted playsinline aria-label="Arcade portal reference"></video>
|
| 61 |
+
<video class="reality-plate" data-reference-image="NeonReflections/3090.webp" muted playsinline aria-label="GitWorld spawn point reference"></video>
|
| 62 |
+
<video class="reality-plate" data-reference-image="NeonReflections/3092.jpg" muted playsinline aria-label="Reality capture avatar reference"></video>
|
| 63 |
+
</div>
|
| 64 |
+
</section>
|
| 65 |
+
<div id="nearby-prompt" class="nearby-prompt" aria-live="polite"><span>WALKABLE CITY</span><strong>NORTH</strong><small>WASD · ARROWS TO NAVIGATE</small></div>
|
| 66 |
+
<div class="city-reticle" aria-hidden="true"><i></i><b></b></div>
|
| 67 |
+
<div class="movement-help"><kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd><span>walk</span><kbd>E</kbd><span>enter</span></div>
|
| 68 |
+
<div class="feedback-line" aria-live="polite"><i></i><span id="feedback-line">Walk toward a district or click a glowing building.</span><time id="world-clock">00:00:00</time></div>
|
| 69 |
+
</main>
|
| 70 |
+
|
| 71 |
+
<aside class="right-rail" aria-label="GitWorld telemetry">
|
| 72 |
+
<section class="map-panel panel-glass">
|
| 73 |
+
<div class="panel-heading"><span><i class="live-dot"></i> GITWORLD MAP</span><button class="icon-button" data-action="close-inspector" aria-label="Collapse map">×</button></div>
|
| 74 |
+
<div id="minimap-viewport" class="minimap-viewport"></div>
|
| 75 |
+
<div class="map-legend"><span><i class="legend-player"></i> YOU</span><span><i class="legend-building"></i> DISTRICT</span><span><i class="legend-road"></i> ROAD</span></div>
|
| 76 |
+
</section>
|
| 77 |
+
<section class="agents-panel panel-glass">
|
| 78 |
+
<div class="panel-heading"><span>BUILD AGENTS <b class="count-badge" id="active-agent-count">3</b></span><small>LIVE SWARM</small></div>
|
| 79 |
+
<ul id="agent-list" class="agent-list"></ul>
|
| 80 |
+
<div class="swarm-flow"><span>WORLD ARCHITECT</span><i>↓</i><span>REPOSITORY BUILDER</span><i>↓</i><span>REALITY/UI INTEGRATOR</span></div>
|
| 81 |
+
</section>
|
| 82 |
+
<section class="event-panel panel-glass">
|
| 83 |
+
<div class="panel-heading"><span>CONSTRUCTION FEED</span><small id="event-count">3 EVENTS</small></div>
|
| 84 |
+
<ul id="event-feed" class="event-feed" aria-live="polite"></ul>
|
| 85 |
+
</section>
|
| 86 |
+
</aside>
|
| 87 |
+
|
| 88 |
+
<section id="inspector-drawer" class="inspector-drawer panel-glass" aria-label="Repository inspector">
|
| 89 |
+
<div class="drawer-heading"><div><span class="section-kicker">CONTEXTUAL INSPECT</span><strong>PHYSICAL SOFTWARE</strong></div><button class="icon-button" data-action="close-inspector" aria-label="Close inspector">×</button></div>
|
| 90 |
+
<div id="inspector-content" class="inspector-content"></div>
|
| 91 |
+
<div class="drawer-tabs"><button data-action="select-collection" data-collection="files">FILES</button><button data-action="select-collection" data-collection="commits">COMMITS</button><button data-action="select-collection" data-collection="issues">ISSUES</button><button data-action="select-collection" data-collection="prs">PRS</button><button data-action="select-collection" data-collection="build">CI</button></div>
|
| 92 |
+
</section>
|
| 93 |
+
|
| 94 |
+
<section id="terminal-phone" class="terminal-phone" aria-label="Simulated terminal phone" aria-hidden="true">
|
| 95 |
+
<div class="phone-speaker"></div><div class="terminal-top"><span>● local-shell</span><button class="icon-button" data-action="close-terminal" aria-label="Close terminal">×</button></div>
|
| 96 |
+
<div class="terminal-screen"><div class="terminal-header"><strong>_ bash</strong><small>pax-coder · main</small></div><div id="terminal-output" class="terminal-output"></div><form id="terminal-form" class="terminal-form"><span>$</span><input id="terminal-input" aria-label="Terminal command" placeholder="type a command…" autocomplete="off"><button type="submit" aria-label="Run command">↵</button></form><div class="terminal-shortcuts"><button data-action="command" data-command="ls">ls</button><button data-action="command" data-command="git status">git status</button><button data-action="command" data-command="cargo test">cargo test</button><button data-action="command" data-command="./build.sh">./build.sh</button></div></div><div class="phone-home"></div>
|
| 97 |
+
</section>
|
| 98 |
+
|
| 99 |
+
<nav id="toolbelt" class="toolbelt panel-glass" aria-label="Software toolbelt"></nav>
|
| 100 |
+
|
| 101 |
+
<section class="software-shelf" aria-labelledby="software-objects-title">
|
| 102 |
+
<div class="shelf-title"><span id="software-objects-title">SEE SOFTWARE AS PHYSICAL OBJECTS</span><small>CLICK A TOOL OR BUILDING TO INTERACT</small></div>
|
| 103 |
+
<div class="object-grid">
|
| 104 |
+
<button class="object-card" data-action="open-terminal"><span class="object-art phone-art">▣</span><strong>TERMINAL = PHONE</strong><small>1 · open shell</small></button>
|
| 105 |
+
<button class="object-card" data-action="tool" data-tool-id="bash"><span class="object-art hammer-art">⚒</span><strong>BASH = HAMMER</strong><small>2 · run operation</small></button>
|
| 106 |
+
<button class="object-card" data-action="tool" data-tool-id="git"><span class="object-art backpack-art">◈</span><strong>GIT = BACKPACK</strong><small>3 · view history</small></button>
|
| 107 |
+
<button class="object-card" data-action="tool" data-tool-id="docker"><span class="object-art box-art">▦</span><strong>DOCKER = BOX</strong><small>4 · open container</small></button>
|
| 108 |
+
<button class="object-card" data-action="tool" data-tool-id="code"><span class="object-art tablet-art">⌘</span><strong>CODE = TABLET</strong><small>5 · read source</small></button>
|
| 109 |
+
<button class="object-card" data-action="tool" data-tool-id="ssh"><span class="object-art key-art">⚿</span><strong>SSH = KEY</strong><small>6 · unlock gate</small></button>
|
| 110 |
+
<button class="object-card" data-action="tool" data-tool-id="database"><span class="object-art vault-art">▤</span><strong>DATABASE = VAULT</strong><small>7 · inspect data</small></button>
|
| 111 |
+
<button class="object-card" data-action="tool" data-tool-id="deploy"><span class="object-art rocket-art">↗</span><strong>DEPLOY = ROCKET</strong><small>8 · launch build</small></button>
|
| 112 |
+
</div>
|
| 113 |
+
</section>
|
| 114 |
+
|
| 115 |
+
<div id="touch-controls" class="touch-controls" aria-label="Touch movement controls">
|
| 116 |
+
<button data-action="move" data-dx="0" data-dy="-1" aria-label="Walk north">▲</button><div><button data-action="move" data-dx="-1" data-dy="0" aria-label="Walk west">◀</button><button data-action="move" data-dx="0" data-dy="1" aria-label="Walk south">▼</button><button data-action="move" data-dx="1" data-dy="0" aria-label="Walk east">▶</button></div>
|
| 117 |
+
</div>
|
| 118 |
+
<footer class="disclaimer"><span>SIMULATED REPOSITORY DATA</span><span>NO BACKEND · NO NETWORK · LOCAL DEMO</span><span id="player-level">LEVEL 12 DEV</span></footer>
|
| 119 |
+
</div>
|
| 120 |
+
<script type="module" src="./main.js"></script>
|
| 121 |
+
</body>
|
| 122 |
+
</html>
|
dist/main.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { EVENT_TYPES, TOOLBELT, commandTranscript } from './shared.js';
|
| 2 |
+
import {
|
| 3 |
+
advanceWorldState,
|
| 4 |
+
createWorldState,
|
| 5 |
+
getNearbyDistrict,
|
| 6 |
+
renderMinimap,
|
| 7 |
+
renderWorld,
|
| 8 |
+
worldReducer
|
| 9 |
+
} from './world-architect.js';
|
| 10 |
+
import { bindUi, renderHud } from './ui-reality.js';
|
| 11 |
+
|
| 12 |
+
const app = document;
|
| 13 |
+
const worldViewport = app.getElementById('world-viewport');
|
| 14 |
+
const minimap = app.getElementById('minimap-viewport');
|
| 15 |
+
let state = createWorldState();
|
| 16 |
+
|
| 17 |
+
function assetPath(relativePath) {
|
| 18 |
+
const root = './phase2-unreal/ReferenceSource/';
|
| 19 |
+
const UrlConstructor = window.URL;
|
| 20 |
+
return new UrlConstructor(root + relativePath, window.location.href).href;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
function setupBrowserScene() {
|
| 24 |
+
const opening = app.getElementById('opening-scene');
|
| 25 |
+
const video = app.getElementById('opening-video');
|
| 26 |
+
const enter = app.querySelector('[data-action="enter-game"]');
|
| 27 |
+
const sound = app.querySelector('[data-action="toggle-opening-sound"]');
|
| 28 |
+
const status = app.getElementById('opening-status');
|
| 29 |
+
if (!opening || !video || !enter) return;
|
| 30 |
+
|
| 31 |
+
const enterWorld = () => {
|
| 32 |
+
opening.classList.add('is-dismissed');
|
| 33 |
+
opening.setAttribute('aria-hidden', 'true');
|
| 34 |
+
video.pause();
|
| 35 |
+
worldViewport?.focus();
|
| 36 |
+
};
|
| 37 |
+
|
| 38 |
+
try {
|
| 39 |
+
video.src = assetPath('Opening/3099.mp4');
|
| 40 |
+
video.poster = assetPath('NeonReflections/3090.webp');
|
| 41 |
+
video.load();
|
| 42 |
+
const openingFallback = window.setTimeout(() => {
|
| 43 |
+
if (video.readyState < 2) enterWorld();
|
| 44 |
+
}, 2500);
|
| 45 |
+
video.play().catch(() => {
|
| 46 |
+
if (status) status.textContent = 'Opening scene ready · press ENTER THE WORLD';
|
| 47 |
+
});
|
| 48 |
+
video.addEventListener('playing', () => {
|
| 49 |
+
window.clearTimeout(openingFallback);
|
| 50 |
+
if (status) status.textContent = 'Playing 3099.mp4 · opening scene';
|
| 51 |
+
}, { once: true });
|
| 52 |
+
video.addEventListener('error', () => {
|
| 53 |
+
if (status) status.textContent = 'Opening media unavailable · entering browser scene';
|
| 54 |
+
window.setTimeout(enterWorld, 700);
|
| 55 |
+
}, { once: true });
|
| 56 |
+
} catch {
|
| 57 |
+
if (status) status.textContent = 'Opening media unavailable · entering browser scene';
|
| 58 |
+
enterWorld();
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
enter.addEventListener('click', enterWorld);
|
| 62 |
+
sound?.addEventListener('click', () => {
|
| 63 |
+
video.muted = !video.muted;
|
| 64 |
+
sound.textContent = video.muted ? 'SOUND OFF' : 'SOUND ON';
|
| 65 |
+
});
|
| 66 |
+
|
| 67 |
+
try {
|
| 68 |
+
app.querySelectorAll('[data-reference-image]').forEach((plate) => {
|
| 69 |
+
plate.setAttribute('poster', assetPath(plate.dataset.referenceImage || ''));
|
| 70 |
+
});
|
| 71 |
+
} catch {
|
| 72 |
+
// Reference plates are optional presentation assets; never block gameplay.
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
function dispatch(event) {
|
| 77 |
+
state = worldReducer(state, event);
|
| 78 |
+
window.gitWorldState = state;
|
| 79 |
+
render();
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
function render() {
|
| 83 |
+
const nearby = getNearbyDistrict(state);
|
| 84 |
+
if (worldViewport) renderWorld(worldViewport, state, (districtId) => dispatch({ type: EVENT_TYPES.SELECT_DISTRICT, districtId }));
|
| 85 |
+
if (minimap) renderMinimap(minimap, state);
|
| 86 |
+
renderHud(app, state, nearby);
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
function move(dx, dy) {
|
| 90 |
+
dispatch({ type: EVENT_TYPES.PLAYER_MOVE, dx, dy });
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
function useToolByKey(key) {
|
| 94 |
+
const tool = TOOLBELT.find((candidate) => candidate.key === key);
|
| 95 |
+
if (!tool) return;
|
| 96 |
+
dispatch({ type: EVENT_TYPES.USE_TOOL, toolId: tool.id, message: `${tool.name}: ${tool.verb}.` });
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
function inspectNearest() {
|
| 100 |
+
const nearby = getNearbyDistrict(state);
|
| 101 |
+
if (nearby.inRange) {
|
| 102 |
+
dispatch({ type: EVENT_TYPES.ENTER_DISTRICT, districtId: nearby.district.id });
|
| 103 |
+
} else {
|
| 104 |
+
dispatch({ type: EVENT_TYPES.SELECT_DISTRICT, districtId: nearby.district.id });
|
| 105 |
+
}
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
function onKeyDown(event) {
|
| 109 |
+
const target = event.target;
|
| 110 |
+
const typing = target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement;
|
| 111 |
+
if (typing && event.key !== 'Escape') return;
|
| 112 |
+
const key = event.key.toLowerCase();
|
| 113 |
+
const movement = {
|
| 114 |
+
w: [0, -1], arrowup: [0, -1],
|
| 115 |
+
s: [0, 1], arrowdown: [0, 1],
|
| 116 |
+
a: [-1, 0], arrowleft: [-1, 0],
|
| 117 |
+
d: [1, 0], arrowright: [1, 0]
|
| 118 |
+
}[key];
|
| 119 |
+
if (movement) {
|
| 120 |
+
event.preventDefault();
|
| 121 |
+
move(movement[0], movement[1]);
|
| 122 |
+
return;
|
| 123 |
+
}
|
| 124 |
+
if (key === 'e' || event.key === 'Enter') {
|
| 125 |
+
event.preventDefault();
|
| 126 |
+
inspectNearest();
|
| 127 |
+
return;
|
| 128 |
+
}
|
| 129 |
+
if (key === 't') {
|
| 130 |
+
event.preventDefault();
|
| 131 |
+
dispatch({ type: EVENT_TYPES.OPEN_TERMINAL });
|
| 132 |
+
return;
|
| 133 |
+
}
|
| 134 |
+
if (key === 'i') {
|
| 135 |
+
event.preventDefault();
|
| 136 |
+
dispatch({ type: EVENT_TYPES.OPEN_INSPECTOR });
|
| 137 |
+
return;
|
| 138 |
+
}
|
| 139 |
+
if (/^[1-8]$/.test(event.key)) {
|
| 140 |
+
event.preventDefault();
|
| 141 |
+
useToolByKey(event.key);
|
| 142 |
+
return;
|
| 143 |
+
}
|
| 144 |
+
if (event.key === 'Escape') {
|
| 145 |
+
dispatch(state.terminal.open ? { type: EVENT_TYPES.CLOSE_TERMINAL } : { type: EVENT_TYPES.CLOSE_INSPECTOR });
|
| 146 |
+
}
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
bindUi(app, dispatch);
|
| 150 |
+
app.addEventListener('keydown', onKeyDown);
|
| 151 |
+
window.addEventListener('resize', render);
|
| 152 |
+
window.gitWorldState = state;
|
| 153 |
+
render();
|
| 154 |
+
setupBrowserScene();
|
| 155 |
+
|
| 156 |
+
// Fixed-step browser-local simulation: the world advances on a deterministic clock,
|
| 157 |
+
// while the render layer simply reflects the latest serializable state.
|
| 158 |
+
window.setInterval(() => {
|
| 159 |
+
const advanced = advanceWorldState(state);
|
| 160 |
+
state = advanced.state;
|
| 161 |
+
window.gitWorldState = state;
|
| 162 |
+
render();
|
| 163 |
+
}, 1600);
|
| 164 |
+
|
| 165 |
+
// Keep the transcript helper reachable for the UI's delegated terminal buttons in
|
| 166 |
+
// environments where the app is embedded without a module bundler.
|
| 167 |
+
window.gitWorldCommandTranscript = commandTranscript;
|
dist/phase2-unreal/ReferenceSource/NeonReflections/3087.webp
ADDED
|
Git LFS Details
|
dist/phase2-unreal/ReferenceSource/NeonReflections/3089.webp
ADDED
|
Git LFS Details
|
dist/phase2-unreal/ReferenceSource/NeonReflections/3090.webp
ADDED
|
Git LFS Details
|
dist/phase2-unreal/ReferenceSource/NeonReflections/3092.jpg
ADDED
|
Git LFS Details
|
dist/phase2-unreal/ReferenceSource/Opening/3099.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:eaaea396db131ac4681bd51cb4dc258ba7cae15efeebab77d752e335ca486ae0
|
| 3 |
+
size 5190235
|
dist/phase2-unreal/ReferenceSource/README.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GitWorld Phase 3 reference set
|
| 2 |
+
|
| 3 |
+
The four user-selected files in `NeonReflections/` are wired as visual reference plates for the `pax-coder` GitWorld sandbox. They establish the neon social-hub, portal, avatar, and VR interface look for the Unreal scene.
|
| 4 |
+
|
| 5 |
+
They are intentionally not passed to the photogrammetry reconstruction script: the frames are generated/stylized renders from different compositions, not a camera sweep around one real subject. RealityCapture would need a separate folder of overlapping photographs to produce a valid 3D mesh.
|
| 6 |
+
|
| 7 |
+
The Unreal Editor script imports the plates, creates unlit display materials, places them on vertical gallery planes around the repository world, and assigns the imported textures to the world actor’s `ReferenceImages` property. This gives the project a visible scene using the supplied art while preserving the separate true-photogrammetry path.
|
dist/phase2-unreal/ReferenceSource/manifest.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"referenceSetId": "neon-reflections-v15",
|
| 3 |
+
"repositoryWorld": "pax-coder",
|
| 4 |
+
"sourceType": "visual-reference-plates",
|
| 5 |
+
"photogrammetryReady": false,
|
| 6 |
+
"reason": "The supplied frames depict different stylized scenes and a character turntable; they are not overlapping views of one physical location.",
|
| 7 |
+
"openingScene": {
|
| 8 |
+
"file": "Opening/3099.mp4",
|
| 9 |
+
"role": "gitworld-opening-cinematic",
|
| 10 |
+
"autoplay": true
|
| 11 |
+
},
|
| 12 |
+
"assets": [
|
| 13 |
+
{
|
| 14 |
+
"file": "3087.webp",
|
| 15 |
+
"role": "arcade-portal-reference",
|
| 16 |
+
"placement": "east-gallery"
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"file": "3089.webp",
|
| 20 |
+
"role": "social-hub-pov-reference",
|
| 21 |
+
"placement": "north-gallery"
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"file": "3090.webp",
|
| 25 |
+
"role": "social-hub-spawn-reference",
|
| 26 |
+
"placement": "west-gallery"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "3092.jpg",
|
| 30 |
+
"role": "avatar-turntable-reference",
|
| 31 |
+
"placement": "avatar-gallery"
|
| 32 |
+
}
|
| 33 |
+
]
|
| 34 |
+
}
|
dist/shared.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export const EVENT_TYPES = Object.freeze({
|
| 2 |
+
PLAYER_MOVE: 'player.move',
|
| 3 |
+
SELECT_DISTRICT: 'district.selected',
|
| 4 |
+
SELECT_FILE: 'file.selected',
|
| 5 |
+
SELECT_COLLECTION: 'collection.selected',
|
| 6 |
+
SELECT_RECORD: 'record.selected',
|
| 7 |
+
OPEN_INSPECTOR: 'inspector.opened',
|
| 8 |
+
CLOSE_INSPECTOR: 'inspector.closed',
|
| 9 |
+
OPEN_TERMINAL: 'terminal.opened',
|
| 10 |
+
CLOSE_TERMINAL: 'terminal.closed',
|
| 11 |
+
RUN_COMMAND: 'terminal.command',
|
| 12 |
+
USE_TOOL: 'tool.used',
|
| 13 |
+
ENTER_DISTRICT: 'district.entered',
|
| 14 |
+
TICK: 'world.tick',
|
| 15 |
+
AGENT_PROGRESS: 'agent.progress',
|
| 16 |
+
CONSTRUCTION: 'construction.updated',
|
| 17 |
+
BUILD_STARTED: 'build.started',
|
| 18 |
+
BUILD_COMPLETED: 'build.completed',
|
| 19 |
+
TEST_STARTED: 'test.started',
|
| 20 |
+
TEST_COMPLETED: 'test.completed',
|
| 21 |
+
DEPLOYMENT_STARTED: 'deployment.started',
|
| 22 |
+
DEPLOYMENT_COMPLETED: 'deployment.completed',
|
| 23 |
+
COMMIT: 'repository.commit',
|
| 24 |
+
ISSUE: 'repository.issue',
|
| 25 |
+
PULL_REQUEST: 'repository.pull_request'
|
| 26 |
+
});
|
| 27 |
+
|
| 28 |
+
export const TOOLBELT = Object.freeze([
|
| 29 |
+
{ id: 'terminal', key: '1', icon: '▣', name: 'Terminal Phone', verb: 'Open shell', color: '#69f6cf' },
|
| 30 |
+
{ id: 'bash', key: '2', icon: '⚒', name: 'Bash Hammer', verb: 'Run operation', color: '#ffbd69' },
|
| 31 |
+
{ id: 'git', key: '3', icon: '◈', name: 'Git Backpack', verb: 'View history', color: '#ff7ac8' },
|
| 32 |
+
{ id: 'docker', key: '4', icon: '▦', name: 'Docker Box', verb: 'Open container', color: '#6dafff' },
|
| 33 |
+
{ id: 'code', key: '5', icon: '⌘', name: 'Code Tablet', verb: 'Read source', color: '#b79cff' },
|
| 34 |
+
{ id: 'ssh', key: '6', icon: '⚿', name: 'SSH Key', verb: 'Unlock gate', color: '#ffdd67' },
|
| 35 |
+
{ id: 'database', key: '7', icon: '▤', name: 'Database Vault', verb: 'Inspect data', color: '#9fe870' },
|
| 36 |
+
{ id: 'deploy', key: '8', icon: '↗', name: 'Deploy Rocket', verb: 'Launch build', color: '#ff8b62' }
|
| 37 |
+
]);
|
| 38 |
+
|
| 39 |
+
export const EVENT_LABELS = Object.freeze({
|
| 40 |
+
[EVENT_TYPES.AGENT_PROGRESS]: 'agent progress',
|
| 41 |
+
[EVENT_TYPES.CONSTRUCTION]: 'construction update',
|
| 42 |
+
[EVENT_TYPES.BUILD_STARTED]: 'build started',
|
| 43 |
+
[EVENT_TYPES.BUILD_COMPLETED]: 'build completed',
|
| 44 |
+
[EVENT_TYPES.TEST_STARTED]: 'tests started',
|
| 45 |
+
[EVENT_TYPES.TEST_COMPLETED]: 'tests passed',
|
| 46 |
+
[EVENT_TYPES.DEPLOYMENT_STARTED]: 'deployment queued',
|
| 47 |
+
[EVENT_TYPES.DEPLOYMENT_COMPLETED]: 'deployment complete',
|
| 48 |
+
[EVENT_TYPES.COMMIT]: 'commit landed',
|
| 49 |
+
[EVENT_TYPES.ISSUE]: 'issue quest',
|
| 50 |
+
[EVENT_TYPES.PULL_REQUEST]: 'pull request gate'
|
| 51 |
+
});
|
| 52 |
+
|
| 53 |
+
export const COLLECTIONS = Object.freeze(['files', 'commits', 'issues', 'prs', 'build']);
|
| 54 |
+
|
| 55 |
+
export const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
| 56 |
+
|
| 57 |
+
export function formatTime(tick) {
|
| 58 |
+
const seconds = String((tick * 7) % 60).padStart(2, '0');
|
| 59 |
+
const minutes = String(Math.floor(tick * 7 / 60)).padStart(2, '0');
|
| 60 |
+
return `00:${minutes}:${seconds}`;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
export function clone(value) {
|
| 64 |
+
return JSON.parse(JSON.stringify(value));
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
export function makeEvent(type, tick, detail = {}) {
|
| 68 |
+
return {
|
| 69 |
+
id: `${type}-${tick}`,
|
| 70 |
+
type,
|
| 71 |
+
tick,
|
| 72 |
+
at: formatTime(tick),
|
| 73 |
+
detail
|
| 74 |
+
};
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
export function commandTranscript(command, state) {
|
| 78 |
+
const prompt = '$';
|
| 79 |
+
const selected = state.selectedFileId || 'README.md';
|
| 80 |
+
const lines = {
|
| 81 |
+
ls: ['src/', 'tests/', 'docs/', '.github/', 'Cargo.toml', 'README.md'],
|
| 82 |
+
'git status': ['On branch main', 'Your branch is up to date with origin/main.', 'working tree clean'],
|
| 83 |
+
'git log': ['8f3a9c1 Fix kernel bounds', '2h4d7e2 Add proof sketch', '9ac1bd9 Update docs'],
|
| 84 |
+
'cargo test': ['Compiling pax-coder v0.8.2', 'running 142 tests', 'test result: ok. 142 passed; 0 failed'],
|
| 85 |
+
'./build.sh': ['[world] assembling repository tower', '[ci] verifying artifacts', 'BUILD #812 completed successfully'],
|
| 86 |
+
help: ['Try: ls, git status, git log, cargo test, ./build.sh']
|
| 87 |
+
};
|
| 88 |
+
const output = lines[command] || [`command not found: ${command}`, 'Try `help` for local commands.'];
|
| 89 |
+
return [`${prompt} ${command}`, ...output, `${prompt} _`];
|
| 90 |
+
}
|
dist/src/main.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { EVENT_TYPES, TOOLBELT, commandTranscript } from './shared.js';
|
| 2 |
+
import {
|
| 3 |
+
advanceWorldState,
|
| 4 |
+
createWorldState,
|
| 5 |
+
getNearbyDistrict,
|
| 6 |
+
renderMinimap,
|
| 7 |
+
renderWorld,
|
| 8 |
+
worldReducer
|
| 9 |
+
} from './world-architect.js';
|
| 10 |
+
import { bindUi, renderHud } from './ui-reality.js';
|
| 11 |
+
|
| 12 |
+
const app = document;
|
| 13 |
+
const worldViewport = app.getElementById('world-viewport');
|
| 14 |
+
const minimap = app.getElementById('minimap-viewport');
|
| 15 |
+
let state = createWorldState();
|
| 16 |
+
|
| 17 |
+
function dispatch(event) {
|
| 18 |
+
state = worldReducer(state, event);
|
| 19 |
+
window.gitWorldState = state;
|
| 20 |
+
render();
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
function render() {
|
| 24 |
+
const nearby = getNearbyDistrict(state);
|
| 25 |
+
if (worldViewport) renderWorld(worldViewport, state, (districtId) => dispatch({ type: EVENT_TYPES.SELECT_DISTRICT, districtId }));
|
| 26 |
+
if (minimap) renderMinimap(minimap, state);
|
| 27 |
+
renderHud(app, state, nearby);
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
function move(dx, dy) {
|
| 31 |
+
dispatch({ type: EVENT_TYPES.PLAYER_MOVE, dx, dy });
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
function useToolByKey(key) {
|
| 35 |
+
const tool = TOOLBELT.find((candidate) => candidate.key === key);
|
| 36 |
+
if (!tool) return;
|
| 37 |
+
dispatch({ type: EVENT_TYPES.USE_TOOL, toolId: tool.id, message: `${tool.name}: ${tool.verb}.` });
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
function inspectNearest() {
|
| 41 |
+
const nearby = getNearbyDistrict(state);
|
| 42 |
+
if (nearby.inRange) {
|
| 43 |
+
dispatch({ type: EVENT_TYPES.ENTER_DISTRICT, districtId: nearby.district.id });
|
| 44 |
+
} else {
|
| 45 |
+
dispatch({ type: EVENT_TYPES.SELECT_DISTRICT, districtId: nearby.district.id });
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
function onKeyDown(event) {
|
| 50 |
+
const target = event.target;
|
| 51 |
+
const typing = target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement;
|
| 52 |
+
if (typing && event.key !== 'Escape') return;
|
| 53 |
+
const key = event.key.toLowerCase();
|
| 54 |
+
const movement = {
|
| 55 |
+
w: [0, -1], arrowup: [0, -1],
|
| 56 |
+
s: [0, 1], arrowdown: [0, 1],
|
| 57 |
+
a: [-1, 0], arrowleft: [-1, 0],
|
| 58 |
+
d: [1, 0], arrowright: [1, 0]
|
| 59 |
+
}[key];
|
| 60 |
+
if (movement) {
|
| 61 |
+
event.preventDefault();
|
| 62 |
+
move(movement[0], movement[1]);
|
| 63 |
+
return;
|
| 64 |
+
}
|
| 65 |
+
if (key === 'e' || event.key === 'Enter') {
|
| 66 |
+
event.preventDefault();
|
| 67 |
+
inspectNearest();
|
| 68 |
+
return;
|
| 69 |
+
}
|
| 70 |
+
if (key === 't') {
|
| 71 |
+
event.preventDefault();
|
| 72 |
+
dispatch({ type: EVENT_TYPES.OPEN_TERMINAL });
|
| 73 |
+
return;
|
| 74 |
+
}
|
| 75 |
+
if (key === 'i') {
|
| 76 |
+
event.preventDefault();
|
| 77 |
+
dispatch({ type: EVENT_TYPES.OPEN_INSPECTOR });
|
| 78 |
+
return;
|
| 79 |
+
}
|
| 80 |
+
if (/^[1-8]$/.test(event.key)) {
|
| 81 |
+
event.preventDefault();
|
| 82 |
+
useToolByKey(event.key);
|
| 83 |
+
return;
|
| 84 |
+
}
|
| 85 |
+
if (event.key === 'Escape') {
|
| 86 |
+
dispatch(state.terminal.open ? { type: EVENT_TYPES.CLOSE_TERMINAL } : { type: EVENT_TYPES.CLOSE_INSPECTOR });
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
bindUi(app, dispatch);
|
| 91 |
+
app.addEventListener('keydown', onKeyDown);
|
| 92 |
+
window.addEventListener('resize', render);
|
| 93 |
+
window.gitWorldState = state;
|
| 94 |
+
render();
|
| 95 |
+
|
| 96 |
+
// Fixed-step browser-local simulation: the world advances on a deterministic clock,
|
| 97 |
+
// while the render layer simply reflects the latest serializable state.
|
| 98 |
+
window.setInterval(() => {
|
| 99 |
+
const advanced = advanceWorldState(state);
|
| 100 |
+
state = advanced.state;
|
| 101 |
+
window.gitWorldState = state;
|
| 102 |
+
render();
|
| 103 |
+
}, 1600);
|
| 104 |
+
|
| 105 |
+
// Keep the transcript helper reachable for the UI's delegated terminal buttons in
|
| 106 |
+
// environments where the app is embedded without a module bundler.
|
| 107 |
+
window.gitWorldCommandTranscript = commandTranscript;
|
dist/src/shared.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export const EVENT_TYPES = Object.freeze({
|
| 2 |
+
PLAYER_MOVE: 'player.move',
|
| 3 |
+
SELECT_DISTRICT: 'district.selected',
|
| 4 |
+
SELECT_FILE: 'file.selected',
|
| 5 |
+
SELECT_COLLECTION: 'collection.selected',
|
| 6 |
+
SELECT_RECORD: 'record.selected',
|
| 7 |
+
OPEN_INSPECTOR: 'inspector.opened',
|
| 8 |
+
CLOSE_INSPECTOR: 'inspector.closed',
|
| 9 |
+
OPEN_TERMINAL: 'terminal.opened',
|
| 10 |
+
CLOSE_TERMINAL: 'terminal.closed',
|
| 11 |
+
RUN_COMMAND: 'terminal.command',
|
| 12 |
+
USE_TOOL: 'tool.used',
|
| 13 |
+
ENTER_DISTRICT: 'district.entered',
|
| 14 |
+
TICK: 'world.tick',
|
| 15 |
+
AGENT_PROGRESS: 'agent.progress',
|
| 16 |
+
CONSTRUCTION: 'construction.updated',
|
| 17 |
+
BUILD_STARTED: 'build.started',
|
| 18 |
+
BUILD_COMPLETED: 'build.completed',
|
| 19 |
+
TEST_STARTED: 'test.started',
|
| 20 |
+
TEST_COMPLETED: 'test.completed',
|
| 21 |
+
DEPLOYMENT_STARTED: 'deployment.started',
|
| 22 |
+
DEPLOYMENT_COMPLETED: 'deployment.completed',
|
| 23 |
+
COMMIT: 'repository.commit',
|
| 24 |
+
ISSUE: 'repository.issue',
|
| 25 |
+
PULL_REQUEST: 'repository.pull_request'
|
| 26 |
+
});
|
| 27 |
+
|
| 28 |
+
export const TOOLBELT = Object.freeze([
|
| 29 |
+
{ id: 'terminal', key: '1', icon: '▣', name: 'Terminal Phone', verb: 'Open shell', color: '#69f6cf' },
|
| 30 |
+
{ id: 'bash', key: '2', icon: '⚒', name: 'Bash Hammer', verb: 'Run operation', color: '#ffbd69' },
|
| 31 |
+
{ id: 'git', key: '3', icon: '◈', name: 'Git Backpack', verb: 'View history', color: '#ff7ac8' },
|
| 32 |
+
{ id: 'docker', key: '4', icon: '▦', name: 'Docker Box', verb: 'Open container', color: '#6dafff' },
|
| 33 |
+
{ id: 'code', key: '5', icon: '⌘', name: 'Code Tablet', verb: 'Read source', color: '#b79cff' },
|
| 34 |
+
{ id: 'ssh', key: '6', icon: '⚿', name: 'SSH Key', verb: 'Unlock gate', color: '#ffdd67' },
|
| 35 |
+
{ id: 'database', key: '7', icon: '▤', name: 'Database Vault', verb: 'Inspect data', color: '#9fe870' },
|
| 36 |
+
{ id: 'deploy', key: '8', icon: '↗', name: 'Deploy Rocket', verb: 'Launch build', color: '#ff8b62' }
|
| 37 |
+
]);
|
| 38 |
+
|
| 39 |
+
export const EVENT_LABELS = Object.freeze({
|
| 40 |
+
[EVENT_TYPES.AGENT_PROGRESS]: 'agent progress',
|
| 41 |
+
[EVENT_TYPES.CONSTRUCTION]: 'construction update',
|
| 42 |
+
[EVENT_TYPES.BUILD_STARTED]: 'build started',
|
| 43 |
+
[EVENT_TYPES.BUILD_COMPLETED]: 'build completed',
|
| 44 |
+
[EVENT_TYPES.TEST_STARTED]: 'tests started',
|
| 45 |
+
[EVENT_TYPES.TEST_COMPLETED]: 'tests passed',
|
| 46 |
+
[EVENT_TYPES.DEPLOYMENT_STARTED]: 'deployment queued',
|
| 47 |
+
[EVENT_TYPES.DEPLOYMENT_COMPLETED]: 'deployment complete',
|
| 48 |
+
[EVENT_TYPES.COMMIT]: 'commit landed',
|
| 49 |
+
[EVENT_TYPES.ISSUE]: 'issue quest',
|
| 50 |
+
[EVENT_TYPES.PULL_REQUEST]: 'pull request gate'
|
| 51 |
+
});
|
| 52 |
+
|
| 53 |
+
export const COLLECTIONS = Object.freeze(['files', 'commits', 'issues', 'prs', 'build']);
|
| 54 |
+
|
| 55 |
+
export const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
| 56 |
+
|
| 57 |
+
export function formatTime(tick) {
|
| 58 |
+
const seconds = String((tick * 7) % 60).padStart(2, '0');
|
| 59 |
+
const minutes = String(Math.floor(tick * 7 / 60)).padStart(2, '0');
|
| 60 |
+
return `00:${minutes}:${seconds}`;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
export function clone(value) {
|
| 64 |
+
return JSON.parse(JSON.stringify(value));
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
export function makeEvent(type, tick, detail = {}) {
|
| 68 |
+
return {
|
| 69 |
+
id: `${type}-${tick}`,
|
| 70 |
+
type,
|
| 71 |
+
tick,
|
| 72 |
+
at: formatTime(tick),
|
| 73 |
+
detail
|
| 74 |
+
};
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
export function commandTranscript(command, state) {
|
| 78 |
+
const prompt = '$';
|
| 79 |
+
const selected = state.selectedFileId || 'README.md';
|
| 80 |
+
const lines = {
|
| 81 |
+
ls: ['src/', 'tests/', 'docs/', '.github/', 'Cargo.toml', 'README.md'],
|
| 82 |
+
'git status': ['On branch main', 'Your branch is up to date with origin/main.', 'working tree clean'],
|
| 83 |
+
'git log': ['8f3a9c1 Fix kernel bounds', '2h4d7e2 Add proof sketch', '9ac1bd9 Update docs'],
|
| 84 |
+
'cargo test': ['Compiling pax-coder v0.8.2', 'running 142 tests', 'test result: ok. 142 passed; 0 failed'],
|
| 85 |
+
'./build.sh': ['[world] assembling repository tower', '[ci] verifying artifacts', 'BUILD #812 completed successfully'],
|
| 86 |
+
help: ['Try: ls, git status, git log, cargo test, ./build.sh']
|
| 87 |
+
};
|
| 88 |
+
const output = lines[command] || [`command not found: ${command}`, 'Try `help` for local commands.'];
|
| 89 |
+
return [`${prompt} ${command}`, ...output, `${prompt} _`];
|
| 90 |
+
}
|
dist/src/ui-reality.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import {
|
| 2 |
+
COLLECTIONS,
|
| 3 |
+
EVENT_LABELS,
|
| 4 |
+
EVENT_TYPES,
|
| 5 |
+
TOOLBELT,
|
| 6 |
+
commandTranscript,
|
| 7 |
+
formatTime
|
| 8 |
+
} from './shared.js';
|
| 9 |
+
|
| 10 |
+
function escapeHtml(value) {
|
| 11 |
+
return String(value).replace(/[&<>"']/g, (character) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[character]));
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
function byId(root, id) {
|
| 15 |
+
return root.getElementById(id);
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
function setText(root, id, value) {
|
| 19 |
+
const element = byId(root, id);
|
| 20 |
+
if (element) element.textContent = value;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
function selectedDistrict(state) {
|
| 24 |
+
return state.districts.find((district) => district.id === state.selectedDistrictId) || state.districts[0];
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
function selectedFile(state) {
|
| 28 |
+
const district = selectedDistrict(state);
|
| 29 |
+
return district.files.find((file) => file.id === state.selectedFileId) || district.files[0];
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
function eventMessage(event) {
|
| 33 |
+
return event.detail?.message || EVENT_LABELS[event.type] || event.type;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
function renderAgents(root, state) {
|
| 37 |
+
const agentList = byId(root, 'agent-list');
|
| 38 |
+
if (!agentList) return;
|
| 39 |
+
agentList.innerHTML = state.agents.map((agent) => `<li class="agent-row" style="--agent-color:${agent.color}">
|
| 40 |
+
<span class="agent-avatar">${agent.name.slice(0, 2)}</span><span class="agent-copy"><strong>${escapeHtml(agent.name)}</strong><small>${escapeHtml(agent.role)}</small><em>${escapeHtml(agent.task)}</em><i class="progress-line"><b style="width:${agent.progress}%"></b></i></span><span class="agent-state">● ${escapeHtml(agent.status)}</span>
|
| 41 |
+
</li>`).join('');
|
| 42 |
+
setText(root, 'active-agent-count', String(state.agents.length));
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
function renderEvents(root, state) {
|
| 46 |
+
const feed = byId(root, 'event-feed');
|
| 47 |
+
if (!feed) return;
|
| 48 |
+
feed.innerHTML = state.events.slice(0, 7).map((event) => `<li class="event-row event-${event.type.replace(/[^a-z]+/g, '-')}" data-event-type="${escapeHtml(event.type)}"><time>${escapeHtml(event.at || formatTime(event.tick))}</time><span class="event-dot"></span><p>${escapeHtml(eventMessage(event))}</p></li>`).join('');
|
| 49 |
+
setText(root, 'event-count', `${state.events.length} EVENTS`);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
function renderToolbelt(root, state) {
|
| 53 |
+
const toolbelt = byId(root, 'toolbelt');
|
| 54 |
+
if (!toolbelt) return;
|
| 55 |
+
toolbelt.innerHTML = TOOLBELT.map((tool) => `<button class="tool-slot${tool.id === state.activeTool ? ' is-active' : ''}" data-action="tool" data-tool-id="${tool.id}" style="--tool-color:${tool.color}" aria-label="${escapeHtml(tool.name)}: ${escapeHtml(tool.verb)}"><span class="tool-key">${tool.key}</span><b>${tool.icon}</b><strong>${escapeHtml(tool.name)}</strong><small>${escapeHtml(tool.verb)}</small></button>`).join('');
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
function renderStatus(root, state) {
|
| 59 |
+
setText(root, 'repo-visibility', state.repository.visibility);
|
| 60 |
+
setText(root, 'repo-branch', `BRANCH / ${state.repository.branch}`);
|
| 61 |
+
setText(root, 'build-status', state.repository.build);
|
| 62 |
+
setText(root, 'test-status', state.repository.tests);
|
| 63 |
+
setText(root, 'deploy-status', state.repository.deployment);
|
| 64 |
+
setText(root, 'world-clock', formatTime(state.tick));
|
| 65 |
+
setText(root, 'player-location', state.view === 'district' ? `INSIDE ${selectedDistrict(state).path}` : 'SPAWN PLAZA');
|
| 66 |
+
setText(root, 'feedback-line', state.feedback);
|
| 67 |
+
const playerLevel = Math.min(99, 12 + Math.floor(state.tick / 3));
|
| 68 |
+
setText(root, 'player-level', `LEVEL ${playerLevel} DEV`);
|
| 69 |
+
const xp = Math.min(100, 34 + state.tick * 2);
|
| 70 |
+
const xpBar = byId(root, 'xp-fill');
|
| 71 |
+
if (xpBar) xpBar.style.width = `${xp}%`;
|
| 72 |
+
setText(root, 'xp-label', `${xp} / 100 XP`);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
function renderFiles(state) {
|
| 76 |
+
const district = selectedDistrict(state);
|
| 77 |
+
return `<div class="collection-heading"><span>DIRECTORY</span><strong>${escapeHtml(district.path)}</strong><small>${escapeHtml(district.stat)}</small></div><div class="file-list">${district.files.map((file) => `<button class="file-row${file.id === state.selectedFileId ? ' is-active' : ''}" data-action="select-file" data-file-id="${file.id}"><span class="file-icon">⌁</span><strong>${escapeHtml(file.name)}</strong><small>${escapeHtml(file.kind)} · ${escapeHtml(file.size)}</small><b>›</b></button>`).join('')}</div>`;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
function renderCommits(state) {
|
| 81 |
+
const commits = state.repository.commits || [];
|
| 82 |
+
return `<div class="collection-heading"><span>HISTORY ROAD</span><strong>RECENT COMMITS</strong><small>${commits.length} simulated events</small></div><div class="commit-list">${commits.map((commit) => `<button class="commit-row" data-action="commit-detail" data-commit-id="${commit.id}"><span class="commit-node"></span><span><strong>${escapeHtml(commit.hash)}</strong><small>${escapeHtml(commit.message)}</small></span><em>${escapeHtml(commit.author)}</em></button>`).join('')}</div>`;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
function renderIssues(state) {
|
| 86 |
+
const issues = state.repository.issues || [];
|
| 87 |
+
return `<div class="collection-heading"><span>QUEST BOARD</span><strong>OPEN ISSUES</strong><small>Quests waiting in the city</small></div><div class="issue-list">${issues.map((issue) => `<button class="issue-row" data-action="issue-detail" data-issue-id="${issue.id}"><span class="issue-marker">!</span><span><strong>${escapeHtml(issue.title)}</strong><small>${escapeHtml(issue.detail)}</small></span><em>${escapeHtml(issue.status)}</em></button>`).join('')}</div>`;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
function renderPullRequests(state) {
|
| 91 |
+
const prs = state.repository.pullRequests || [];
|
| 92 |
+
return `<div class="collection-heading"><span>MERGE GATE</span><strong>PULL REQUESTS</strong><small>Doors into the next branch</small></div><div class="pr-list">${prs.map((pr) => `<button class="pr-row" data-action="pr-detail" data-pr-id="${pr.id}"><span class="pr-marker">↔</span><span><strong>${escapeHtml(pr.title)}</strong><small>${escapeHtml(pr.detail)}</small></span><em>${escapeHtml(pr.status)}</em></button>`).join('')}</div>`;
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
function renderBuild(state) {
|
| 96 |
+
return `<div class="collection-heading"><span>AUTOMATION CENTER</span><strong>BUILD & TEST</strong><small>All values are simulated browser-local signals</small></div><div class="build-stack"><div><span>BUILD #812</span><b class="status-${state.repository.build.toLowerCase()}">${escapeHtml(state.repository.build)}</b></div><div><span>INSPECTORS</span><b class="status-${state.repository.tests.toLowerCase().split(' ')[0]}">${escapeHtml(state.repository.tests)}</b></div><div><span>PAGES RUNWAY</span><b>${escapeHtml(state.repository.deployment)}</b></div><div class="pipeline-steps"><i class="done">SOURCE</i><i class="done">COMPILE</i><i class="done">TEST</i><i class="active">DEPLOY</i></div></div>`;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
function renderRecord(state) {
|
| 100 |
+
const record = state.selectedRecord;
|
| 101 |
+
if (!record) return renderBuild(state);
|
| 102 |
+
if (record.type === 'commit') {
|
| 103 |
+
const item = (state.repository.commits || []).find((candidate) => candidate.id === record.id);
|
| 104 |
+
return `<div class="collection-heading"><span>COMMIT EVENT</span><strong>${escapeHtml(item?.hash || record.id)}</strong><small>Historical event made physical</small></div><div class="record-card"><b>${escapeHtml(item?.message || 'Commit selected')}</b><p>This simulated commit is a construction event. The repository builder routes its changed files into the city.</p><em>author · ${escapeHtml(item?.author || 'builder')}</em></div><button class="panel-action" data-action="select-collection" data-collection="commits">← Back to commit history</button>`;
|
| 105 |
+
}
|
| 106 |
+
if (record.type === 'issue') {
|
| 107 |
+
const item = (state.repository.issues || []).find((candidate) => candidate.id === record.id);
|
| 108 |
+
return `<div class="collection-heading"><span>ISSUE QUEST</span><strong>${escapeHtml(item?.title || record.id)}</strong><small>${escapeHtml(item?.status || 'OPEN')}</small></div><div class="record-card issue-record"><b>Quest objective</b><p>${escapeHtml(item?.detail || 'Inspect the open issue in the quest district.')}</p><em>marker active · browser-local simulation</em></div><button class="panel-action" data-action="select-collection" data-collection="issues">← Back to issue board</button>`;
|
| 109 |
+
}
|
| 110 |
+
const item = (state.repository.pullRequests || []).find((candidate) => candidate.id === record.id);
|
| 111 |
+
return `<div class="collection-heading"><span>PULL REQUEST DOOR</span><strong>${escapeHtml(item?.title || record.id)}</strong><small>${escapeHtml(item?.status || 'REVIEW')}</small></div><div class="record-card pr-record"><b>Merge gate</b><p>${escapeHtml(item?.detail || 'The gate is waiting for review.')}</p><em>checks passing · browser-local simulation</em></div><button class="panel-action" data-action="select-collection" data-collection="prs">← Back to pull requests</button>`;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
function renderInspector(root, state, nearby) {
|
| 115 |
+
const panel = byId(root, 'inspector-content');
|
| 116 |
+
if (!panel) return;
|
| 117 |
+
const district = selectedDistrict(state);
|
| 118 |
+
const file = selectedFile(state);
|
| 119 |
+
const collection = state.selectedCollection;
|
| 120 |
+
let content = '';
|
| 121 |
+
if (state.activePanel === 'file' || (state.activePanel === 'collection' && collection === 'files')) {
|
| 122 |
+
content = `<div class="inspect-header"><span class="eyebrow">${escapeHtml(file.kind)} OBJECT</span><h2>${escapeHtml(file.name)}</h2><p>${escapeHtml(file.detail)}</p></div><pre class="code-preview" aria-label="Selected file preview">${escapeHtml(file.code)}</pre><button class="panel-action" data-action="select-collection" data-collection="files">← Back to ${escapeHtml(district.path)}</button>`;
|
| 123 |
+
} else if (state.activePanel === 'record') {
|
| 124 |
+
content = renderRecord(state);
|
| 125 |
+
} else if (state.activePanel === 'collection') {
|
| 126 |
+
content = collection === 'commits' ? renderCommits(state) : collection === 'issues' ? renderIssues(state) : collection === 'prs' ? renderPullRequests(state) : renderBuild(state);
|
| 127 |
+
} else if (state.activePanel === 'inspector' || state.activePanel === 'world') {
|
| 128 |
+
const canEnter = nearby?.district?.id === district.id && nearby.inRange;
|
| 129 |
+
content = `<div class="inspect-header"><span class="eyebrow">${state.view === 'district' ? 'CURRENT DISTRICT' : 'NEAREST DISTRICT'}</span><h2>${escapeHtml(district.title)}</h2><p>${escapeHtml(district.summary)}</p><div class="district-metrics"><span>${escapeHtml(district.path)}</span><span>${escapeHtml(district.stat)}</span><span style="color:${district.accent}">${Math.round((state.construction[district.id] || 0) * 100)}% BUILT</span></div></div><div class="inspector-actions"><button class="panel-action primary" data-action="open-inspector">INSPECT FILES</button><button class="panel-action" data-action="enter-district" ${canEnter ? '' : 'disabled'}>${canEnter ? 'ENTER DISTRICT' : 'WALK CLOSER TO ENTER'}</button></div><div class="mini-file-list">${district.files.slice(0, 3).map((item) => `<button data-action="select-file" data-file-id="${item.id}"><span>⌘</span>${escapeHtml(item.name)}<b>›</b></button>`).join('')}</div>`;
|
| 130 |
+
} else {
|
| 131 |
+
content = `<div class="inspect-header"><span class="eyebrow">${escapeHtml(district.path)}</span><h2>Repository objects</h2><p>Select a building, tool, or activity to inspect the physical software around you.</p></div>`;
|
| 132 |
+
}
|
| 133 |
+
panel.innerHTML = content;
|
| 134 |
+
const nearbyPrompt = byId(root, 'nearby-prompt');
|
| 135 |
+
if (nearbyPrompt) {
|
| 136 |
+
nearbyPrompt.innerHTML = nearby?.district && nearby.inRange ? `<span>NEARBY</span><strong>${escapeHtml(nearby.district.name)}</strong><small>PRESS E / ENTER TO ENTER</small>` : `<span>WALKABLE CITY</span><strong>${escapeHtml(state.player.facing.toUpperCase())}</strong><small>WASD · ARROWS TO NAVIGATE</small>`;
|
| 137 |
+
nearbyPrompt.classList.toggle('is-ready', Boolean(nearby?.inRange));
|
| 138 |
+
}
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
function renderTerminal(root, state) {
|
| 142 |
+
const overlay = byId(root, 'terminal-phone');
|
| 143 |
+
if (!overlay) return;
|
| 144 |
+
overlay.classList.toggle('is-open', state.terminal.open);
|
| 145 |
+
overlay.setAttribute('aria-hidden', String(!state.terminal.open));
|
| 146 |
+
const output = byId(root, 'terminal-output');
|
| 147 |
+
if (output) output.innerHTML = state.terminal.lines.map((line) => `<div class="terminal-line${line.startsWith('$') ? ' command' : ''}">${escapeHtml(line)}</div>`).join('');
|
| 148 |
+
const input = byId(root, 'terminal-input');
|
| 149 |
+
if (input && input.value !== state.terminal.command) input.value = '';
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
function renderSearch(root) {
|
| 153 |
+
const search = byId(root, 'repo-search');
|
| 154 |
+
if (!search) return;
|
| 155 |
+
const query = search.value.trim().toLowerCase();
|
| 156 |
+
root.querySelectorAll('.world-building').forEach((building) => {
|
| 157 |
+
building.classList.toggle('is-search-hidden', Boolean(query) && !building.textContent.toLowerCase().includes(query));
|
| 158 |
+
});
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
export function renderHud(root, state, nearby) {
|
| 162 |
+
renderAgents(root, state);
|
| 163 |
+
renderEvents(root, state);
|
| 164 |
+
renderToolbelt(root, state);
|
| 165 |
+
renderStatus(root, state);
|
| 166 |
+
renderInspector(root, state, nearby);
|
| 167 |
+
renderTerminal(root, state);
|
| 168 |
+
renderSearch(root);
|
| 169 |
+
const drawer = byId(root, 'inspector-drawer');
|
| 170 |
+
if (drawer) drawer.classList.toggle('is-open', state.activePanel !== 'world' && state.activePanel !== 'terminal');
|
| 171 |
+
const shell = byId(root, 'game-shell');
|
| 172 |
+
if (shell) shell.dataset.view = state.view;
|
| 173 |
+
setText(root, 'selected-tool-label', (TOOLBELT.find((tool) => tool.id === state.activeTool) || TOOLBELT[0]).name);
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
export function bindUi(root, dispatch) {
|
| 177 |
+
root.addEventListener('click', (event) => {
|
| 178 |
+
const target = event.target.closest('[data-action], [data-district-id]');
|
| 179 |
+
if (!target) return;
|
| 180 |
+
const action = target.dataset.action || (target.dataset.districtId ? 'select-district' : '');
|
| 181 |
+
if (action === 'select-district') dispatch({ type: EVENT_TYPES.SELECT_DISTRICT, districtId: target.dataset.districtId });
|
| 182 |
+
if (action === 'open-inspector') dispatch({ type: EVENT_TYPES.OPEN_INSPECTOR });
|
| 183 |
+
if (action === 'enter-district') dispatch({ type: EVENT_TYPES.ENTER_DISTRICT, districtId: target.dataset.districtId });
|
| 184 |
+
if (action === 'close-inspector') dispatch({ type: EVENT_TYPES.CLOSE_INSPECTOR });
|
| 185 |
+
if (action === 'select-file') dispatch({ type: EVENT_TYPES.SELECT_FILE, fileId: target.dataset.fileId });
|
| 186 |
+
if (action === 'select-collection') dispatch({ type: EVENT_TYPES.SELECT_COLLECTION, collection: target.dataset.collection });
|
| 187 |
+
if (action === 'commit-detail') dispatch({ type: EVENT_TYPES.SELECT_RECORD, recordType: 'commit', recordId: target.dataset.commitId });
|
| 188 |
+
if (action === 'issue-detail') dispatch({ type: EVENT_TYPES.SELECT_RECORD, recordType: 'issue', recordId: target.dataset.issueId });
|
| 189 |
+
if (action === 'pr-detail') dispatch({ type: EVENT_TYPES.SELECT_RECORD, recordType: 'pr', recordId: target.dataset.prId });
|
| 190 |
+
if (action === 'move') dispatch({ type: EVENT_TYPES.PLAYER_MOVE, dx: target.dataset.dx, dy: target.dataset.dy });
|
| 191 |
+
if (action === 'tool') {
|
| 192 |
+
const tool = TOOLBELT.find((item) => item.id === target.dataset.toolId);
|
| 193 |
+
dispatch({ type: EVENT_TYPES.USE_TOOL, toolId: target.dataset.toolId, message: tool?.verb ? `${tool.name}: ${tool.verb}.` : undefined });
|
| 194 |
+
}
|
| 195 |
+
if (action === 'open-terminal') dispatch({ type: EVENT_TYPES.OPEN_TERMINAL });
|
| 196 |
+
if (action === 'close-terminal') dispatch({ type: EVENT_TYPES.CLOSE_TERMINAL });
|
| 197 |
+
if (action === 'command') dispatch({ type: EVENT_TYPES.RUN_COMMAND, command: target.dataset.command, lines: commandTranscript(target.dataset.command, window.gitWorldState || { selectedFileId: 'README.md' }) });
|
| 198 |
+
});
|
| 199 |
+
|
| 200 |
+
root.addEventListener('submit', (event) => {
|
| 201 |
+
if (!event.target.matches('#terminal-form')) return;
|
| 202 |
+
event.preventDefault();
|
| 203 |
+
const input = byId(root, 'terminal-input');
|
| 204 |
+
const command = input?.value.trim() || '';
|
| 205 |
+
if (command) dispatch({ type: EVENT_TYPES.RUN_COMMAND, command, lines: commandTranscript(command, window.gitWorldState || { selectedFileId: 'README.md' }) });
|
| 206 |
+
});
|
| 207 |
+
|
| 208 |
+
const search = byId(root, 'repo-search');
|
| 209 |
+
if (search) search.addEventListener('input', () => renderSearch(root));
|
| 210 |
+
}
|
dist/src/world-architect.js
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import {
|
| 2 |
+
clamp,
|
| 3 |
+
clone,
|
| 4 |
+
EVENT_TYPES,
|
| 5 |
+
makeEvent
|
| 6 |
+
} from './shared.js';
|
| 7 |
+
|
| 8 |
+
export const WALKABLE_BOUNDS = Object.freeze({ minX: 70, maxX: 1530, minY: 90, maxY: 850 });
|
| 9 |
+
|
| 10 |
+
export const DISTRICTS = Object.freeze([
|
| 11 |
+
{
|
| 12 |
+
id: 'src',
|
| 13 |
+
name: 'SRC DISTRICT',
|
| 14 |
+
path: '/src',
|
| 15 |
+
title: 'Central Kernel Tower',
|
| 16 |
+
glyph: '⌘',
|
| 17 |
+
accent: '#62f2d2',
|
| 18 |
+
x: 565,
|
| 19 |
+
y: 230,
|
| 20 |
+
width: 255,
|
| 21 |
+
height: 220,
|
| 22 |
+
summary: 'The living source tree: modules, kernel code, and the repository entry point.',
|
| 23 |
+
stat: '42 files · 213 commits',
|
| 24 |
+
files: [
|
| 25 |
+
{ id: 'main-rs', name: 'main.rs', kind: 'Rust', size: '8.4 KB', detail: 'Repository bootstrap and agent scheduler.', code: 'fn main() {\n let world = GitWorld::spawn();\n world.walk();\n}' },
|
| 26 |
+
{ id: 'kernel-cu', name: 'kernel.cu', kind: 'CUDA', size: '24.1 KB', detail: 'The compute floor is under construction.', code: '__global__ void kernel_step(Agent agent) {\n agent.advance();\n}' },
|
| 27 |
+
{ id: 'lib-rs', name: 'lib.rs', kind: 'Rust', size: '12.7 KB', detail: 'Shared repository primitives and event contracts.', code: 'pub mod events;\npub mod districts;\npub mod builders;' },
|
| 28 |
+
{ id: 'mod-rs', name: 'mod.rs', kind: 'Rust', size: '2.1 KB', detail: 'District module registry.', code: 'pub const DISTRICTS: &[&str] = &[\n "src", "tests", "docs"\n];' }
|
| 29 |
+
]
|
| 30 |
+
},
|
| 31 |
+
{
|
| 32 |
+
id: 'tests',
|
| 33 |
+
name: 'TEST DISTRICT',
|
| 34 |
+
path: '/tests',
|
| 35 |
+
title: 'Test Factory',
|
| 36 |
+
glyph: '✓',
|
| 37 |
+
accent: '#91e85e',
|
| 38 |
+
x: 230,
|
| 39 |
+
y: 370,
|
| 40 |
+
width: 235,
|
| 41 |
+
height: 185,
|
| 42 |
+
summary: 'Inspection robots verify each build before the city opens another street.',
|
| 43 |
+
stat: '142 passing · 0 failing',
|
| 44 |
+
files: [
|
| 45 |
+
{ id: 'world-test', name: 'world.test.js', kind: 'Test', size: '9.7 KB', detail: 'Deterministic movement and event checks.', code: 'assert.equal(world.tick, 12);\nassert.equal(events.length, 4);' },
|
| 46 |
+
{ id: 'contracts-test', name: 'contracts.test.js', kind: 'Test', size: '6.2 KB', detail: 'Shared UI/world event contract checks.', code: 'assert.ok(EVENT_TYPES.PLAYER_MOVE);\nassert.ok(EVENT_TYPES.BUILD_COMPLETED);' },
|
| 47 |
+
{ id: 'fixtures', name: 'fixtures/', kind: 'Directory', size: '18 items', detail: 'Small simulated repository fixtures.', code: 'fixture("pax-coder");' }
|
| 48 |
+
]
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
id: 'docs',
|
| 52 |
+
name: 'DOCS LIBRARY',
|
| 53 |
+
path: '/docs',
|
| 54 |
+
title: 'Documentation Library',
|
| 55 |
+
glyph: '▤',
|
| 56 |
+
accent: '#ffcf68',
|
| 57 |
+
x: 1085,
|
| 58 |
+
y: 245,
|
| 59 |
+
width: 240,
|
| 60 |
+
height: 205,
|
| 61 |
+
summary: 'A warm archive of architecture decisions, guides, and the city map legend.',
|
| 62 |
+
stat: '18 guides · 96% coverage',
|
| 63 |
+
files: [
|
| 64 |
+
{ id: 'architecture', name: 'architecture.md', kind: 'Markdown', size: '14.2 KB', detail: 'The frontend event-driven architecture.', code: '# GitWorld architecture\nInput -> simulation -> state -> HUD' },
|
| 65 |
+
{ id: 'contributing', name: 'contributing.md', kind: 'Markdown', size: '4.8 KB', detail: 'How builders add a district.', code: '1. Choose a street\n2. Add a contract\n3. Run the inspectors' },
|
| 66 |
+
{ id: 'readme', name: 'README.md', kind: 'Markdown', size: '8.3 KB', detail: 'The repository welcome sign.', code: '# pax-coder\nA simulated repository world.' }
|
| 67 |
+
]
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
id: 'issues',
|
| 71 |
+
name: 'ISSUE QUESTS',
|
| 72 |
+
path: '/issues',
|
| 73 |
+
title: 'Issue Quest Board',
|
| 74 |
+
glyph: '!',
|
| 75 |
+
accent: '#ff9f58',
|
| 76 |
+
x: 865,
|
| 77 |
+
y: 470,
|
| 78 |
+
width: 215,
|
| 79 |
+
height: 160,
|
| 80 |
+
summary: 'Open issues become quests with markers, owners, and a clear next action.',
|
| 81 |
+
stat: '3 open quests · 1 in progress',
|
| 82 |
+
files: [
|
| 83 |
+
{ id: 'issue-201', name: '#201 bug in proof', kind: 'Issue', size: 'open', detail: 'Reproduce the proof boundary on the kernel floor.', code: 'status: open\nowner: proof-agent' },
|
| 84 |
+
{ id: 'issue-202', name: '#202 add test', kind: 'Issue', size: 'open', detail: 'Add a regression test for agent movement.', code: 'status: open\nowner: test-agent' },
|
| 85 |
+
{ id: 'issue-203', name: '#203 docs update', kind: 'Issue', size: 'queued', detail: 'Document the deploy launch sequence.', code: 'status: queued\nowner: doc-agent' }
|
| 86 |
+
]
|
| 87 |
+
},
|
| 88 |
+
{
|
| 89 |
+
id: 'prs',
|
| 90 |
+
name: 'PR GATE',
|
| 91 |
+
path: '/pulls',
|
| 92 |
+
title: 'Pull Request Gate',
|
| 93 |
+
glyph: '↔',
|
| 94 |
+
accent: '#d37cff',
|
| 95 |
+
x: 600,
|
| 96 |
+
y: 475,
|
| 97 |
+
width: 210,
|
| 98 |
+
height: 180,
|
| 99 |
+
summary: 'A glowing merge door: review the change before opening the next branch road.',
|
| 100 |
+
stat: '2 open · review required',
|
| 101 |
+
files: [
|
| 102 |
+
{ id: 'pr-88', name: '#88 merge kernel fix', kind: 'Pull request', size: 'review', detail: 'Two approvals still needed before merge.', code: 'checks: passing\nreviews: 1 / 3' },
|
| 103 |
+
{ id: 'pr-91', name: '#91 docs refresh', kind: 'Pull request', size: 'ready', detail: 'Ready to merge after the documentation quest.', code: 'checks: passing\nreviews: 3 / 3' }
|
| 104 |
+
]
|
| 105 |
+
},
|
| 106 |
+
{
|
| 107 |
+
id: 'actions',
|
| 108 |
+
name: 'ACTIONS FACTORY',
|
| 109 |
+
path: '/.github',
|
| 110 |
+
title: 'Automation Center',
|
| 111 |
+
glyph: '⚙',
|
| 112 |
+
accent: '#66a9ff',
|
| 113 |
+
x: 1250,
|
| 114 |
+
y: 485,
|
| 115 |
+
width: 235,
|
| 116 |
+
height: 195,
|
| 117 |
+
summary: 'CI factories turn commits into tested artifacts and light the deploy runway.',
|
| 118 |
+
stat: 'BUILD #812 · passing',
|
| 119 |
+
files: [
|
| 120 |
+
{ id: 'ci-yml', name: 'ci.yml', kind: 'Actions', size: '5.2 KB', detail: 'Build, test, and artifact inspection pipeline.', code: 'jobs:\n build:\n test:\n deploy:' },
|
| 121 |
+
{ id: 'pages-yml', name: 'pages.yml', kind: 'Actions', size: '2.4 KB', detail: 'Static Pages deployment path.', code: 'deploy-pages: enabled' },
|
| 122 |
+
{ id: 'release', name: 'release.yml', kind: 'Actions', size: '3.0 KB', detail: 'Packages the next city snapshot.', code: 'tag: v0.8.2' }
|
| 123 |
+
]
|
| 124 |
+
}
|
| 125 |
+
]);
|
| 126 |
+
|
| 127 |
+
const INITIAL_AGENTS = [
|
| 128 |
+
{ id: 'world-architect', name: 'WORLD ARCHITECT', role: 'World geometry', task: 'Building /src district', status: 'BUILDING', progress: 78, color: '#62f2d2' },
|
| 129 |
+
{ id: 'repository-builder', name: 'REPOSITORY BUILDER', role: 'Repository mapping', task: 'Mapping 42 files', status: 'MAPPING', progress: 64, color: '#ffcf68' },
|
| 130 |
+
{ id: 'reality-integrator', name: 'REALITY/UI INTEGRATOR', role: 'Interaction layer', task: 'Connecting terminal', status: 'WIRING', progress: 52, color: '#d37cff' }
|
| 131 |
+
];
|
| 132 |
+
|
| 133 |
+
const INITIAL_EVENTS = [
|
| 134 |
+
{ id: 'seed-1', type: EVENT_TYPES.COMMIT, tick: 0, at: '00:00:00', detail: { message: '8f3a9c1 Fix kernel bounds', districtId: 'src' } },
|
| 135 |
+
{ id: 'seed-2', type: EVENT_TYPES.AGENT_PROGRESS, tick: 0, at: '00:00:00', detail: { message: 'WORLD ARCHITECT placed a new tower window.', agentId: 'world-architect' } },
|
| 136 |
+
{ id: 'seed-3', type: EVENT_TYPES.TEST_COMPLETED, tick: 0, at: '00:00:00', detail: { message: '142 inspectors passed the street check.', districtId: 'tests' } }
|
| 137 |
+
];
|
| 138 |
+
|
| 139 |
+
const SIMULATION_SEQUENCE = [
|
| 140 |
+
{ type: EVENT_TYPES.AGENT_PROGRESS, detail: { agentId: 'world-architect', message: 'WORLD ARCHITECT raised the /src tower scaffold.' } },
|
| 141 |
+
{ type: EVENT_TYPES.AGENT_PROGRESS, detail: { agentId: 'repository-builder', message: 'REPOSITORY BUILDER mapped kernel.cu into the compute floor.' } },
|
| 142 |
+
{ type: EVENT_TYPES.CONSTRUCTION, detail: { districtId: 'src', amount: 0.03, message: 'A new source window flickers online.' } },
|
| 143 |
+
{ type: EVENT_TYPES.TEST_STARTED, detail: { districtId: 'tests', message: 'Inspection robots started a deterministic street sweep.' } },
|
| 144 |
+
{ type: EVENT_TYPES.TEST_COMPLETED, detail: { districtId: 'tests', message: '142 tests passed. The test district lights turn green.' } },
|
| 145 |
+
{ type: EVENT_TYPES.BUILD_STARTED, detail: { districtId: 'actions', message: 'BUILD #813 started in the automation factory.' } },
|
| 146 |
+
{ type: EVENT_TYPES.AGENT_PROGRESS, detail: { agentId: 'reality-integrator', message: 'REALITY/UI INTEGRATOR wired a contextual inspect panel.' } },
|
| 147 |
+
{ type: EVENT_TYPES.BUILD_COMPLETED, detail: { districtId: 'actions', message: 'BUILD #813 completed and artifacts are staged.' } },
|
| 148 |
+
{ type: EVENT_TYPES.COMMIT, detail: { districtId: 'src', message: '2h4d7e2 Add proof sketch' } },
|
| 149 |
+
{ type: EVENT_TYPES.ISSUE, detail: { districtId: 'issues', message: '#202 quest marker activated: add test' } },
|
| 150 |
+
{ type: EVENT_TYPES.PULL_REQUEST, detail: { districtId: 'prs', message: '#91 merge gate is ready for review.' } },
|
| 151 |
+
{ type: EVENT_TYPES.DEPLOYMENT_STARTED, detail: { districtId: 'actions', message: 'Deploy rocket is fueling on the Pages runway.' } },
|
| 152 |
+
{ type: EVENT_TYPES.DEPLOYMENT_COMPLETED, detail: { districtId: 'actions', message: 'Static city snapshot deployed to the local launch pad.' } }
|
| 153 |
+
];
|
| 154 |
+
|
| 155 |
+
export function createWorldState(seed = 20260828) {
|
| 156 |
+
return {
|
| 157 |
+
seed,
|
| 158 |
+
tick: 0,
|
| 159 |
+
repository: {
|
| 160 |
+
owner: 'SNAPKITTYWEST',
|
| 161 |
+
name: 'pax-coder',
|
| 162 |
+
branch: 'main',
|
| 163 |
+
visibility: 'Public',
|
| 164 |
+
simulated: true,
|
| 165 |
+
stars: 213,
|
| 166 |
+
forks: 45,
|
| 167 |
+
languages: ['Rust', 'CUDA', 'Markdown'],
|
| 168 |
+
build: 'PASSING',
|
| 169 |
+
tests: '142 PASS',
|
| 170 |
+
deployment: 'STAGED',
|
| 171 |
+
commits: [
|
| 172 |
+
{ id: '8f3a9c1', hash: '8f3a9c1', message: 'Fix kernel bounds', author: 'AhmadParr' },
|
| 173 |
+
{ id: '2h4d7e2', hash: '2h4d7e2', message: 'Add proof sketch', author: 'proof-agent' },
|
| 174 |
+
{ id: '9ac1bd9', hash: '9ac1bd9', message: 'Update docs', author: 'doc-agent' },
|
| 175 |
+
{ id: '7d0c812', hash: '7d0c812', message: 'Wire agent event bus', author: 'world-architect' }
|
| 176 |
+
],
|
| 177 |
+
issues: [
|
| 178 |
+
{ id: '201', title: '#201 Bug in proof', detail: 'Reproduce the proof boundary on the kernel floor.', status: 'OPEN' },
|
| 179 |
+
{ id: '202', title: '#202 Add test', detail: 'Add a regression test for agent movement.', status: 'OPEN' },
|
| 180 |
+
{ id: '203', title: '#203 Docs update', detail: 'Document the deploy launch sequence.', status: 'QUEUED' }
|
| 181 |
+
],
|
| 182 |
+
pullRequests: [
|
| 183 |
+
{ id: '88', title: '#88 Merge kernel fix', detail: 'Two approvals still needed before merge.', status: 'REVIEW' },
|
| 184 |
+
{ id: '91', title: '#91 Docs refresh', detail: 'Ready to merge after the documentation quest.', status: 'READY' }
|
| 185 |
+
]
|
| 186 |
+
},
|
| 187 |
+
districts: clone(DISTRICTS),
|
| 188 |
+
agents: clone(INITIAL_AGENTS),
|
| 189 |
+
events: clone(INITIAL_EVENTS),
|
| 190 |
+
construction: { src: 0.78, tests: 1, docs: 0.96, issues: 0.7, prs: 0.82, actions: 0.88 },
|
| 191 |
+
player: { x: 800, y: 735, speed: 18, facing: 'north', step: 0 },
|
| 192 |
+
selectedDistrictId: 'src',
|
| 193 |
+
selectedFileId: 'main-rs',
|
| 194 |
+
selectedCollection: 'files',
|
| 195 |
+
selectedRecord: null,
|
| 196 |
+
activeTool: 'terminal',
|
| 197 |
+
activePanel: 'world',
|
| 198 |
+
terminal: { open: false, lines: ['$ local shell connected', '$ type `help` for commands'], command: '' },
|
| 199 |
+
feedback: 'Walk toward a district or click a glowing building.',
|
| 200 |
+
unlocked: false,
|
| 201 |
+
view: 'city'
|
| 202 |
+
};
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
function pushEvent(state, event) {
|
| 206 |
+
state.events = [event, ...state.events].slice(0, 14);
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
function findDistrict(state, districtId) {
|
| 210 |
+
return state.districts.find((district) => district.id === districtId) || state.districts[0];
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
function selectedFile(state) {
|
| 214 |
+
const district = findDistrict(state, state.selectedDistrictId);
|
| 215 |
+
return district.files.find((file) => file.id === state.selectedFileId) || district.files[0];
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
function circlesOverlap(x, y, district, margin = 28) {
|
| 219 |
+
const left = district.x - margin;
|
| 220 |
+
const right = district.x + district.width + margin;
|
| 221 |
+
const top = district.y - margin;
|
| 222 |
+
const bottom = district.y + district.height + margin;
|
| 223 |
+
return x >= left && x <= right && y >= top && y <= bottom;
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
export function getNearbyDistrict(state, range = 185) {
|
| 227 |
+
let nearest = null;
|
| 228 |
+
let distance = Number.POSITIVE_INFINITY;
|
| 229 |
+
for (const district of state.districts) {
|
| 230 |
+
const centerX = district.x + district.width / 2;
|
| 231 |
+
const centerY = district.y + district.height / 2;
|
| 232 |
+
const nextDistance = Math.hypot(state.player.x - centerX, state.player.y - centerY);
|
| 233 |
+
if (nextDistance < distance) {
|
| 234 |
+
nearest = district;
|
| 235 |
+
distance = nextDistance;
|
| 236 |
+
}
|
| 237 |
+
}
|
| 238 |
+
return { district: nearest, distance, inRange: distance <= range };
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
export function worldReducer(previous, event) {
|
| 242 |
+
const state = clone(previous);
|
| 243 |
+
switch (event.type) {
|
| 244 |
+
case EVENT_TYPES.PLAYER_MOVE: {
|
| 245 |
+
const dx = Number(event.dx) || 0;
|
| 246 |
+
const dy = Number(event.dy) || 0;
|
| 247 |
+
const magnitude = Math.hypot(dx, dy) || 1;
|
| 248 |
+
const nextX = clamp(state.player.x + (dx / magnitude) * state.player.speed, WALKABLE_BOUNDS.minX, WALKABLE_BOUNDS.maxX);
|
| 249 |
+
const nextY = clamp(state.player.y + (dy / magnitude) * state.player.speed, WALKABLE_BOUNDS.minY, WALKABLE_BOUNDS.maxY);
|
| 250 |
+
if (!state.districts.some((district) => circlesOverlap(nextX, nextY, district))) {
|
| 251 |
+
state.player.x = nextX;
|
| 252 |
+
state.player.y = nextY;
|
| 253 |
+
state.player.step = (state.player.step + 1) % 4;
|
| 254 |
+
}
|
| 255 |
+
state.player.facing = Math.abs(dx) > Math.abs(dy) ? (dx > 0 ? 'east' : 'west') : (dy > 0 ? 'south' : 'north');
|
| 256 |
+
return state;
|
| 257 |
+
}
|
| 258 |
+
case EVENT_TYPES.SELECT_DISTRICT: {
|
| 259 |
+
const district = findDistrict(state, event.districtId);
|
| 260 |
+
state.selectedDistrictId = district.id;
|
| 261 |
+
state.selectedFileId = district.files[0]?.id || null;
|
| 262 |
+
state.feedback = `${district.name} selected. Press Enter or E to inspect.`;
|
| 263 |
+
return state;
|
| 264 |
+
}
|
| 265 |
+
case EVENT_TYPES.SELECT_FILE:
|
| 266 |
+
state.selectedFileId = event.fileId;
|
| 267 |
+
state.activePanel = 'file';
|
| 268 |
+
state.feedback = `Inspecting ${selectedFile(state).name}`;
|
| 269 |
+
return state;
|
| 270 |
+
case EVENT_TYPES.SELECT_COLLECTION:
|
| 271 |
+
state.selectedCollection = event.collection;
|
| 272 |
+
state.activePanel = 'collection';
|
| 273 |
+
return state;
|
| 274 |
+
case EVENT_TYPES.SELECT_RECORD:
|
| 275 |
+
state.selectedRecord = { type: event.recordType, id: event.recordId };
|
| 276 |
+
state.activePanel = 'record';
|
| 277 |
+
return state;
|
| 278 |
+
case EVENT_TYPES.OPEN_INSPECTOR:
|
| 279 |
+
state.activePanel = 'inspector';
|
| 280 |
+
state.feedback = `Inspecting ${findDistrict(state, state.selectedDistrictId).path}`;
|
| 281 |
+
return state;
|
| 282 |
+
case EVENT_TYPES.CLOSE_INSPECTOR:
|
| 283 |
+
state.activePanel = 'world';
|
| 284 |
+
return state;
|
| 285 |
+
case EVENT_TYPES.ENTER_DISTRICT: {
|
| 286 |
+
const district = findDistrict(state, event.districtId || state.selectedDistrictId);
|
| 287 |
+
state.selectedDistrictId = district.id;
|
| 288 |
+
state.selectedFileId = district.files[0]?.id || null;
|
| 289 |
+
state.activePanel = 'inspector';
|
| 290 |
+
state.view = 'district';
|
| 291 |
+
state.feedback = `Entered ${district.path}. Code is physical here.`;
|
| 292 |
+
pushEvent(state, makeEvent(EVENT_TYPES.ENTER_DISTRICT, state.tick, { message: `Player entered ${district.path}.`, districtId: district.id }));
|
| 293 |
+
return state;
|
| 294 |
+
}
|
| 295 |
+
case EVENT_TYPES.OPEN_TERMINAL:
|
| 296 |
+
state.terminal.open = true;
|
| 297 |
+
state.activeTool = 'terminal';
|
| 298 |
+
state.activePanel = 'terminal';
|
| 299 |
+
state.feedback = 'Terminal phone online. Commands are simulated locally.';
|
| 300 |
+
return state;
|
| 301 |
+
case EVENT_TYPES.CLOSE_TERMINAL:
|
| 302 |
+
state.terminal.open = false;
|
| 303 |
+
if (state.activePanel === 'terminal') state.activePanel = 'world';
|
| 304 |
+
return state;
|
| 305 |
+
case EVENT_TYPES.RUN_COMMAND: {
|
| 306 |
+
const command = String(event.command || '').trim().toLowerCase();
|
| 307 |
+
if (!command) return state;
|
| 308 |
+
state.terminal.command = command;
|
| 309 |
+
state.terminal.lines = [...state.terminal.lines, ...event.lines].slice(-28);
|
| 310 |
+
state.feedback = 'Ran `' + command + '` in the simulated terminal.';
|
| 311 |
+
pushEvent(state, makeEvent(EVENT_TYPES.USE_TOOL, state.tick, { message: `Terminal ran ${command}.`, toolId: 'terminal' }));
|
| 312 |
+
return state;
|
| 313 |
+
}
|
| 314 |
+
case EVENT_TYPES.USE_TOOL: {
|
| 315 |
+
state.activeTool = event.toolId;
|
| 316 |
+
if (event.toolId === 'terminal') state.terminal.open = true;
|
| 317 |
+
if (event.toolId === 'git') { state.selectedCollection = 'commits'; state.activePanel = 'collection'; }
|
| 318 |
+
if (event.toolId === 'docker') { state.selectedCollection = 'build'; state.activePanel = 'collection'; }
|
| 319 |
+
if (event.toolId === 'code') state.activePanel = 'file';
|
| 320 |
+
if (event.toolId === 'ssh') { state.unlocked = true; state.activePanel = 'inspector'; }
|
| 321 |
+
if (event.toolId === 'database') { state.selectedCollection = 'files'; state.activePanel = 'collection'; }
|
| 322 |
+
if (event.toolId === 'deploy') { state.repository.deployment = 'LAUNCHING'; state.selectedCollection = 'build'; state.activePanel = 'collection'; }
|
| 323 |
+
state.feedback = event.message || `Equipped ${event.toolId}.`;
|
| 324 |
+
pushEvent(state, makeEvent(EVENT_TYPES.USE_TOOL, state.tick, { message: state.feedback, toolId: event.toolId }));
|
| 325 |
+
return state;
|
| 326 |
+
}
|
| 327 |
+
case EVENT_TYPES.TICK:
|
| 328 |
+
state.tick += 1;
|
| 329 |
+
return state;
|
| 330 |
+
case EVENT_TYPES.AGENT_PROGRESS: {
|
| 331 |
+
const agent = state.agents.find((candidate) => candidate.id === event.detail.agentId);
|
| 332 |
+
if (agent) agent.progress = Math.min(100, agent.progress + 2);
|
| 333 |
+
pushEvent(state, makeEvent(event.type, state.tick, event.detail));
|
| 334 |
+
state.feedback = event.detail.message;
|
| 335 |
+
return state;
|
| 336 |
+
}
|
| 337 |
+
case EVENT_TYPES.CONSTRUCTION: {
|
| 338 |
+
const districtId = event.detail.districtId;
|
| 339 |
+
state.construction[districtId] = clamp((state.construction[districtId] || 0) + (event.detail.amount || 0), 0, 1);
|
| 340 |
+
pushEvent(state, makeEvent(event.type, state.tick, event.detail));
|
| 341 |
+
state.feedback = event.detail.message;
|
| 342 |
+
return state;
|
| 343 |
+
}
|
| 344 |
+
case EVENT_TYPES.BUILD_STARTED:
|
| 345 |
+
state.repository.build = 'RUNNING';
|
| 346 |
+
pushEvent(state, makeEvent(event.type, state.tick, event.detail));
|
| 347 |
+
state.feedback = event.detail.message;
|
| 348 |
+
return state;
|
| 349 |
+
case EVENT_TYPES.BUILD_COMPLETED:
|
| 350 |
+
state.repository.build = 'PASSING';
|
| 351 |
+
pushEvent(state, makeEvent(event.type, state.tick, event.detail));
|
| 352 |
+
state.feedback = event.detail.message;
|
| 353 |
+
return state;
|
| 354 |
+
case EVENT_TYPES.TEST_STARTED:
|
| 355 |
+
state.repository.tests = 'RUNNING';
|
| 356 |
+
pushEvent(state, makeEvent(event.type, state.tick, event.detail));
|
| 357 |
+
state.feedback = event.detail.message;
|
| 358 |
+
return state;
|
| 359 |
+
case EVENT_TYPES.TEST_COMPLETED:
|
| 360 |
+
state.repository.tests = '142 PASS';
|
| 361 |
+
pushEvent(state, makeEvent(event.type, state.tick, event.detail));
|
| 362 |
+
state.feedback = event.detail.message;
|
| 363 |
+
return state;
|
| 364 |
+
case EVENT_TYPES.DEPLOYMENT_STARTED:
|
| 365 |
+
state.repository.deployment = 'LAUNCHING';
|
| 366 |
+
pushEvent(state, makeEvent(event.type, state.tick, event.detail));
|
| 367 |
+
state.feedback = event.detail.message;
|
| 368 |
+
return state;
|
| 369 |
+
case EVENT_TYPES.DEPLOYMENT_COMPLETED:
|
| 370 |
+
state.repository.deployment = 'STAGED';
|
| 371 |
+
pushEvent(state, makeEvent(event.type, state.tick, event.detail));
|
| 372 |
+
state.feedback = event.detail.message;
|
| 373 |
+
return state;
|
| 374 |
+
case EVENT_TYPES.COMMIT:
|
| 375 |
+
case EVENT_TYPES.ISSUE:
|
| 376 |
+
case EVENT_TYPES.PULL_REQUEST:
|
| 377 |
+
pushEvent(state, makeEvent(event.type, state.tick, event.detail));
|
| 378 |
+
state.feedback = event.detail.message;
|
| 379 |
+
return state;
|
| 380 |
+
default:
|
| 381 |
+
return state;
|
| 382 |
+
}
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
export function advanceWorldState(state) {
|
| 386 |
+
const ticked = worldReducer(state, { type: EVENT_TYPES.TICK });
|
| 387 |
+
const plan = SIMULATION_SEQUENCE[(ticked.tick - 1) % SIMULATION_SEQUENCE.length];
|
| 388 |
+
const event = makeEvent(plan.type, ticked.tick, plan.detail);
|
| 389 |
+
return { state: worldReducer(ticked, event), event };
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
function escapeHtml(value) {
|
| 393 |
+
return String(value).replace(/[&<>"']/g, (character) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[character]));
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
function windowBlocks(count, color) {
|
| 397 |
+
return Array.from({ length: count }, (_, index) => `<i class="building-window" style="--window-index:${index};--window-color:${color}"></i>`).join('');
|
| 398 |
+
}
|
| 399 |
+
|
| 400 |
+
export function renderWorld(viewport, state, onBuildingSelect) {
|
| 401 |
+
const viewportWidth = viewport.clientWidth || 960;
|
| 402 |
+
const viewportHeight = viewport.clientHeight || 680;
|
| 403 |
+
const centerX = viewportWidth / 2;
|
| 404 |
+
const centerY = viewportHeight * 0.56;
|
| 405 |
+
const translateX = centerX - state.player.x * 0.88;
|
| 406 |
+
const translateY = centerY - state.player.y * 0.88;
|
| 407 |
+
const buildingMarkup = state.districts.map((district) => {
|
| 408 |
+
const selected = district.id === state.selectedDistrictId ? ' is-selected' : '';
|
| 409 |
+
const progress = Math.round((state.construction[district.id] || 0) * 100);
|
| 410 |
+
return `<button class="world-building building-${district.id}${selected}" data-district-id="${district.id}" aria-label="Inspect ${escapeHtml(district.name)}" style="--building-x:${district.x}px;--building-y:${district.y}px;--building-w:${district.width}px;--building-h:${district.height}px;--district-accent:${district.accent};--construction:${progress}%">
|
| 411 |
+
<span class="building-cap"></span>
|
| 412 |
+
<span class="building-facade">${windowBlocks(district.id === 'src' ? 20 : 12, district.accent)}</span>
|
| 413 |
+
<span class="building-sign"><b>${escapeHtml(district.glyph)}</b>${escapeHtml(district.name)}</span>
|
| 414 |
+
<span class="building-status">${progress}% BUILT</span>
|
| 415 |
+
<span class="building-floor"></span>
|
| 416 |
+
</button>`;
|
| 417 |
+
}).join('');
|
| 418 |
+
|
| 419 |
+
viewport.innerHTML = `<div class="world-canvas" style="transform:translate(${translateX}px,${translateY}px) scale(.88)">
|
| 420 |
+
<div class="distant-skyline" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
|
| 421 |
+
<div class="moon-orb" aria-hidden="true"></div>
|
| 422 |
+
<div class="world-ground" aria-label="Walkable GitWorld repository city">
|
| 423 |
+
<div class="ground-grid"></div>
|
| 424 |
+
<div class="road road-main"><span>MAIN</span></div>
|
| 425 |
+
<div class="road road-cross"><span>HISTORY</span></div>
|
| 426 |
+
<div class="road road-branch"><span>BRANCH / ALT</span></div>
|
| 427 |
+
<div class="plaza"><span>SPAWN PLAZA</span></div>
|
| 428 |
+
${buildingMarkup}
|
| 429 |
+
<div class="construction-crane crane-one" aria-hidden="true"><i></i><b></b><em></em></div>
|
| 430 |
+
<div class="construction-crane crane-two" aria-hidden="true"><i></i><b></b><em></em></div>
|
| 431 |
+
<div class="agent-bot bot-a" aria-label="Simulated world architect builder"><i></i><b></b></div>
|
| 432 |
+
<div class="agent-bot bot-b" aria-label="Simulated repository builder"><i></i><b></b></div>
|
| 433 |
+
<div class="quest-marker">!</div>
|
| 434 |
+
<div class="pr-gate"><span>PULL REQUEST</span><b>OPEN GATE</b></div>
|
| 435 |
+
<div class="player-avatar" data-facing="${state.player.facing}" aria-label="AhmadParr avatar" style="left:${state.player.x}px;top:${state.player.y}px">
|
| 436 |
+
<span class="avatar-shadow"></span><span class="avatar-legs"><i></i><i></i></span><span class="avatar-body"><i class="avatar-pack"></i></span><span class="avatar-head"></span>
|
| 437 |
+
</div>
|
| 438 |
+
<div class="world-sign repo-sign"><small>SIMULATED REPOSITORY</small><strong>SNAPKITTYWEST</strong><b>/ pax-coder</b><em>★ 213 · ⑂ 45 · ◉ PUBLIC</em></div>
|
| 439 |
+
<div class="world-sign build-sign"><small>BUILD AGENTS</small><strong>3 ACTIVE</strong><b>WATCH THE CREW WORK</b></div>
|
| 440 |
+
</div>
|
| 441 |
+
</div>`;
|
| 442 |
+
|
| 443 |
+
viewport.querySelectorAll('[data-district-id]').forEach((building) => {
|
| 444 |
+
building.addEventListener('click', () => onBuildingSelect(building.dataset.districtId));
|
| 445 |
+
});
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
export function renderMinimap(root, state) {
|
| 449 |
+
const districts = state.districts.map((district) => `<button class="minimap-district" data-district-id="${district.id}" aria-label="Map ${escapeHtml(district.name)}" style="left:${district.x / 16}px;top:${district.y / 9.2}px;background:${district.accent}"></button>`).join('');
|
| 450 |
+
root.innerHTML = `<div class="minimap-grid" aria-label="GitWorld district map"><div class="map-road map-road-a"></div><div class="map-road map-road-b"></div>${districts}<span class="map-player" style="left:${state.player.x / 16}px;top:${state.player.y / 9.2}px"></span></div><div class="map-caption"><span>LIVE MAP</span><b>${state.selectedDistrictId.toUpperCase()}</b></div>`;
|
| 451 |
+
}
|
dist/styles.css
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
color-scheme: dark;
|
| 3 |
+
--night: #040b14;
|
| 4 |
+
--sky: #071829;
|
| 5 |
+
--panel: rgba(5, 16, 29, .92);
|
| 6 |
+
--panel-solid: #091827;
|
| 7 |
+
--line: #164666;
|
| 8 |
+
--line-hot: #2a7195;
|
| 9 |
+
--white: #e7f5ff;
|
| 10 |
+
--muted: #8daabe;
|
| 11 |
+
--cyan: #64f4e1;
|
| 12 |
+
--blue: #68acff;
|
| 13 |
+
--green: #9ff254;
|
| 14 |
+
--gold: #ffc95b;
|
| 15 |
+
--orange: #ff9d54;
|
| 16 |
+
--pink: #e887ff;
|
| 17 |
+
--red: #ff6877;
|
| 18 |
+
--mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
| 19 |
+
--sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
* { box-sizing: border-box; }
|
| 23 |
+
html { min-width: 320px; background: var(--night); }
|
| 24 |
+
body { margin: 0; min-width: 320px; overflow-x: hidden; color: var(--white); font-family: var(--sans); background: var(--night); }
|
| 25 |
+
button, input { font: inherit; }
|
| 26 |
+
button { color: inherit; cursor: pointer; }
|
| 27 |
+
button:focus-visible, input:focus-visible { outline: 2px solid var(--cyan); outline-offset: 3px; }
|
| 28 |
+
.game-shell { position: relative; min-height: 100vh; isolation: isolate; padding: 14px 15px 10px; background: #050e1b; overflow: hidden; }
|
| 29 |
+
.game-shell::before { content: ""; position: fixed; z-index: -4; inset: 0; pointer-events: none; background-image: radial-gradient(circle at 50% 15%, rgba(33, 91, 129, .2), transparent 38%), linear-gradient(180deg, #07182a 0, #050d18 58%, #071322 100%); }
|
| 30 |
+
.game-shell::after { content: ""; position: fixed; z-index: -3; inset: 0; pointer-events: none; opacity: .34; background-image: radial-gradient(circle, rgba(102, 198, 255, .8) 0 1px, transparent 1.5px); background-size: 113px 137px; background-position: 25px 18px; }
|
| 31 |
+
.ambient { position: fixed; z-index: -2; width: 390px; height: 390px; border-radius: 50%; pointer-events: none; filter: blur(46px); opacity: .16; }.ambient-one { top: 6%; left: 31%; background: #216cbb; }.ambient-two { right: -90px; bottom: 8%; background: #8e2aa8; }
|
| 32 |
+
.panel-glass { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; box-shadow: 0 16px 36px rgba(0, 0, 0, .28), inset 0 1px 0 rgba(118, 215, 255, .08); backdrop-filter: blur(10px); }
|
| 33 |
+
.topbar { position: relative; z-index: 5; display: grid; grid-template-columns: 230px minmax(260px, 1fr) 260px; align-items: center; gap: 30px; width: min(1500px, 100%); margin: 0 auto; padding: 0 2px 13px; }
|
| 34 |
+
.brand-lockup { display: flex; align-items: center; gap: 11px; text-transform: uppercase; }.brand-lockup > span:last-child { display: grid; gap: 2px; }.brand-lockup strong { color: var(--cyan); font: 700 19px var(--mono); letter-spacing: -.04em; text-shadow: 0 0 16px rgba(100, 244, 225, .45); }.brand-lockup small { color: var(--muted); font: 8px var(--mono); letter-spacing: .18em; }.brand-mark { width: 32px; height: 32px; display: grid; place-items: center; color: var(--cyan); border: 1px solid var(--cyan); border-radius: 6px; font-size: 20px; box-shadow: 0 0 16px rgba(100, 244, 225, .22); }
|
| 35 |
+
.repo-search-wrap { position: relative; display: flex; align-items: center; }.repo-search-wrap label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); }.repo-search-wrap input { width: 100%; height: 34px; padding: 0 44px 0 16px; color: var(--white); border: 1px solid #355675; border-radius: 8px; background: rgba(6, 14, 25, .72); box-shadow: inset 0 0 12px rgba(31, 120, 191, .1); }.repo-search-wrap input::placeholder { color: #7c9ab5; }.repo-search-wrap kbd { position: absolute; right: 7px; color: var(--cyan); font: 14px var(--mono); pointer-events: none; }.topbar-actions { display: flex; align-items: center; justify-content: flex-end; gap: 11px; }.simulated-pill { display: flex; align-items: center; gap: 7px; color: var(--green); font: 8px var(--mono); letter-spacing: .06em; }.simulated-pill i, .live-dot { width: 7px; height: 7px; display: inline-block; border-radius: 50%; background: var(--green); box-shadow: 0 0 10px var(--green); }.icon-button { width: 29px; height: 29px; padding: 0; border: 1px solid var(--line-hot); border-radius: 6px; background: rgba(9, 27, 45, .8); color: var(--cyan); font-size: 17px; line-height: 1; }.icon-button:hover { border-color: var(--cyan); background: rgba(27, 79, 102, .7); }
|
| 36 |
+
|
| 37 |
+
.left-rail { position: absolute; z-index: 4; top: 72px; left: 15px; width: 254px; padding: 17px 18px; }.panel-title { display: grid; gap: 1px; padding-bottom: 12px; border-bottom: 1px solid rgba(61, 141, 177, .35); }.section-kicker { color: var(--cyan); font: 10px var(--mono); letter-spacing: .14em; }.panel-title h1 { margin: 2px 0 0; color: var(--white); font: 700 19px var(--mono); letter-spacing: .02em; }.panel-title i { width: 56px; height: 2px; margin-top: 9px; background: var(--cyan); box-shadow: 0 0 8px var(--cyan); }.welcome-copy { margin: 14px 0 0; color: #d2e3ed; font-size: 14px; line-height: 1.45; }.welcome-copy.muted { margin-top: 6px; color: var(--muted); font-size: 12px; }.welcome-copy strong { color: var(--cyan); }.rail-divider { height: 1px; margin: 16px 0 13px; background: var(--line); }.component-heading { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }.component-heading > span { color: var(--cyan); font: 12px var(--mono); letter-spacing: .05em; }.component-heading small { color: #6d8da7; font: 8px var(--mono); }.component-list { display: grid; gap: 9px; margin: 13px 0 0; padding: 0; list-style: none; }.component-list li { display: flex; align-items: center; gap: 9px; color: #bfd3df; font-size: 12px; }.component-list li b { width: 20px; height: 20px; display: grid; place-items: center; color: var(--cyan); border: 1px solid #2e7798; border-radius: 5px; font-size: 13px; }.component-list strong { color: var(--white); }.how-it-works { margin-top: 18px; padding-top: 13px; border-top: 1px solid var(--line); }.how-it-works ol { display: grid; gap: 5px; margin: 10px 0 0; padding-left: 21px; color: var(--muted); font: 10px/1.4 var(--mono); }
|
| 38 |
+
|
| 39 |
+
.world-stage { position: relative; min-height: 720px; width: min(1500px, 100%); margin: 0 auto; padding: 9px 287px 0 274px; }.stage-heading { position: relative; z-index: 3; display: flex; justify-content: space-between; align-items: flex-end; gap: 14px; padding: 5px 18px 10px; }.stage-heading h2 { margin: 4px 0 0; color: var(--white); font: 700 21px var(--mono); letter-spacing: -.03em; text-shadow: 0 0 19px rgba(148, 218, 255, .28); }.stage-heading h2 b { color: var(--cyan); font-weight: 500; }.stage-state { display: flex; gap: 10px; color: var(--green); font: 9px var(--mono); }.stage-state span + span { padding-left: 10px; border-left: 1px solid var(--line); color: var(--muted); }.world-viewport { position: relative; height: 690px; overflow: hidden; border: 1px solid #195174; border-radius: 13px; background: #071725; box-shadow: inset 0 0 50px rgba(5, 43, 69, .55), 0 20px 50px rgba(0, 0, 0, .34); }.world-viewport::after { content: ""; position: absolute; z-index: 3; inset: 0; pointer-events: none; border: 1px solid rgba(117, 212, 255, .08); border-radius: 12px; box-shadow: inset 0 0 35px rgba(0, 0, 0, .62); }
|
| 40 |
+
.world-canvas { position: absolute; width: 1600px; height: 960px; transform-origin: 0 0; transition: transform .25s ease; }.world-ground { position: absolute; inset: 0; overflow: hidden; background: linear-gradient(180deg, #0b2435 0 40%, #102f3b 40% 46%, #132c31 46% 100%); }.world-ground::before { content: ""; position: absolute; left: -8%; right: -8%; bottom: -25%; height: 64%; transform: perspective(540px) rotateX(57deg); transform-origin: bottom; background-image: linear-gradient(rgba(49, 129, 134, .25) 1px, transparent 1px), linear-gradient(90deg, rgba(49, 129, 134, .22) 1px, transparent 1px); background-size: 42px 42px; opacity: .58; }.world-ground::after { content: ""; position: absolute; inset: 0; pointer-events: none; background-image: radial-gradient(circle at 15% 22%, rgba(155, 235, 255, .8) 0 1px, transparent 1.5px); background-size: 87px 73px; opacity: .55; }.ground-grid { position: absolute; inset: 0; background: linear-gradient(105deg, transparent 49.7%, rgba(36, 117, 126, .26) 50%, transparent 50.3%); opacity: .28; }.distant-skyline { position: absolute; z-index: 0; top: 74px; left: 0; right: 0; height: 220px; display: flex; align-items: flex-end; justify-content: space-around; padding: 0 65px; opacity: .8; }.distant-skyline i { display: block; width: 43px; height: 120px; background: #0d2d42; border: 1px solid #1d6383; box-shadow: 9px -24px 0 -8px #0d2c42, inset 9px 16px 0 -8px rgba(114, 218, 255, .28); }.distant-skyline i:nth-child(2n) { height: 172px; width: 66px; }.distant-skyline i:nth-child(3n) { height: 92px; background: #112949; }.moon-orb { position: absolute; z-index: 0; top: 36px; right: 210px; width: 56px; height: 56px; border-radius: 50%; background: #98cbdf; box-shadow: 0 0 36px rgba(100, 216, 255, .45); opacity: .8; }.moon-orb::after { content: ""; position: absolute; inset: 9px 13px 13px 7px; border-radius: 50%; box-shadow: 20px 2px 0 -14px #5a8caa, 10px 27px 0 -13px #6598ac; }
|
| 41 |
+
.road { position: absolute; z-index: 1; background: #183e45; border: 2px solid #3b6c67; box-shadow: inset 0 0 0 3px rgba(5, 25, 35, .35), 0 0 22px rgba(32, 153, 157, .12); }.road span { position: absolute; color: rgba(116, 219, 211, .5); font: 10px var(--mono); letter-spacing: .2em; }.road-main { left: -120px; top: 531px; width: 1850px; height: 111px; transform: rotate(-2deg); }.road-main span { top: 47px; left: 215px; }.road-cross { left: 657px; top: 232px; width: 122px; height: 610px; transform: rotate(16deg); }.road-cross span { top: 300px; left: 27px; transform: rotate(-90deg); }.road-branch { left: 975px; top: 406px; width: 700px; height: 73px; transform: rotate(26deg); border-color: #6b5687; background: #2b3145; }.road-branch span { top: 27px; left: 30px; color: rgba(204, 156, 255, .65); }.plaza { position: absolute; z-index: 2; left: 683px; top: 590px; width: 188px; height: 122px; border: 3px solid #a17d51; background: #4b4b3b; transform: rotate(45deg); box-shadow: 0 0 0 7px rgba(32, 68, 64, .7), inset 0 0 0 2px #c6a15f; }.plaza span { position: absolute; left: 45px; top: 48px; transform: rotate(-45deg); color: #f5d894; font: 11px var(--mono); white-space: nowrap; }
|
| 42 |
+
|
| 43 |
+
.world-building { position: absolute; z-index: 3; left: var(--building-x); top: var(--building-y); width: var(--building-w); height: var(--building-h); padding: 0; border: 1px solid color-mix(in srgb, var(--district-accent) 65%, #1e475b); border-radius: 5px; background: #102531; box-shadow: 8px 13px 0 rgba(4, 13, 19, .45), 0 0 24px color-mix(in srgb, var(--district-accent) 24%, transparent), inset 0 0 0 3px rgba(5, 17, 27, .62); transform: perspective(440px) rotateX(4deg); transition: transform .2s ease, filter .2s ease, border-color .2s ease; overflow: hidden; text-align: left; }.world-building:hover, .world-building.is-selected { z-index: 5; filter: brightness(1.24); transform: perspective(440px) rotateX(4deg) translateY(-5px); border-color: var(--district-accent); }.world-building.is-search-hidden { opacity: .16; filter: grayscale(1); }.building-cap { position: absolute; top: -22px; left: 13px; width: calc(100% - 26px); height: 24px; border: 1px solid var(--district-accent); background: #152f3a; transform: skewX(-23deg); box-shadow: inset 0 0 14px color-mix(in srgb, var(--district-accent) 33%, transparent); }.building-facade { position: absolute; inset: 32px 11px 31px; display: grid; grid-template-columns: repeat(5, 1fr); grid-auto-rows: 16px; gap: 9px 10px; padding: 9px; background: repeating-linear-gradient(90deg, rgba(53, 111, 116, .16) 0 22px, transparent 22px 40px), #17313a; border: 1px solid rgba(98, 166, 168, .55); }.building-window { height: 9px; background: var(--district-accent); opacity: calc(.25 + (var(--construction) / 130)); box-shadow: 0 0 7px var(--district-accent); animation: window-blink 4s infinite alternate; animation-delay: calc(var(--window-index) * -140ms); }.building-sign { position: absolute; z-index: 1; top: 7px; left: 9px; right: 9px; min-height: 23px; display: flex; align-items: center; gap: 6px; padding: 3px 7px; color: var(--white); background: rgba(7, 20, 29, .92); border-left: 3px solid var(--district-accent); font: 11px var(--mono); letter-spacing: .03em; }.building-sign b { color: var(--district-accent); font-size: 18px; }.building-status { position: absolute; bottom: 7px; left: 12px; color: var(--district-accent); font: 9px var(--mono); letter-spacing: .1em; }.building-floor { position: absolute; left: -8px; right: -8px; bottom: -13px; height: 18px; background: #081a25; border-top: 2px solid var(--district-accent); transform: skewX(-22deg); }.building-issues { transform: perspective(440px) rotateX(4deg) rotate(-1deg); }.building-prs { box-shadow: 8px 13px 0 rgba(4, 13, 19, .45), 0 0 30px rgba(232, 135, 255, .34); }.building-prs .building-sign { color: var(--pink); }.construction-crane { position: absolute; z-index: 2; width: 13px; height: 236px; border-left: 5px solid var(--gold); opacity: .75; }.construction-crane i { position: absolute; top: 22px; left: 0; width: 145px; height: 5px; background: var(--gold); }.construction-crane b { position: absolute; top: 26px; left: 118px; width: 2px; height: 130px; background: var(--gold); }.construction-crane em { position: absolute; top: 151px; left: 109px; width: 20px; height: 20px; border: 2px solid var(--orange); border-radius: 50%; animation: crane-pulse 2s infinite; }.crane-one { left: 480px; top: 115px; transform: rotate(-2deg); }.crane-two { right: 173px; top: 107px; transform: scale(.82) rotate(3deg); }
|
| 44 |
+
.agent-bot { position: absolute; z-index: 5; width: 24px; height: 31px; border: 2px solid var(--green); background: #182d33; box-shadow: 0 0 14px rgba(159, 242, 84, .48); animation: bot-walk 4s ease-in-out infinite; }.agent-bot::after { content: ""; position: absolute; left: 4px; right: 4px; top: 6px; height: 7px; background: var(--green); box-shadow: 0 0 7px var(--green); }.agent-bot i, .agent-bot b { position: absolute; bottom: -8px; width: 4px; height: 8px; background: var(--green); }.agent-bot i { left: 2px; }.agent-bot b { right: 2px; }.bot-a { left: 763px; top: 437px; }.bot-b { left: 1086px; top: 453px; animation-delay: -2s; border-color: var(--gold); }.bot-b::after { background: var(--gold); box-shadow: 0 0 7px var(--gold); }.bot-b i, .bot-b b { background: var(--gold); }.quest-marker { position: absolute; z-index: 6; left: 1006px; top: 552px; width: 43px; height: 43px; display: grid; place-items: center; color: var(--gold); border: 4px solid var(--gold); border-radius: 50%; font: bold 28px var(--mono); box-shadow: 0 0 16px rgba(255, 201, 91, .6); animation: quest-pulse 1.8s infinite; }.pr-gate { position: absolute; z-index: 4; left: 695px; top: 415px; width: 130px; height: 76px; display: grid; place-items: center; align-content: center; gap: 6px; color: var(--pink); border: 3px solid var(--pink); border-radius: 5px; background: #2c173d; box-shadow: 0 0 23px rgba(232, 135, 255, .55), inset 0 0 16px rgba(232, 135, 255, .2); transform: perspective(240px) rotateX(8deg); }.pr-gate span { font: 9px var(--mono); }.pr-gate b { font-size: 11px; }.player-avatar { position: absolute; z-index: 8; width: 42px; height: 74px; transform: translate(-50%, -78%); transition: left .13s linear, top .13s linear; filter: drop-shadow(0 7px 3px rgba(0, 0, 0, .55)); }.avatar-shadow { position: absolute; left: 1px; bottom: -3px; width: 40px; height: 11px; border-radius: 50%; background: rgba(0, 0, 0, .48); }.avatar-legs { position: absolute; left: 9px; bottom: 5px; width: 24px; height: 28px; display: flex; gap: 3px; }.avatar-legs i { flex: 1; background: #152d4b; border: 2px solid #09192d; }.avatar-body { position: absolute; left: 7px; bottom: 25px; width: 29px; height: 29px; background: #171b24; border: 2px solid #060d19; }.avatar-body::after { content: "⌘"; position: absolute; inset: 2px; display: grid; place-items: center; color: var(--white); font-size: 17px; }.avatar-pack { position: absolute; z-index: -1; left: -7px; top: 2px; width: 11px; height: 22px; background: #3a2a20; border: 1px solid #9a6d38; }.avatar-head { position: absolute; left: 10px; top: 3px; width: 23px; height: 22px; background: #5f402d; border: 2px solid #09111c; border-radius: 3px 3px 8px 8px; }.avatar-head::before { content: ""; position: absolute; left: -2px; right: -2px; top: -3px; height: 9px; background: #171515; border-radius: 5px 5px 0 0; }.world-sign { position: absolute; z-index: 6; display: grid; gap: 2px; padding: 11px 15px; background: rgba(5, 20, 30, .91); border: 2px solid var(--cyan); box-shadow: 0 0 19px rgba(100, 244, 225, .22), inset 0 0 0 3px rgba(11, 56, 65, .7); transform: perspective(340px) rotateX(4deg); }.world-sign small { color: var(--green); font: 9px var(--mono); }.world-sign strong { color: var(--cyan); font: 17px var(--mono); letter-spacing: .06em; }.world-sign b { color: #bfe6ee; font: 13px var(--mono); }.world-sign em { color: var(--gold); font: 10px var(--mono); font-style: normal; }.repo-sign { left: 631px; top: 155px; width: 255px; transform: perspective(340px) rotateX(4deg) rotate(-1deg); }.build-sign { right: 88px; top: 201px; width: 260px; border-color: var(--green); }.build-sign strong { color: var(--green); }.build-sign b { color: var(--white); }
|
| 45 |
+
|
| 46 |
+
.nearby-prompt { position: absolute; z-index: 5; left: 300px; bottom: 65px; display: grid; gap: 2px; min-width: 192px; padding: 9px 13px; border-left: 2px solid var(--cyan); background: rgba(5, 19, 30, .84); }.nearby-prompt span, .nearby-prompt small { color: var(--muted); font: 9px var(--mono); letter-spacing: .1em; }.nearby-prompt strong { color: var(--cyan); font: 13px var(--mono); }.nearby-prompt.is-ready { border-color: var(--gold); }.nearby-prompt.is-ready strong { color: var(--gold); }.city-reticle { position: absolute; z-index: 4; left: 50%; top: 53%; width: 45px; height: 45px; transform: translate(-50%, -50%); pointer-events: none; opacity: .35; }.city-reticle i, .city-reticle b { position: absolute; display: block; background: var(--cyan); }.city-reticle i { left: 21px; top: 0; width: 1px; height: 45px; }.city-reticle b { left: 0; top: 21px; width: 45px; height: 1px; }.movement-help { position: absolute; z-index: 5; right: 306px; bottom: 63px; display: flex; align-items: center; gap: 5px; color: var(--muted); font: 9px var(--mono); }.movement-help kbd, .terminal-form input, .repo-search-wrap kbd { font-family: var(--mono); }.movement-help kbd { padding: 3px 5px; border: 1px solid #38556a; border-radius: 4px; color: var(--white); background: #112334; }.movement-help span { margin-right: 6px; }
|
| 47 |
+
.feedback-line { position: absolute; z-index: 5; left: 300px; right: 306px; bottom: 14px; display: flex; align-items: center; gap: 8px; min-height: 29px; padding: 0 11px; border-top: 1px solid #1d4d64; background: rgba(5, 17, 27, .82); color: var(--muted); font: 10px var(--mono); }.feedback-line i { width: 6px; height: 6px; border-radius: 50%; background: var(--green); box-shadow: 0 0 8px var(--green); }.feedback-line time { margin-left: auto; color: var(--cyan); }
|
| 48 |
+
|
| 49 |
+
.right-rail { position: absolute; z-index: 4; top: 72px; right: 15px; width: 276px; display: grid; gap: 10px; }.map-panel, .agents-panel, .event-panel { padding: 12px; }.panel-heading { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding-bottom: 9px; color: var(--cyan); border-bottom: 1px solid rgba(61, 141, 177, .35); font: 11px var(--mono); letter-spacing: .08em; }.panel-heading > span { display: flex; align-items: center; gap: 7px; }.panel-heading small { color: var(--muted); font: 8px var(--mono); }.count-badge { display: inline-grid; place-items: center; width: 21px; height: 17px; color: var(--green); border: 1px solid var(--green); border-radius: 4px; font: 9px var(--mono); }.minimap-viewport { position: relative; height: 153px; margin: 11px 0 9px; overflow: hidden; border: 1px solid #22536e; border-radius: 6px; background: #1c3b2b; }.minimap-grid { position: absolute; inset: 0; background-color: #173b2e; background-image: linear-gradient(25deg, transparent 40%, rgba(19, 75, 63, .75) 41% 47%, transparent 48%), repeating-linear-gradient(90deg, rgba(91, 169, 90, .23) 0 3px, transparent 3px 17px), repeating-linear-gradient(0deg, rgba(70, 131, 71, .2) 0 3px, transparent 3px 21px); }.map-road { position: absolute; background: #7a7251; opacity: .8; }.map-road-a { left: -12%; top: 52%; width: 125%; height: 7px; transform: rotate(-15deg); }.map-road-b { left: 42%; top: -12%; width: 8px; height: 125%; transform: rotate(24deg); }.minimap-district { position: absolute; width: 13px; height: 13px; padding: 0; border: 2px solid #081a24; transform: translate(-50%, -50%); box-shadow: 0 0 5px currentColor; }.minimap-district:hover { transform: translate(-50%, -50%) scale(1.4); }.map-player { position: absolute; left: 50%; top: 50%; width: 0; height: 0; border-right: 7px solid transparent; border-bottom: 16px solid white; border-left: 7px solid transparent; filter: drop-shadow(0 0 4px var(--cyan)); }.map-caption { display: flex; justify-content: space-between; color: var(--muted); font: 9px var(--mono); }.map-caption b { color: var(--cyan); }.map-legend { display: flex; justify-content: space-between; color: var(--muted); font: 8px var(--mono); }.map-legend i { display: inline-block; width: 6px; height: 6px; margin-right: 3px; border-radius: 50%; }.legend-player { background: white; }.legend-building { background: var(--green); }.legend-road { background: #bca46b; }.agent-list { display: grid; gap: 10px; margin: 12px 0; padding: 0; list-style: none; }.agent-row { display: grid; grid-template-columns: 27px 1fr auto; gap: 7px; align-items: center; min-width: 0; }.agent-avatar { width: 26px; height: 26px; display: grid; place-items: center; color: var(--agent-color); border: 1px solid var(--agent-color); border-radius: 5px; font: 9px var(--mono); }.agent-copy { display: grid; gap: 1px; min-width: 0; }.agent-copy strong { overflow: hidden; color: var(--white); font: 9px var(--mono); text-overflow: ellipsis; white-space: nowrap; }.agent-copy small { color: var(--muted); font: 8px var(--mono); }.agent-copy em { overflow: hidden; color: var(--agent-color); font: 8px var(--mono); font-style: normal; text-overflow: ellipsis; white-space: nowrap; }.progress-line { display: block; height: 3px; margin-top: 2px; background: #173040; }.progress-line b { display: block; height: 100%; background: var(--agent-color); box-shadow: 0 0 6px var(--agent-color); transition: width .4s ease; }.agent-state { color: var(--green); font: 8px var(--mono); }.swarm-flow { display: grid; justify-items: center; gap: 3px; padding-top: 9px; border-top: 1px solid var(--line); color: var(--muted); font: 8px var(--mono); }.swarm-flow span:first-of-type { color: var(--cyan); }.swarm-flow i { color: var(--line-hot); font-style: normal; }.event-panel { max-height: 205px; overflow: hidden; }.event-feed { display: grid; gap: 7px; margin: 11px 0 0; padding: 0; overflow: auto; list-style: none; }.event-row { display: grid; grid-template-columns: 52px 8px 1fr; gap: 6px; align-items: start; color: var(--muted); font-size: 9px; }.event-row time { color: #668aa3; font: 8px var(--mono); }.event-row p { margin: 0; line-height: 1.3; }.event-dot { width: 6px; height: 6px; margin-top: 3px; border-radius: 50%; background: var(--green); box-shadow: 0 0 7px var(--green); }.event-repository-issue .event-dot, .event-repository-pull-request .event-dot { background: var(--orange); box-shadow: 0 0 7px var(--orange); }.event-build-started .event-dot, .event-deployment-started .event-dot { background: var(--gold); box-shadow: 0 0 7px var(--gold); }
|
| 50 |
+
|
| 51 |
+
.inspector-drawer { position: absolute; z-index: 7; top: 385px; right: 307px; width: 330px; min-height: 300px; padding: 15px; opacity: 0; pointer-events: none; transform: translateX(24px); transition: opacity .22s ease, transform .22s ease; }.inspector-drawer.is-open { opacity: 1; pointer-events: auto; transform: translateX(0); }.drawer-heading { display: flex; justify-content: space-between; gap: 12px; padding-bottom: 10px; border-bottom: 1px solid var(--line); }.drawer-heading div { display: grid; gap: 3px; }.drawer-heading strong { color: var(--white); font: 13px var(--mono); }.inspector-content { min-height: 215px; padding: 14px 0 8px; }.inspect-header { display: grid; gap: 6px; }.inspect-header h2 { margin: 0; color: var(--white); font: 17px var(--mono); }.inspect-header p { margin: 0; color: var(--muted); font-size: 11px; line-height: 1.45; }.district-metrics { display: flex; flex-wrap: wrap; gap: 5px 11px; margin-top: 5px; color: var(--cyan); font: 9px var(--mono); }.district-metrics span + span { color: var(--muted); }.inspector-actions { display: flex; flex-wrap: wrap; gap: 7px; margin: 15px 0 10px; }.panel-action { padding: 7px 9px; border: 1px solid var(--line-hot); border-radius: 5px; background: #0c2435; color: var(--cyan); font: 9px var(--mono); }.panel-action:hover { border-color: var(--cyan); }.panel-action.primary { border-color: var(--cyan); background: rgba(43, 111, 117, .3); }.panel-action:disabled { cursor: not-allowed; opacity: .48; }.mini-file-list, .file-list, .commit-list, .issue-list, .pr-list { display: grid; gap: 5px; }.mini-file-list button, .file-row, .commit-row, .issue-row, .pr-row { display: grid; grid-template-columns: 20px 1fr auto; gap: 7px; align-items: center; padding: 6px 7px; border: 1px solid transparent; background: rgba(11, 37, 51, .72); color: var(--muted); text-align: left; }.mini-file-list button:hover, .file-row:hover, .file-row.is-active, .commit-row:hover, .issue-row:hover, .pr-row:hover { border-color: var(--line-hot); color: var(--white); }.mini-file-list b, .file-row b { color: var(--cyan); }.file-row strong, .commit-row strong, .issue-row strong, .pr-row strong { display: block; overflow: hidden; color: var(--white); font: 10px var(--mono); text-overflow: ellipsis; white-space: nowrap; }.file-row small, .commit-row small, .issue-row small, .pr-row small { display: block; color: var(--muted); font-size: 9px; }.collection-heading { display: grid; gap: 4px; margin-bottom: 10px; }.collection-heading span { color: var(--cyan); font: 9px var(--mono); letter-spacing: .1em; }.collection-heading strong { color: var(--white); font: 14px var(--mono); }.collection-heading small { color: var(--muted); font-size: 10px; }.commit-row, .issue-row, .pr-row { grid-template-columns: 15px 1fr auto; }.commit-node, .issue-marker, .pr-marker { width: 9px; height: 9px; display: grid; place-items: center; color: var(--green); border: 1px solid var(--green); border-radius: 50%; font: 8px var(--mono); }.issue-marker { color: var(--orange); border-color: var(--orange); }.pr-marker { color: var(--pink); border-color: var(--pink); border-radius: 2px; }.commit-row em, .issue-row em, .pr-row em { color: var(--green); font: 8px var(--mono); font-style: normal; }.issue-row em { color: var(--orange); }.pr-row em { color: var(--pink); }.code-preview { max-height: 145px; overflow: auto; margin: 13px 0; padding: 11px; color: var(--green); border: 1px solid #244f52; background: #07151c; font: 10px/1.45 var(--mono); white-space: pre-wrap; }.build-stack { display: grid; gap: 9px; color: var(--muted); font: 10px var(--mono); }.build-stack > div:not(.pipeline-steps) { display: flex; justify-content: space-between; gap: 10px; }.build-stack b { color: var(--green); font: 10px var(--mono); }.status-running, .status-launching { color: var(--gold) !important; }.pipeline-steps { display: flex; gap: 4px; padding-top: 10px; border-top: 1px solid var(--line); }.pipeline-steps i { flex: 1; padding: 5px 2px; border: 1px solid #27404c; color: #5e7a8a; font: 8px var(--mono); font-style: normal; text-align: center; }.pipeline-steps i.done { border-color: #3a764e; color: var(--green); }.pipeline-steps i.active { border-color: var(--cyan); color: var(--cyan); }
|
| 52 |
+
.drawer-tabs { display: flex; gap: 4px; padding-top: 9px; border-top: 1px solid var(--line); }.drawer-tabs button { flex: 1; padding: 5px 2px; border: 1px solid #234456; background: transparent; color: var(--muted); font: 8px var(--mono); }.drawer-tabs button:hover { color: var(--cyan); border-color: var(--cyan); }
|
| 53 |
+
|
| 54 |
+
.terminal-phone { position: fixed; z-index: 10; right: 22px; bottom: 91px; width: 300px; min-height: 405px; padding: 17px 12px 13px; border: 3px solid #2e3c48; border-radius: 27px; background: #060b10; box-shadow: 0 18px 50px rgba(0,0,0,.65), 0 0 0 4px rgba(1, 6, 10, .75), inset 0 0 22px rgba(96, 177, 211, .08); opacity: 0; pointer-events: none; transform: translateY(14px) rotate(1deg); transition: opacity .25s ease, transform .25s ease; }.terminal-phone.is-open { opacity: 1; pointer-events: auto; transform: translateY(0) rotate(1deg); }.phone-speaker { position: absolute; top: 8px; left: 50%; width: 55px; height: 4px; transform: translateX(-50%); border-radius: 5px; background: #29343d; }.terminal-top { display: flex; justify-content: space-between; align-items: center; padding: 3px 4px 11px; color: #d3e6eb; font: 10px var(--mono); }.terminal-top .icon-button { width: 22px; height: 22px; }.terminal-screen { min-height: 344px; padding: 11px; border: 1px solid #213844; border-radius: 7px; background: #020608; }.terminal-header { display: grid; gap: 4px; padding-bottom: 10px; border-bottom: 1px solid #1c303b; }.terminal-header strong { color: var(--white); font: 13px var(--mono); }.terminal-header small { color: var(--muted); font: 8px var(--mono); }.terminal-output { height: 222px; overflow: auto; padding-top: 9px; color: #8bd35e; font: 9px/1.5 var(--mono); }.terminal-line.command { color: var(--cyan); }.terminal-form { display: flex; gap: 5px; align-items: center; padding: 8px 0; border-top: 1px solid #1c303b; color: var(--green); font: 10px var(--mono); }.terminal-form input { flex: 1; min-width: 0; padding: 3px; border: 0; outline: 0; background: transparent; color: var(--white); font: 10px var(--mono); }.terminal-form button { border: 0; background: transparent; color: var(--cyan); font-size: 17px; }.terminal-shortcuts { display: flex; flex-wrap: wrap; gap: 4px; }.terminal-shortcuts button { padding: 4px 5px; border: 1px solid #244655; border-radius: 3px; background: #091a25; color: var(--muted); font: 8px var(--mono); }.terminal-shortcuts button:hover { color: var(--cyan); border-color: var(--cyan); }.phone-home { width: 54px; height: 4px; margin: 11px auto 0; border-radius: 5px; background: #29343d; }
|
| 55 |
+
.toolbelt { position: absolute; z-index: 6; left: 50%; bottom: 61px; display: grid; grid-template-columns: repeat(8, 1fr); width: min(560px, calc(100% - 620px)); transform: translateX(-50%); overflow: hidden; }.tool-slot { min-width: 0; display: grid; place-items: center; gap: 2px; padding: 7px 4px 5px; border: 0; border-right: 1px solid #29495b; background: rgba(6, 18, 29, .94); color: var(--muted); }.tool-slot:last-child { border-right: 0; }.tool-slot:hover, .tool-slot.is-active { background: rgba(16, 57, 69, .96); color: var(--tool-color); box-shadow: inset 0 -2px 0 var(--tool-color); }.tool-slot b { color: var(--tool-color); font-size: 24px; line-height: 1; text-shadow: 0 0 9px var(--tool-color); }.tool-slot strong { overflow: hidden; max-width: 100%; color: var(--white); font: 8px var(--mono); text-align: center; text-overflow: ellipsis; white-space: nowrap; }.tool-slot small { color: var(--muted); font: 7px var(--mono); }.tool-key { position: absolute; margin: 0 0 -49px 0; color: #6a8ca3; font: 8px var(--mono); }
|
| 56 |
+
.software-shelf { position: relative; z-index: 8; width: min(1500px, 100%); margin: 8px auto 0; padding: 10px 14px 12px; border: 1px solid #17415b; border-radius: 10px; background: rgba(5, 17, 29, .92); }.shelf-title { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }.shelf-title span { color: var(--white); font: 700 13px var(--mono); letter-spacing: .04em; }.shelf-title small { color: var(--cyan); font: 8px var(--mono); }.object-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 6px; }.object-card { min-height: 105px; display: grid; place-items: center; align-content: center; gap: 3px; padding: 7px 4px; border: 1px solid #1b4056; border-radius: 6px; background: #071521; }.object-card:hover { border-color: var(--cyan); background: #0d2635; transform: translateY(-2px); }.object-card strong { color: var(--white); font: 8px var(--mono); text-align: center; }.object-card small { color: var(--muted); font: 8px var(--mono); }.object-art { display: grid; place-items: center; width: 51px; height: 58px; color: var(--cyan); font-size: 42px; text-shadow: 0 0 13px currentColor; }.hammer-art { color: var(--orange); }.backpack-art { color: #ff7561; }.box-art { color: var(--blue); }.tablet-art { color: var(--pink); }.key-art { color: var(--gold); }.vault-art { color: #a9b1b7; }.rocket-art { color: var(--orange); }.touch-controls { display: none; }.disclaimer { position: relative; z-index: 7; display: flex; justify-content: space-between; width: min(1500px, 100%); margin: 9px auto 0; color: #617f94; font: 8px var(--mono); letter-spacing: .08em; }
|
| 57 |
+
|
| 58 |
+
@keyframes window-blink { 0%, 77% { opacity: .45; } 86% { opacity: 1; } 92%, 100% { opacity: .58; } }
|
| 59 |
+
@keyframes quest-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.09); box-shadow: 0 0 28px rgba(255, 201, 91, .88); } }
|
| 60 |
+
@keyframes crane-pulse { 0%, 100% { opacity: .35; transform: scale(.7); } 50% { opacity: 1; transform: scale(1.1); } }
|
| 61 |
+
@keyframes bot-walk { 0%, 100% { transform: translate(0, 0); } 50% { transform: translate(22px, -4px); } }
|
| 62 |
+
|
| 63 |
+
@media (max-width: 1240px) {
|
| 64 |
+
.left-rail { width: 222px; }.right-rail { width: 245px; }.world-stage { padding-left: 235px; padding-right: 255px; }.world-viewport { height: 650px; }.toolbelt { width: min(520px, calc(100% - 500px)); }.inspector-drawer { right: 262px; }.feedback-line { left: 260px; right: 260px; }.movement-help { right: 260px; }.nearby-prompt { left: 260px; }
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
@media (max-width: 980px) {
|
| 68 |
+
.topbar { grid-template-columns: 1fr minmax(230px, 1fr) auto; gap: 12px; }.left-rail { position: relative; top: auto; left: auto; width: min(100%, 650px); margin: 0 auto 10px; }.left-rail .how-it-works { display: none; }.world-stage { min-height: 600px; padding: 0; }.world-viewport { height: 650px; }.right-rail { position: relative; top: auto; right: auto; width: 100%; grid-template-columns: 1fr 1fr 1fr; margin: 10px auto 0; }.event-panel { max-height: 255px; }.inspector-drawer { position: fixed; top: auto; right: 15px; bottom: 160px; width: min(360px, calc(100% - 30px)); }.toolbelt { position: relative; left: auto; bottom: auto; width: min(640px, 100%); margin: 10px auto 0; transform: none; }.software-shelf { margin-top: 10px; }.feedback-line { left: 0; right: 0; bottom: 0; }.movement-help { right: 12px; bottom: 42px; }.nearby-prompt { left: 12px; bottom: 42px; }.disclaimer { margin-top: 8px; }.world-stage .stage-heading { padding-inline: 2px; }
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
@media (max-width: 680px) {
|
| 72 |
+
.game-shell { padding: 10px 9px 9px; }.topbar { grid-template-columns: 1fr auto; }.repo-search-wrap { grid-column: 1 / -1; grid-row: 2; }.simulated-pill { display: none; }.brand-lockup strong { font-size: 17px; }.stage-heading { align-items: flex-start; flex-direction: column; gap: 6px; }.stage-heading h2 { font-size: 17px; }.world-viewport { height: 500px; }.world-canvas { transform: scale(.7) !important; transform-origin: 0 0; }.right-rail { grid-template-columns: 1fr; }.map-panel, .agents-panel { min-height: 0; }.event-panel { max-height: 210px; }.world-stage { min-height: 550px; }.world-stage .movement-help { display: none; }.nearby-prompt { bottom: 42px; }.toolbelt { grid-template-columns: repeat(4, 1fr); }.tool-slot { min-height: 54px; }.tool-slot b { font-size: 20px; }.tool-key { margin-bottom: -39px; }.object-grid { grid-template-columns: repeat(4, 1fr); }.object-card { min-height: 90px; }.object-art { width: 40px; height: 43px; font-size: 30px; }.shelf-title { align-items: flex-start; flex-direction: column; gap: 3px; }.disclaimer { flex-direction: column; gap: 4px; }.terminal-phone { right: 14px; bottom: 130px; width: min(300px, calc(100% - 28px)); }.inspector-drawer { right: 9px; bottom: 128px; width: calc(100% - 18px); }.left-rail { padding: 14px; }.component-list { grid-template-columns: 1fr 1fr; }.component-list li { font-size: 10px; }.component-list li b { width: 17px; height: 17px; }
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
@media (prefers-reduced-motion: reduce) {
|
| 76 |
+
*, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
/* Browser-native opening scene and reality plates. These are local assets from
|
| 80 |
+
phase2-unreal/ReferenceSource, so the playable build stays self-contained. */
|
| 81 |
+
.opening-scene { position: fixed; z-index: 100; inset: 0; display: grid; place-items: end start; overflow: hidden; background: #02050d; transition: opacity .55s ease, visibility .55s ease; }
|
| 82 |
+
.opening-scene.is-dismissed { opacity: 0; visibility: hidden; pointer-events: none; }
|
| 83 |
+
.opening-video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; background: #050816; filter: saturate(1.15) contrast(1.05) brightness(.72); }
|
| 84 |
+
.opening-vignette { position: absolute; inset: 0; pointer-events: none; background: radial-gradient(circle at 55% 44%, transparent 0 24%, rgba(4, 5, 18, .22) 54%, rgba(2, 4, 12, .92) 100%), linear-gradient(90deg, rgba(2, 5, 13, .9), transparent 55%, rgba(2, 5, 13, .35)); }
|
| 85 |
+
.opening-copy { position: relative; z-index: 2; width: min(630px, calc(100% - 48px)); margin: 0 0 11vh 7vw; padding: 24px 28px 27px; border: 1px solid rgba(125, 232, 255, .35); border-radius: 16px; background: linear-gradient(135deg, rgba(4, 14, 32, .86), rgba(20, 10, 46, .72)); box-shadow: 0 0 42px rgba(32, 145, 255, .18), inset 0 0 30px rgba(111, 73, 255, .1); backdrop-filter: blur(7px); }
|
| 86 |
+
.opening-copy h1 { margin: 6px 0 7px; color: #f5fbff; font: 800 clamp(50px, 10vw, 104px)/.86 var(--mono); letter-spacing: -.08em; text-shadow: 0 0 28px rgba(181, 140, 255, .75); }
|
| 87 |
+
.opening-copy p { margin: 0 0 18px; color: #d5e6f4; font-size: clamp(16px, 2vw, 22px); }
|
| 88 |
+
.opening-meta { display: flex; flex-wrap: wrap; gap: 9px 18px; color: #9ab6cf; font: 10px var(--mono); letter-spacing: .06em; }
|
| 89 |
+
.opening-meta i { display: inline-block; width: 7px; height: 7px; margin-right: 5px; border-radius: 50%; background: #91e85e; box-shadow: 0 0 12px #91e85e; }
|
| 90 |
+
.opening-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-top: 24px; }
|
| 91 |
+
.opening-enter, .opening-sound { border: 1px solid rgba(125, 232, 255, .55); border-radius: 8px; color: #effbff; background: rgba(24, 88, 128, .52); font: 700 12px var(--mono); letter-spacing: .06em; cursor: pointer; }
|
| 92 |
+
.opening-enter { padding: 14px 17px; box-shadow: 0 0 18px rgba(125, 232, 255, .2); }
|
| 93 |
+
.opening-enter b { margin-left: 14px; color: #7de8ff; font-size: 19px; }
|
| 94 |
+
.opening-sound { padding: 13px 14px; color: #abc1d7; background: rgba(5, 10, 25, .45); }
|
| 95 |
+
.opening-enter:hover, .opening-sound:hover { border-color: #b58cff; box-shadow: 0 0 22px rgba(181, 140, 255, .3); }
|
| 96 |
+
.opening-copy > small { display: block; margin-top: 13px; color: #7e9bb6; font: 10px var(--mono); }
|
| 97 |
+
.reality-deck { position: absolute; z-index: 5; right: 27px; bottom: 27px; left: 27px; padding: 9px; border: 1px solid rgba(125, 232, 255, .24); border-radius: 10px; background: linear-gradient(90deg, rgba(4, 15, 29, .9), rgba(8, 19, 41, .72)); box-shadow: 0 0 28px rgba(12, 89, 125, .16); backdrop-filter: blur(5px); }
|
| 98 |
+
.reality-deck-heading { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 0 3px 7px; color: #9edff4; font: 10px var(--mono); letter-spacing: .07em; }
|
| 99 |
+
.reality-deck-heading small { color: #6c8aa4; font-size: 8px; }
|
| 100 |
+
.reality-deck-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 7px; }
|
| 101 |
+
.reality-plate { display: block; width: 100%; height: 72px; object-fit: cover; border: 1px solid rgba(125, 232, 255, .22); border-radius: 6px; background: #081322; filter: saturate(1.16) contrast(1.04); }
|
| 102 |
+
@media (max-width: 680px) {
|
| 103 |
+
.opening-copy { width: calc(100% - 24px); margin: 0 12px 24px; padding: 18px; }
|
| 104 |
+
.opening-copy h1 { font-size: 53px; }
|
| 105 |
+
.reality-deck { right: 12px; bottom: 12px; left: 12px; }
|
| 106 |
+
.reality-deck-heading small { display: none; }
|
| 107 |
+
.reality-plate { height: 48px; }
|
| 108 |
+
}
|
dist/ui-reality.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import {
|
| 2 |
+
COLLECTIONS,
|
| 3 |
+
EVENT_LABELS,
|
| 4 |
+
EVENT_TYPES,
|
| 5 |
+
TOOLBELT,
|
| 6 |
+
commandTranscript,
|
| 7 |
+
formatTime
|
| 8 |
+
} from './shared.js';
|
| 9 |
+
|
| 10 |
+
function escapeHtml(value) {
|
| 11 |
+
return String(value).replace(/[&<>"']/g, (character) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[character]));
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
function byId(root, id) {
|
| 15 |
+
return root.getElementById(id);
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
function setText(root, id, value) {
|
| 19 |
+
const element = byId(root, id);
|
| 20 |
+
if (element) element.textContent = value;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
function selectedDistrict(state) {
|
| 24 |
+
return state.districts.find((district) => district.id === state.selectedDistrictId) || state.districts[0];
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
function selectedFile(state) {
|
| 28 |
+
const district = selectedDistrict(state);
|
| 29 |
+
return district.files.find((file) => file.id === state.selectedFileId) || district.files[0];
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
function eventMessage(event) {
|
| 33 |
+
return event.detail?.message || EVENT_LABELS[event.type] || event.type;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
function renderAgents(root, state) {
|
| 37 |
+
const agentList = byId(root, 'agent-list');
|
| 38 |
+
if (!agentList) return;
|
| 39 |
+
agentList.innerHTML = state.agents.map((agent) => `<li class="agent-row" style="--agent-color:${agent.color}">
|
| 40 |
+
<span class="agent-avatar">${agent.name.slice(0, 2)}</span><span class="agent-copy"><strong>${escapeHtml(agent.name)}</strong><small>${escapeHtml(agent.role)}</small><em>${escapeHtml(agent.task)}</em><i class="progress-line"><b style="width:${agent.progress}%"></b></i></span><span class="agent-state">● ${escapeHtml(agent.status)}</span>
|
| 41 |
+
</li>`).join('');
|
| 42 |
+
setText(root, 'active-agent-count', String(state.agents.length));
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
function renderEvents(root, state) {
|
| 46 |
+
const feed = byId(root, 'event-feed');
|
| 47 |
+
if (!feed) return;
|
| 48 |
+
feed.innerHTML = state.events.slice(0, 7).map((event) => `<li class="event-row event-${event.type.replace(/[^a-z]+/g, '-')}" data-event-type="${escapeHtml(event.type)}"><time>${escapeHtml(event.at || formatTime(event.tick))}</time><span class="event-dot"></span><p>${escapeHtml(eventMessage(event))}</p></li>`).join('');
|
| 49 |
+
setText(root, 'event-count', `${state.events.length} EVENTS`);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
function renderToolbelt(root, state) {
|
| 53 |
+
const toolbelt = byId(root, 'toolbelt');
|
| 54 |
+
if (!toolbelt) return;
|
| 55 |
+
toolbelt.innerHTML = TOOLBELT.map((tool) => `<button class="tool-slot${tool.id === state.activeTool ? ' is-active' : ''}" data-action="tool" data-tool-id="${tool.id}" style="--tool-color:${tool.color}" aria-label="${escapeHtml(tool.name)}: ${escapeHtml(tool.verb)}"><span class="tool-key">${tool.key}</span><b>${tool.icon}</b><strong>${escapeHtml(tool.name)}</strong><small>${escapeHtml(tool.verb)}</small></button>`).join('');
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
function renderStatus(root, state) {
|
| 59 |
+
setText(root, 'repo-visibility', state.repository.visibility);
|
| 60 |
+
setText(root, 'repo-branch', `BRANCH / ${state.repository.branch}`);
|
| 61 |
+
setText(root, 'build-status', state.repository.build);
|
| 62 |
+
setText(root, 'test-status', state.repository.tests);
|
| 63 |
+
setText(root, 'deploy-status', state.repository.deployment);
|
| 64 |
+
setText(root, 'world-clock', formatTime(state.tick));
|
| 65 |
+
setText(root, 'player-location', state.view === 'district' ? `INSIDE ${selectedDistrict(state).path}` : 'SPAWN PLAZA');
|
| 66 |
+
setText(root, 'feedback-line', state.feedback);
|
| 67 |
+
const playerLevel = Math.min(99, 12 + Math.floor(state.tick / 3));
|
| 68 |
+
setText(root, 'player-level', `LEVEL ${playerLevel} DEV`);
|
| 69 |
+
const xp = Math.min(100, 34 + state.tick * 2);
|
| 70 |
+
const xpBar = byId(root, 'xp-fill');
|
| 71 |
+
if (xpBar) xpBar.style.width = `${xp}%`;
|
| 72 |
+
setText(root, 'xp-label', `${xp} / 100 XP`);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
function renderFiles(state) {
|
| 76 |
+
const district = selectedDistrict(state);
|
| 77 |
+
return `<div class="collection-heading"><span>DIRECTORY</span><strong>${escapeHtml(district.path)}</strong><small>${escapeHtml(district.stat)}</small></div><div class="file-list">${district.files.map((file) => `<button class="file-row${file.id === state.selectedFileId ? ' is-active' : ''}" data-action="select-file" data-file-id="${file.id}"><span class="file-icon">⌁</span><strong>${escapeHtml(file.name)}</strong><small>${escapeHtml(file.kind)} · ${escapeHtml(file.size)}</small><b>›</b></button>`).join('')}</div>`;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
function renderCommits(state) {
|
| 81 |
+
const commits = state.repository.commits || [];
|
| 82 |
+
return `<div class="collection-heading"><span>HISTORY ROAD</span><strong>RECENT COMMITS</strong><small>${commits.length} simulated events</small></div><div class="commit-list">${commits.map((commit) => `<button class="commit-row" data-action="commit-detail" data-commit-id="${commit.id}"><span class="commit-node"></span><span><strong>${escapeHtml(commit.hash)}</strong><small>${escapeHtml(commit.message)}</small></span><em>${escapeHtml(commit.author)}</em></button>`).join('')}</div>`;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
function renderIssues(state) {
|
| 86 |
+
const issues = state.repository.issues || [];
|
| 87 |
+
return `<div class="collection-heading"><span>QUEST BOARD</span><strong>OPEN ISSUES</strong><small>Quests waiting in the city</small></div><div class="issue-list">${issues.map((issue) => `<button class="issue-row" data-action="issue-detail" data-issue-id="${issue.id}"><span class="issue-marker">!</span><span><strong>${escapeHtml(issue.title)}</strong><small>${escapeHtml(issue.detail)}</small></span><em>${escapeHtml(issue.status)}</em></button>`).join('')}</div>`;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
function renderPullRequests(state) {
|
| 91 |
+
const prs = state.repository.pullRequests || [];
|
| 92 |
+
return `<div class="collection-heading"><span>MERGE GATE</span><strong>PULL REQUESTS</strong><small>Doors into the next branch</small></div><div class="pr-list">${prs.map((pr) => `<button class="pr-row" data-action="pr-detail" data-pr-id="${pr.id}"><span class="pr-marker">↔</span><span><strong>${escapeHtml(pr.title)}</strong><small>${escapeHtml(pr.detail)}</small></span><em>${escapeHtml(pr.status)}</em></button>`).join('')}</div>`;
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
function renderBuild(state) {
|
| 96 |
+
return `<div class="collection-heading"><span>AUTOMATION CENTER</span><strong>BUILD & TEST</strong><small>All values are simulated browser-local signals</small></div><div class="build-stack"><div><span>BUILD #812</span><b class="status-${state.repository.build.toLowerCase()}">${escapeHtml(state.repository.build)}</b></div><div><span>INSPECTORS</span><b class="status-${state.repository.tests.toLowerCase().split(' ')[0]}">${escapeHtml(state.repository.tests)}</b></div><div><span>PAGES RUNWAY</span><b>${escapeHtml(state.repository.deployment)}</b></div><div class="pipeline-steps"><i class="done">SOURCE</i><i class="done">COMPILE</i><i class="done">TEST</i><i class="active">DEPLOY</i></div></div>`;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
function renderRecord(state) {
|
| 100 |
+
const record = state.selectedRecord;
|
| 101 |
+
if (!record) return renderBuild(state);
|
| 102 |
+
if (record.type === 'commit') {
|
| 103 |
+
const item = (state.repository.commits || []).find((candidate) => candidate.id === record.id);
|
| 104 |
+
return `<div class="collection-heading"><span>COMMIT EVENT</span><strong>${escapeHtml(item?.hash || record.id)}</strong><small>Historical event made physical</small></div><div class="record-card"><b>${escapeHtml(item?.message || 'Commit selected')}</b><p>This simulated commit is a construction event. The repository builder routes its changed files into the city.</p><em>author · ${escapeHtml(item?.author || 'builder')}</em></div><button class="panel-action" data-action="select-collection" data-collection="commits">← Back to commit history</button>`;
|
| 105 |
+
}
|
| 106 |
+
if (record.type === 'issue') {
|
| 107 |
+
const item = (state.repository.issues || []).find((candidate) => candidate.id === record.id);
|
| 108 |
+
return `<div class="collection-heading"><span>ISSUE QUEST</span><strong>${escapeHtml(item?.title || record.id)}</strong><small>${escapeHtml(item?.status || 'OPEN')}</small></div><div class="record-card issue-record"><b>Quest objective</b><p>${escapeHtml(item?.detail || 'Inspect the open issue in the quest district.')}</p><em>marker active · browser-local simulation</em></div><button class="panel-action" data-action="select-collection" data-collection="issues">← Back to issue board</button>`;
|
| 109 |
+
}
|
| 110 |
+
const item = (state.repository.pullRequests || []).find((candidate) => candidate.id === record.id);
|
| 111 |
+
return `<div class="collection-heading"><span>PULL REQUEST DOOR</span><strong>${escapeHtml(item?.title || record.id)}</strong><small>${escapeHtml(item?.status || 'REVIEW')}</small></div><div class="record-card pr-record"><b>Merge gate</b><p>${escapeHtml(item?.detail || 'The gate is waiting for review.')}</p><em>checks passing · browser-local simulation</em></div><button class="panel-action" data-action="select-collection" data-collection="prs">← Back to pull requests</button>`;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
function renderInspector(root, state, nearby) {
|
| 115 |
+
const panel = byId(root, 'inspector-content');
|
| 116 |
+
if (!panel) return;
|
| 117 |
+
const district = selectedDistrict(state);
|
| 118 |
+
const file = selectedFile(state);
|
| 119 |
+
const collection = state.selectedCollection;
|
| 120 |
+
let content = '';
|
| 121 |
+
if (state.activePanel === 'file' || (state.activePanel === 'collection' && collection === 'files')) {
|
| 122 |
+
content = `<div class="inspect-header"><span class="eyebrow">${escapeHtml(file.kind)} OBJECT</span><h2>${escapeHtml(file.name)}</h2><p>${escapeHtml(file.detail)}</p></div><pre class="code-preview" aria-label="Selected file preview">${escapeHtml(file.code)}</pre><button class="panel-action" data-action="select-collection" data-collection="files">← Back to ${escapeHtml(district.path)}</button>`;
|
| 123 |
+
} else if (state.activePanel === 'record') {
|
| 124 |
+
content = renderRecord(state);
|
| 125 |
+
} else if (state.activePanel === 'collection') {
|
| 126 |
+
content = collection === 'commits' ? renderCommits(state) : collection === 'issues' ? renderIssues(state) : collection === 'prs' ? renderPullRequests(state) : renderBuild(state);
|
| 127 |
+
} else if (state.activePanel === 'inspector' || state.activePanel === 'world') {
|
| 128 |
+
const canEnter = nearby?.district?.id === district.id && nearby.inRange;
|
| 129 |
+
content = `<div class="inspect-header"><span class="eyebrow">${state.view === 'district' ? 'CURRENT DISTRICT' : 'NEAREST DISTRICT'}</span><h2>${escapeHtml(district.title)}</h2><p>${escapeHtml(district.summary)}</p><div class="district-metrics"><span>${escapeHtml(district.path)}</span><span>${escapeHtml(district.stat)}</span><span style="color:${district.accent}">${Math.round((state.construction[district.id] || 0) * 100)}% BUILT</span></div></div><div class="inspector-actions"><button class="panel-action primary" data-action="open-inspector">INSPECT FILES</button><button class="panel-action" data-action="enter-district" ${canEnter ? '' : 'disabled'}>${canEnter ? 'ENTER DISTRICT' : 'WALK CLOSER TO ENTER'}</button></div><div class="mini-file-list">${district.files.slice(0, 3).map((item) => `<button data-action="select-file" data-file-id="${item.id}"><span>⌘</span>${escapeHtml(item.name)}<b>›</b></button>`).join('')}</div>`;
|
| 130 |
+
} else {
|
| 131 |
+
content = `<div class="inspect-header"><span class="eyebrow">${escapeHtml(district.path)}</span><h2>Repository objects</h2><p>Select a building, tool, or activity to inspect the physical software around you.</p></div>`;
|
| 132 |
+
}
|
| 133 |
+
panel.innerHTML = content;
|
| 134 |
+
const nearbyPrompt = byId(root, 'nearby-prompt');
|
| 135 |
+
if (nearbyPrompt) {
|
| 136 |
+
nearbyPrompt.innerHTML = nearby?.district && nearby.inRange ? `<span>NEARBY</span><strong>${escapeHtml(nearby.district.name)}</strong><small>PRESS E / ENTER TO ENTER</small>` : `<span>WALKABLE CITY</span><strong>${escapeHtml(state.player.facing.toUpperCase())}</strong><small>WASD · ARROWS TO NAVIGATE</small>`;
|
| 137 |
+
nearbyPrompt.classList.toggle('is-ready', Boolean(nearby?.inRange));
|
| 138 |
+
}
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
function renderTerminal(root, state) {
|
| 142 |
+
const overlay = byId(root, 'terminal-phone');
|
| 143 |
+
if (!overlay) return;
|
| 144 |
+
overlay.classList.toggle('is-open', state.terminal.open);
|
| 145 |
+
overlay.setAttribute('aria-hidden', String(!state.terminal.open));
|
| 146 |
+
const output = byId(root, 'terminal-output');
|
| 147 |
+
if (output) output.innerHTML = state.terminal.lines.map((line) => `<div class="terminal-line${line.startsWith('$') ? ' command' : ''}">${escapeHtml(line)}</div>`).join('');
|
| 148 |
+
const input = byId(root, 'terminal-input');
|
| 149 |
+
if (input && input.value !== state.terminal.command) input.value = '';
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
function renderSearch(root) {
|
| 153 |
+
const search = byId(root, 'repo-search');
|
| 154 |
+
if (!search) return;
|
| 155 |
+
const query = search.value.trim().toLowerCase();
|
| 156 |
+
root.querySelectorAll('.world-building').forEach((building) => {
|
| 157 |
+
building.classList.toggle('is-search-hidden', Boolean(query) && !building.textContent.toLowerCase().includes(query));
|
| 158 |
+
});
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
export function renderHud(root, state, nearby) {
|
| 162 |
+
renderAgents(root, state);
|
| 163 |
+
renderEvents(root, state);
|
| 164 |
+
renderToolbelt(root, state);
|
| 165 |
+
renderStatus(root, state);
|
| 166 |
+
renderInspector(root, state, nearby);
|
| 167 |
+
renderTerminal(root, state);
|
| 168 |
+
renderSearch(root);
|
| 169 |
+
const drawer = byId(root, 'inspector-drawer');
|
| 170 |
+
if (drawer) drawer.classList.toggle('is-open', state.activePanel !== 'world' && state.activePanel !== 'terminal');
|
| 171 |
+
const shell = byId(root, 'game-shell');
|
| 172 |
+
if (shell) shell.dataset.view = state.view;
|
| 173 |
+
setText(root, 'selected-tool-label', (TOOLBELT.find((tool) => tool.id === state.activeTool) || TOOLBELT[0]).name);
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
export function bindUi(root, dispatch) {
|
| 177 |
+
root.addEventListener('click', (event) => {
|
| 178 |
+
const target = event.target.closest('[data-action], [data-district-id]');
|
| 179 |
+
if (!target) return;
|
| 180 |
+
const action = target.dataset.action || (target.dataset.districtId ? 'select-district' : '');
|
| 181 |
+
if (action === 'select-district') dispatch({ type: EVENT_TYPES.SELECT_DISTRICT, districtId: target.dataset.districtId });
|
| 182 |
+
if (action === 'open-inspector') dispatch({ type: EVENT_TYPES.OPEN_INSPECTOR });
|
| 183 |
+
if (action === 'enter-district') dispatch({ type: EVENT_TYPES.ENTER_DISTRICT, districtId: target.dataset.districtId });
|
| 184 |
+
if (action === 'close-inspector') dispatch({ type: EVENT_TYPES.CLOSE_INSPECTOR });
|
| 185 |
+
if (action === 'select-file') dispatch({ type: EVENT_TYPES.SELECT_FILE, fileId: target.dataset.fileId });
|
| 186 |
+
if (action === 'select-collection') dispatch({ type: EVENT_TYPES.SELECT_COLLECTION, collection: target.dataset.collection });
|
| 187 |
+
if (action === 'commit-detail') dispatch({ type: EVENT_TYPES.SELECT_RECORD, recordType: 'commit', recordId: target.dataset.commitId });
|
| 188 |
+
if (action === 'issue-detail') dispatch({ type: EVENT_TYPES.SELECT_RECORD, recordType: 'issue', recordId: target.dataset.issueId });
|
| 189 |
+
if (action === 'pr-detail') dispatch({ type: EVENT_TYPES.SELECT_RECORD, recordType: 'pr', recordId: target.dataset.prId });
|
| 190 |
+
if (action === 'move') dispatch({ type: EVENT_TYPES.PLAYER_MOVE, dx: target.dataset.dx, dy: target.dataset.dy });
|
| 191 |
+
if (action === 'tool') {
|
| 192 |
+
const tool = TOOLBELT.find((item) => item.id === target.dataset.toolId);
|
| 193 |
+
dispatch({ type: EVENT_TYPES.USE_TOOL, toolId: target.dataset.toolId, message: tool?.verb ? `${tool.name}: ${tool.verb}.` : undefined });
|
| 194 |
+
}
|
| 195 |
+
if (action === 'open-terminal') dispatch({ type: EVENT_TYPES.OPEN_TERMINAL });
|
| 196 |
+
if (action === 'close-terminal') dispatch({ type: EVENT_TYPES.CLOSE_TERMINAL });
|
| 197 |
+
if (action === 'command') dispatch({ type: EVENT_TYPES.RUN_COMMAND, command: target.dataset.command, lines: commandTranscript(target.dataset.command, window.gitWorldState || { selectedFileId: 'README.md' }) });
|
| 198 |
+
});
|
| 199 |
+
|
| 200 |
+
root.addEventListener('submit', (event) => {
|
| 201 |
+
if (!event.target.matches('#terminal-form')) return;
|
| 202 |
+
event.preventDefault();
|
| 203 |
+
const input = byId(root, 'terminal-input');
|
| 204 |
+
const command = input?.value.trim() || '';
|
| 205 |
+
if (command) dispatch({ type: EVENT_TYPES.RUN_COMMAND, command, lines: commandTranscript(command, window.gitWorldState || { selectedFileId: 'README.md' }) });
|
| 206 |
+
});
|
| 207 |
+
|
| 208 |
+
const search = byId(root, 'repo-search');
|
| 209 |
+
if (search) search.addEventListener('input', () => renderSearch(root));
|
| 210 |
+
}
|
dist/world-architect.js
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import {
|
| 2 |
+
clamp,
|
| 3 |
+
clone,
|
| 4 |
+
EVENT_TYPES,
|
| 5 |
+
makeEvent
|
| 6 |
+
} from './shared.js';
|
| 7 |
+
|
| 8 |
+
export const WALKABLE_BOUNDS = Object.freeze({ minX: 70, maxX: 1530, minY: 90, maxY: 850 });
|
| 9 |
+
|
| 10 |
+
export const DISTRICTS = Object.freeze([
|
| 11 |
+
{
|
| 12 |
+
id: 'src',
|
| 13 |
+
name: 'SRC DISTRICT',
|
| 14 |
+
path: '/src',
|
| 15 |
+
title: 'Central Kernel Tower',
|
| 16 |
+
glyph: '⌘',
|
| 17 |
+
accent: '#62f2d2',
|
| 18 |
+
x: 565,
|
| 19 |
+
y: 230,
|
| 20 |
+
width: 255,
|
| 21 |
+
height: 220,
|
| 22 |
+
summary: 'The living source tree: modules, kernel code, and the repository entry point.',
|
| 23 |
+
stat: '42 files · 213 commits',
|
| 24 |
+
files: [
|
| 25 |
+
{ id: 'main-rs', name: 'main.rs', kind: 'Rust', size: '8.4 KB', detail: 'Repository bootstrap and agent scheduler.', code: 'fn main() {\n let world = GitWorld::spawn();\n world.walk();\n}' },
|
| 26 |
+
{ id: 'kernel-cu', name: 'kernel.cu', kind: 'CUDA', size: '24.1 KB', detail: 'The compute floor is under construction.', code: '__global__ void kernel_step(Agent agent) {\n agent.advance();\n}' },
|
| 27 |
+
{ id: 'lib-rs', name: 'lib.rs', kind: 'Rust', size: '12.7 KB', detail: 'Shared repository primitives and event contracts.', code: 'pub mod events;\npub mod districts;\npub mod builders;' },
|
| 28 |
+
{ id: 'mod-rs', name: 'mod.rs', kind: 'Rust', size: '2.1 KB', detail: 'District module registry.', code: 'pub const DISTRICTS: &[&str] = &[\n "src", "tests", "docs"\n];' }
|
| 29 |
+
]
|
| 30 |
+
},
|
| 31 |
+
{
|
| 32 |
+
id: 'tests',
|
| 33 |
+
name: 'TEST DISTRICT',
|
| 34 |
+
path: '/tests',
|
| 35 |
+
title: 'Test Factory',
|
| 36 |
+
glyph: '✓',
|
| 37 |
+
accent: '#91e85e',
|
| 38 |
+
x: 230,
|
| 39 |
+
y: 370,
|
| 40 |
+
width: 235,
|
| 41 |
+
height: 185,
|
| 42 |
+
summary: 'Inspection robots verify each build before the city opens another street.',
|
| 43 |
+
stat: '142 passing · 0 failing',
|
| 44 |
+
files: [
|
| 45 |
+
{ id: 'world-test', name: 'world.test.js', kind: 'Test', size: '9.7 KB', detail: 'Deterministic movement and event checks.', code: 'assert.equal(world.tick, 12);\nassert.equal(events.length, 4);' },
|
| 46 |
+
{ id: 'contracts-test', name: 'contracts.test.js', kind: 'Test', size: '6.2 KB', detail: 'Shared UI/world event contract checks.', code: 'assert.ok(EVENT_TYPES.PLAYER_MOVE);\nassert.ok(EVENT_TYPES.BUILD_COMPLETED);' },
|
| 47 |
+
{ id: 'fixtures', name: 'fixtures/', kind: 'Directory', size: '18 items', detail: 'Small simulated repository fixtures.', code: 'fixture("pax-coder");' }
|
| 48 |
+
]
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
id: 'docs',
|
| 52 |
+
name: 'DOCS LIBRARY',
|
| 53 |
+
path: '/docs',
|
| 54 |
+
title: 'Documentation Library',
|
| 55 |
+
glyph: '▤',
|
| 56 |
+
accent: '#ffcf68',
|
| 57 |
+
x: 1085,
|
| 58 |
+
y: 245,
|
| 59 |
+
width: 240,
|
| 60 |
+
height: 205,
|
| 61 |
+
summary: 'A warm archive of architecture decisions, guides, and the city map legend.',
|
| 62 |
+
stat: '18 guides · 96% coverage',
|
| 63 |
+
files: [
|
| 64 |
+
{ id: 'architecture', name: 'architecture.md', kind: 'Markdown', size: '14.2 KB', detail: 'The frontend event-driven architecture.', code: '# GitWorld architecture\nInput -> simulation -> state -> HUD' },
|
| 65 |
+
{ id: 'contributing', name: 'contributing.md', kind: 'Markdown', size: '4.8 KB', detail: 'How builders add a district.', code: '1. Choose a street\n2. Add a contract\n3. Run the inspectors' },
|
| 66 |
+
{ id: 'readme', name: 'README.md', kind: 'Markdown', size: '8.3 KB', detail: 'The repository welcome sign.', code: '# pax-coder\nA simulated repository world.' }
|
| 67 |
+
]
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
id: 'issues',
|
| 71 |
+
name: 'ISSUE QUESTS',
|
| 72 |
+
path: '/issues',
|
| 73 |
+
title: 'Issue Quest Board',
|
| 74 |
+
glyph: '!',
|
| 75 |
+
accent: '#ff9f58',
|
| 76 |
+
x: 865,
|
| 77 |
+
y: 470,
|
| 78 |
+
width: 215,
|
| 79 |
+
height: 160,
|
| 80 |
+
summary: 'Open issues become quests with markers, owners, and a clear next action.',
|
| 81 |
+
stat: '3 open quests · 1 in progress',
|
| 82 |
+
files: [
|
| 83 |
+
{ id: 'issue-201', name: '#201 bug in proof', kind: 'Issue', size: 'open', detail: 'Reproduce the proof boundary on the kernel floor.', code: 'status: open\nowner: proof-agent' },
|
| 84 |
+
{ id: 'issue-202', name: '#202 add test', kind: 'Issue', size: 'open', detail: 'Add a regression test for agent movement.', code: 'status: open\nowner: test-agent' },
|
| 85 |
+
{ id: 'issue-203', name: '#203 docs update', kind: 'Issue', size: 'queued', detail: 'Document the deploy launch sequence.', code: 'status: queued\nowner: doc-agent' }
|
| 86 |
+
]
|
| 87 |
+
},
|
| 88 |
+
{
|
| 89 |
+
id: 'prs',
|
| 90 |
+
name: 'PR GATE',
|
| 91 |
+
path: '/pulls',
|
| 92 |
+
title: 'Pull Request Gate',
|
| 93 |
+
glyph: '↔',
|
| 94 |
+
accent: '#d37cff',
|
| 95 |
+
x: 600,
|
| 96 |
+
y: 475,
|
| 97 |
+
width: 210,
|
| 98 |
+
height: 180,
|
| 99 |
+
summary: 'A glowing merge door: review the change before opening the next branch road.',
|
| 100 |
+
stat: '2 open · review required',
|
| 101 |
+
files: [
|
| 102 |
+
{ id: 'pr-88', name: '#88 merge kernel fix', kind: 'Pull request', size: 'review', detail: 'Two approvals still needed before merge.', code: 'checks: passing\nreviews: 1 / 3' },
|
| 103 |
+
{ id: 'pr-91', name: '#91 docs refresh', kind: 'Pull request', size: 'ready', detail: 'Ready to merge after the documentation quest.', code: 'checks: passing\nreviews: 3 / 3' }
|
| 104 |
+
]
|
| 105 |
+
},
|
| 106 |
+
{
|
| 107 |
+
id: 'actions',
|
| 108 |
+
name: 'ACTIONS FACTORY',
|
| 109 |
+
path: '/.github',
|
| 110 |
+
title: 'Automation Center',
|
| 111 |
+
glyph: '⚙',
|
| 112 |
+
accent: '#66a9ff',
|
| 113 |
+
x: 1250,
|
| 114 |
+
y: 485,
|
| 115 |
+
width: 235,
|
| 116 |
+
height: 195,
|
| 117 |
+
summary: 'CI factories turn commits into tested artifacts and light the deploy runway.',
|
| 118 |
+
stat: 'BUILD #812 · passing',
|
| 119 |
+
files: [
|
| 120 |
+
{ id: 'ci-yml', name: 'ci.yml', kind: 'Actions', size: '5.2 KB', detail: 'Build, test, and artifact inspection pipeline.', code: 'jobs:\n build:\n test:\n deploy:' },
|
| 121 |
+
{ id: 'pages-yml', name: 'pages.yml', kind: 'Actions', size: '2.4 KB', detail: 'Static Pages deployment path.', code: 'deploy-pages: enabled' },
|
| 122 |
+
{ id: 'release', name: 'release.yml', kind: 'Actions', size: '3.0 KB', detail: 'Packages the next city snapshot.', code: 'tag: v0.8.2' }
|
| 123 |
+
]
|
| 124 |
+
}
|
| 125 |
+
]);
|
| 126 |
+
|
| 127 |
+
const INITIAL_AGENTS = [
|
| 128 |
+
{ id: 'world-architect', name: 'WORLD ARCHITECT', role: 'World geometry', task: 'Building /src district', status: 'BUILDING', progress: 78, color: '#62f2d2' },
|
| 129 |
+
{ id: 'repository-builder', name: 'REPOSITORY BUILDER', role: 'Repository mapping', task: 'Mapping 42 files', status: 'MAPPING', progress: 64, color: '#ffcf68' },
|
| 130 |
+
{ id: 'reality-integrator', name: 'REALITY/UI INTEGRATOR', role: 'Interaction layer', task: 'Connecting terminal', status: 'WIRING', progress: 52, color: '#d37cff' }
|
| 131 |
+
];
|
| 132 |
+
|
| 133 |
+
const INITIAL_EVENTS = [
|
| 134 |
+
{ id: 'seed-1', type: EVENT_TYPES.COMMIT, tick: 0, at: '00:00:00', detail: { message: '8f3a9c1 Fix kernel bounds', districtId: 'src' } },
|
| 135 |
+
{ id: 'seed-2', type: EVENT_TYPES.AGENT_PROGRESS, tick: 0, at: '00:00:00', detail: { message: 'WORLD ARCHITECT placed a new tower window.', agentId: 'world-architect' } },
|
| 136 |
+
{ id: 'seed-3', type: EVENT_TYPES.TEST_COMPLETED, tick: 0, at: '00:00:00', detail: { message: '142 inspectors passed the street check.', districtId: 'tests' } }
|
| 137 |
+
];
|
| 138 |
+
|
| 139 |
+
const SIMULATION_SEQUENCE = [
|
| 140 |
+
{ type: EVENT_TYPES.AGENT_PROGRESS, detail: { agentId: 'world-architect', message: 'WORLD ARCHITECT raised the /src tower scaffold.' } },
|
| 141 |
+
{ type: EVENT_TYPES.AGENT_PROGRESS, detail: { agentId: 'repository-builder', message: 'REPOSITORY BUILDER mapped kernel.cu into the compute floor.' } },
|
| 142 |
+
{ type: EVENT_TYPES.CONSTRUCTION, detail: { districtId: 'src', amount: 0.03, message: 'A new source window flickers online.' } },
|
| 143 |
+
{ type: EVENT_TYPES.TEST_STARTED, detail: { districtId: 'tests', message: 'Inspection robots started a deterministic street sweep.' } },
|
| 144 |
+
{ type: EVENT_TYPES.TEST_COMPLETED, detail: { districtId: 'tests', message: '142 tests passed. The test district lights turn green.' } },
|
| 145 |
+
{ type: EVENT_TYPES.BUILD_STARTED, detail: { districtId: 'actions', message: 'BUILD #813 started in the automation factory.' } },
|
| 146 |
+
{ type: EVENT_TYPES.AGENT_PROGRESS, detail: { agentId: 'reality-integrator', message: 'REALITY/UI INTEGRATOR wired a contextual inspect panel.' } },
|
| 147 |
+
{ type: EVENT_TYPES.BUILD_COMPLETED, detail: { districtId: 'actions', message: 'BUILD #813 completed and artifacts are staged.' } },
|
| 148 |
+
{ type: EVENT_TYPES.COMMIT, detail: { districtId: 'src', message: '2h4d7e2 Add proof sketch' } },
|
| 149 |
+
{ type: EVENT_TYPES.ISSUE, detail: { districtId: 'issues', message: '#202 quest marker activated: add test' } },
|
| 150 |
+
{ type: EVENT_TYPES.PULL_REQUEST, detail: { districtId: 'prs', message: '#91 merge gate is ready for review.' } },
|
| 151 |
+
{ type: EVENT_TYPES.DEPLOYMENT_STARTED, detail: { districtId: 'actions', message: 'Deploy rocket is fueling on the Pages runway.' } },
|
| 152 |
+
{ type: EVENT_TYPES.DEPLOYMENT_COMPLETED, detail: { districtId: 'actions', message: 'Static city snapshot deployed to the local launch pad.' } }
|
| 153 |
+
];
|
| 154 |
+
|
| 155 |
+
export function createWorldState(seed = 20260828) {
|
| 156 |
+
return {
|
| 157 |
+
seed,
|
| 158 |
+
tick: 0,
|
| 159 |
+
repository: {
|
| 160 |
+
owner: 'SNAPKITTYWEST',
|
| 161 |
+
name: 'pax-coder',
|
| 162 |
+
branch: 'main',
|
| 163 |
+
visibility: 'Public',
|
| 164 |
+
simulated: true,
|
| 165 |
+
stars: 213,
|
| 166 |
+
forks: 45,
|
| 167 |
+
languages: ['Rust', 'CUDA', 'Markdown'],
|
| 168 |
+
build: 'PASSING',
|
| 169 |
+
tests: '142 PASS',
|
| 170 |
+
deployment: 'STAGED',
|
| 171 |
+
commits: [
|
| 172 |
+
{ id: '8f3a9c1', hash: '8f3a9c1', message: 'Fix kernel bounds', author: 'AhmadParr' },
|
| 173 |
+
{ id: '2h4d7e2', hash: '2h4d7e2', message: 'Add proof sketch', author: 'proof-agent' },
|
| 174 |
+
{ id: '9ac1bd9', hash: '9ac1bd9', message: 'Update docs', author: 'doc-agent' },
|
| 175 |
+
{ id: '7d0c812', hash: '7d0c812', message: 'Wire agent event bus', author: 'world-architect' }
|
| 176 |
+
],
|
| 177 |
+
issues: [
|
| 178 |
+
{ id: '201', title: '#201 Bug in proof', detail: 'Reproduce the proof boundary on the kernel floor.', status: 'OPEN' },
|
| 179 |
+
{ id: '202', title: '#202 Add test', detail: 'Add a regression test for agent movement.', status: 'OPEN' },
|
| 180 |
+
{ id: '203', title: '#203 Docs update', detail: 'Document the deploy launch sequence.', status: 'QUEUED' }
|
| 181 |
+
],
|
| 182 |
+
pullRequests: [
|
| 183 |
+
{ id: '88', title: '#88 Merge kernel fix', detail: 'Two approvals still needed before merge.', status: 'REVIEW' },
|
| 184 |
+
{ id: '91', title: '#91 Docs refresh', detail: 'Ready to merge after the documentation quest.', status: 'READY' }
|
| 185 |
+
]
|
| 186 |
+
},
|
| 187 |
+
districts: clone(DISTRICTS),
|
| 188 |
+
agents: clone(INITIAL_AGENTS),
|
| 189 |
+
events: clone(INITIAL_EVENTS),
|
| 190 |
+
construction: { src: 0.78, tests: 1, docs: 0.96, issues: 0.7, prs: 0.82, actions: 0.88 },
|
| 191 |
+
player: { x: 800, y: 735, speed: 18, facing: 'north', step: 0 },
|
| 192 |
+
selectedDistrictId: 'src',
|
| 193 |
+
selectedFileId: 'main-rs',
|
| 194 |
+
selectedCollection: 'files',
|
| 195 |
+
selectedRecord: null,
|
| 196 |
+
activeTool: 'terminal',
|
| 197 |
+
activePanel: 'world',
|
| 198 |
+
terminal: { open: false, lines: ['$ local shell connected', '$ type `help` for commands'], command: '' },
|
| 199 |
+
feedback: 'Walk toward a district or click a glowing building.',
|
| 200 |
+
unlocked: false,
|
| 201 |
+
view: 'city'
|
| 202 |
+
};
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
function pushEvent(state, event) {
|
| 206 |
+
state.events = [event, ...state.events].slice(0, 14);
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
function findDistrict(state, districtId) {
|
| 210 |
+
return state.districts.find((district) => district.id === districtId) || state.districts[0];
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
function selectedFile(state) {
|
| 214 |
+
const district = findDistrict(state, state.selectedDistrictId);
|
| 215 |
+
return district.files.find((file) => file.id === state.selectedFileId) || district.files[0];
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
function circlesOverlap(x, y, district, margin = 28) {
|
| 219 |
+
const left = district.x - margin;
|
| 220 |
+
const right = district.x + district.width + margin;
|
| 221 |
+
const top = district.y - margin;
|
| 222 |
+
const bottom = district.y + district.height + margin;
|
| 223 |
+
return x >= left && x <= right && y >= top && y <= bottom;
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
export function getNearbyDistrict(state, range = 185) {
|
| 227 |
+
let nearest = null;
|
| 228 |
+
let distance = Number.POSITIVE_INFINITY;
|
| 229 |
+
for (const district of state.districts) {
|
| 230 |
+
const centerX = district.x + district.width / 2;
|
| 231 |
+
const centerY = district.y + district.height / 2;
|
| 232 |
+
const nextDistance = Math.hypot(state.player.x - centerX, state.player.y - centerY);
|
| 233 |
+
if (nextDistance < distance) {
|
| 234 |
+
nearest = district;
|
| 235 |
+
distance = nextDistance;
|
| 236 |
+
}
|
| 237 |
+
}
|
| 238 |
+
return { district: nearest, distance, inRange: distance <= range };
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
export function worldReducer(previous, event) {
|
| 242 |
+
const state = clone(previous);
|
| 243 |
+
switch (event.type) {
|
| 244 |
+
case EVENT_TYPES.PLAYER_MOVE: {
|
| 245 |
+
const dx = Number(event.dx) || 0;
|
| 246 |
+
const dy = Number(event.dy) || 0;
|
| 247 |
+
const magnitude = Math.hypot(dx, dy) || 1;
|
| 248 |
+
const nextX = clamp(state.player.x + (dx / magnitude) * state.player.speed, WALKABLE_BOUNDS.minX, WALKABLE_BOUNDS.maxX);
|
| 249 |
+
const nextY = clamp(state.player.y + (dy / magnitude) * state.player.speed, WALKABLE_BOUNDS.minY, WALKABLE_BOUNDS.maxY);
|
| 250 |
+
if (!state.districts.some((district) => circlesOverlap(nextX, nextY, district))) {
|
| 251 |
+
state.player.x = nextX;
|
| 252 |
+
state.player.y = nextY;
|
| 253 |
+
state.player.step = (state.player.step + 1) % 4;
|
| 254 |
+
}
|
| 255 |
+
state.player.facing = Math.abs(dx) > Math.abs(dy) ? (dx > 0 ? 'east' : 'west') : (dy > 0 ? 'south' : 'north');
|
| 256 |
+
return state;
|
| 257 |
+
}
|
| 258 |
+
case EVENT_TYPES.SELECT_DISTRICT: {
|
| 259 |
+
const district = findDistrict(state, event.districtId);
|
| 260 |
+
state.selectedDistrictId = district.id;
|
| 261 |
+
state.selectedFileId = district.files[0]?.id || null;
|
| 262 |
+
state.feedback = `${district.name} selected. Press Enter or E to inspect.`;
|
| 263 |
+
return state;
|
| 264 |
+
}
|
| 265 |
+
case EVENT_TYPES.SELECT_FILE:
|
| 266 |
+
state.selectedFileId = event.fileId;
|
| 267 |
+
state.activePanel = 'file';
|
| 268 |
+
state.feedback = `Inspecting ${selectedFile(state).name}`;
|
| 269 |
+
return state;
|
| 270 |
+
case EVENT_TYPES.SELECT_COLLECTION:
|
| 271 |
+
state.selectedCollection = event.collection;
|
| 272 |
+
state.activePanel = 'collection';
|
| 273 |
+
return state;
|
| 274 |
+
case EVENT_TYPES.SELECT_RECORD:
|
| 275 |
+
state.selectedRecord = { type: event.recordType, id: event.recordId };
|
| 276 |
+
state.activePanel = 'record';
|
| 277 |
+
return state;
|
| 278 |
+
case EVENT_TYPES.OPEN_INSPECTOR:
|
| 279 |
+
state.activePanel = 'inspector';
|
| 280 |
+
state.feedback = `Inspecting ${findDistrict(state, state.selectedDistrictId).path}`;
|
| 281 |
+
return state;
|
| 282 |
+
case EVENT_TYPES.CLOSE_INSPECTOR:
|
| 283 |
+
state.activePanel = 'world';
|
| 284 |
+
return state;
|
| 285 |
+
case EVENT_TYPES.ENTER_DISTRICT: {
|
| 286 |
+
const district = findDistrict(state, event.districtId || state.selectedDistrictId);
|
| 287 |
+
state.selectedDistrictId = district.id;
|
| 288 |
+
state.selectedFileId = district.files[0]?.id || null;
|
| 289 |
+
state.activePanel = 'inspector';
|
| 290 |
+
state.view = 'district';
|
| 291 |
+
state.feedback = `Entered ${district.path}. Code is physical here.`;
|
| 292 |
+
pushEvent(state, makeEvent(EVENT_TYPES.ENTER_DISTRICT, state.tick, { message: `Player entered ${district.path}.`, districtId: district.id }));
|
| 293 |
+
return state;
|
| 294 |
+
}
|
| 295 |
+
case EVENT_TYPES.OPEN_TERMINAL:
|
| 296 |
+
state.terminal.open = true;
|
| 297 |
+
state.activeTool = 'terminal';
|
| 298 |
+
state.activePanel = 'terminal';
|
| 299 |
+
state.feedback = 'Terminal phone online. Commands are simulated locally.';
|
| 300 |
+
return state;
|
| 301 |
+
case EVENT_TYPES.CLOSE_TERMINAL:
|
| 302 |
+
state.terminal.open = false;
|
| 303 |
+
if (state.activePanel === 'terminal') state.activePanel = 'world';
|
| 304 |
+
return state;
|
| 305 |
+
case EVENT_TYPES.RUN_COMMAND: {
|
| 306 |
+
const command = String(event.command || '').trim().toLowerCase();
|
| 307 |
+
if (!command) return state;
|
| 308 |
+
state.terminal.command = command;
|
| 309 |
+
state.terminal.lines = [...state.terminal.lines, ...event.lines].slice(-28);
|
| 310 |
+
state.feedback = 'Ran `' + command + '` in the simulated terminal.';
|
| 311 |
+
pushEvent(state, makeEvent(EVENT_TYPES.USE_TOOL, state.tick, { message: `Terminal ran ${command}.`, toolId: 'terminal' }));
|
| 312 |
+
return state;
|
| 313 |
+
}
|
| 314 |
+
case EVENT_TYPES.USE_TOOL: {
|
| 315 |
+
state.activeTool = event.toolId;
|
| 316 |
+
if (event.toolId === 'terminal') state.terminal.open = true;
|
| 317 |
+
if (event.toolId === 'git') { state.selectedCollection = 'commits'; state.activePanel = 'collection'; }
|
| 318 |
+
if (event.toolId === 'docker') { state.selectedCollection = 'build'; state.activePanel = 'collection'; }
|
| 319 |
+
if (event.toolId === 'code') state.activePanel = 'file';
|
| 320 |
+
if (event.toolId === 'ssh') { state.unlocked = true; state.activePanel = 'inspector'; }
|
| 321 |
+
if (event.toolId === 'database') { state.selectedCollection = 'files'; state.activePanel = 'collection'; }
|
| 322 |
+
if (event.toolId === 'deploy') { state.repository.deployment = 'LAUNCHING'; state.selectedCollection = 'build'; state.activePanel = 'collection'; }
|
| 323 |
+
state.feedback = event.message || `Equipped ${event.toolId}.`;
|
| 324 |
+
pushEvent(state, makeEvent(EVENT_TYPES.USE_TOOL, state.tick, { message: state.feedback, toolId: event.toolId }));
|
| 325 |
+
return state;
|
| 326 |
+
}
|
| 327 |
+
case EVENT_TYPES.TICK:
|
| 328 |
+
state.tick += 1;
|
| 329 |
+
return state;
|
| 330 |
+
case EVENT_TYPES.AGENT_PROGRESS: {
|
| 331 |
+
const agent = state.agents.find((candidate) => candidate.id === event.detail.agentId);
|
| 332 |
+
if (agent) agent.progress = Math.min(100, agent.progress + 2);
|
| 333 |
+
pushEvent(state, makeEvent(event.type, state.tick, event.detail));
|
| 334 |
+
state.feedback = event.detail.message;
|
| 335 |
+
return state;
|
| 336 |
+
}
|
| 337 |
+
case EVENT_TYPES.CONSTRUCTION: {
|
| 338 |
+
const districtId = event.detail.districtId;
|
| 339 |
+
state.construction[districtId] = clamp((state.construction[districtId] || 0) + (event.detail.amount || 0), 0, 1);
|
| 340 |
+
pushEvent(state, makeEvent(event.type, state.tick, event.detail));
|
| 341 |
+
state.feedback = event.detail.message;
|
| 342 |
+
return state;
|
| 343 |
+
}
|
| 344 |
+
case EVENT_TYPES.BUILD_STARTED:
|
| 345 |
+
state.repository.build = 'RUNNING';
|
| 346 |
+
pushEvent(state, makeEvent(event.type, state.tick, event.detail));
|
| 347 |
+
state.feedback = event.detail.message;
|
| 348 |
+
return state;
|
| 349 |
+
case EVENT_TYPES.BUILD_COMPLETED:
|
| 350 |
+
state.repository.build = 'PASSING';
|
| 351 |
+
pushEvent(state, makeEvent(event.type, state.tick, event.detail));
|
| 352 |
+
state.feedback = event.detail.message;
|
| 353 |
+
return state;
|
| 354 |
+
case EVENT_TYPES.TEST_STARTED:
|
| 355 |
+
state.repository.tests = 'RUNNING';
|
| 356 |
+
pushEvent(state, makeEvent(event.type, state.tick, event.detail));
|
| 357 |
+
state.feedback = event.detail.message;
|
| 358 |
+
return state;
|
| 359 |
+
case EVENT_TYPES.TEST_COMPLETED:
|
| 360 |
+
state.repository.tests = '142 PASS';
|
| 361 |
+
pushEvent(state, makeEvent(event.type, state.tick, event.detail));
|
| 362 |
+
state.feedback = event.detail.message;
|
| 363 |
+
return state;
|
| 364 |
+
case EVENT_TYPES.DEPLOYMENT_STARTED:
|
| 365 |
+
state.repository.deployment = 'LAUNCHING';
|
| 366 |
+
pushEvent(state, makeEvent(event.type, state.tick, event.detail));
|
| 367 |
+
state.feedback = event.detail.message;
|
| 368 |
+
return state;
|
| 369 |
+
case EVENT_TYPES.DEPLOYMENT_COMPLETED:
|
| 370 |
+
state.repository.deployment = 'STAGED';
|
| 371 |
+
pushEvent(state, makeEvent(event.type, state.tick, event.detail));
|
| 372 |
+
state.feedback = event.detail.message;
|
| 373 |
+
return state;
|
| 374 |
+
case EVENT_TYPES.COMMIT:
|
| 375 |
+
case EVENT_TYPES.ISSUE:
|
| 376 |
+
case EVENT_TYPES.PULL_REQUEST:
|
| 377 |
+
pushEvent(state, makeEvent(event.type, state.tick, event.detail));
|
| 378 |
+
state.feedback = event.detail.message;
|
| 379 |
+
return state;
|
| 380 |
+
default:
|
| 381 |
+
return state;
|
| 382 |
+
}
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
export function advanceWorldState(state) {
|
| 386 |
+
const ticked = worldReducer(state, { type: EVENT_TYPES.TICK });
|
| 387 |
+
const plan = SIMULATION_SEQUENCE[(ticked.tick - 1) % SIMULATION_SEQUENCE.length];
|
| 388 |
+
const event = makeEvent(plan.type, ticked.tick, plan.detail);
|
| 389 |
+
return { state: worldReducer(ticked, event), event };
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
function escapeHtml(value) {
|
| 393 |
+
return String(value).replace(/[&<>"']/g, (character) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[character]));
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
function windowBlocks(count, color) {
|
| 397 |
+
return Array.from({ length: count }, (_, index) => `<i class="building-window" style="--window-index:${index};--window-color:${color}"></i>`).join('');
|
| 398 |
+
}
|
| 399 |
+
|
| 400 |
+
export function renderWorld(viewport, state, onBuildingSelect) {
|
| 401 |
+
const viewportWidth = viewport.clientWidth || 960;
|
| 402 |
+
const viewportHeight = viewport.clientHeight || 680;
|
| 403 |
+
const centerX = viewportWidth / 2;
|
| 404 |
+
const centerY = viewportHeight * 0.56;
|
| 405 |
+
const translateX = centerX - state.player.x * 0.88;
|
| 406 |
+
const translateY = centerY - state.player.y * 0.88;
|
| 407 |
+
const buildingMarkup = state.districts.map((district) => {
|
| 408 |
+
const selected = district.id === state.selectedDistrictId ? ' is-selected' : '';
|
| 409 |
+
const progress = Math.round((state.construction[district.id] || 0) * 100);
|
| 410 |
+
return `<button class="world-building building-${district.id}${selected}" data-district-id="${district.id}" aria-label="Inspect ${escapeHtml(district.name)}" style="--building-x:${district.x}px;--building-y:${district.y}px;--building-w:${district.width}px;--building-h:${district.height}px;--district-accent:${district.accent};--construction:${progress}%">
|
| 411 |
+
<span class="building-cap"></span>
|
| 412 |
+
<span class="building-facade">${windowBlocks(district.id === 'src' ? 20 : 12, district.accent)}</span>
|
| 413 |
+
<span class="building-sign"><b>${escapeHtml(district.glyph)}</b>${escapeHtml(district.name)}</span>
|
| 414 |
+
<span class="building-status">${progress}% BUILT</span>
|
| 415 |
+
<span class="building-floor"></span>
|
| 416 |
+
</button>`;
|
| 417 |
+
}).join('');
|
| 418 |
+
|
| 419 |
+
viewport.innerHTML = `<div class="world-canvas" style="transform:translate(${translateX}px,${translateY}px) scale(.88)">
|
| 420 |
+
<div class="distant-skyline" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
|
| 421 |
+
<div class="moon-orb" aria-hidden="true"></div>
|
| 422 |
+
<div class="world-ground" aria-label="Walkable GitWorld repository city">
|
| 423 |
+
<div class="ground-grid"></div>
|
| 424 |
+
<div class="road road-main"><span>MAIN</span></div>
|
| 425 |
+
<div class="road road-cross"><span>HISTORY</span></div>
|
| 426 |
+
<div class="road road-branch"><span>BRANCH / ALT</span></div>
|
| 427 |
+
<div class="plaza"><span>SPAWN PLAZA</span></div>
|
| 428 |
+
${buildingMarkup}
|
| 429 |
+
<div class="construction-crane crane-one" aria-hidden="true"><i></i><b></b><em></em></div>
|
| 430 |
+
<div class="construction-crane crane-two" aria-hidden="true"><i></i><b></b><em></em></div>
|
| 431 |
+
<div class="agent-bot bot-a" aria-label="Simulated world architect builder"><i></i><b></b></div>
|
| 432 |
+
<div class="agent-bot bot-b" aria-label="Simulated repository builder"><i></i><b></b></div>
|
| 433 |
+
<div class="quest-marker">!</div>
|
| 434 |
+
<div class="pr-gate"><span>PULL REQUEST</span><b>OPEN GATE</b></div>
|
| 435 |
+
<div class="player-avatar" data-facing="${state.player.facing}" aria-label="AhmadParr avatar" style="left:${state.player.x}px;top:${state.player.y}px">
|
| 436 |
+
<span class="avatar-shadow"></span><span class="avatar-legs"><i></i><i></i></span><span class="avatar-body"><i class="avatar-pack"></i></span><span class="avatar-head"></span>
|
| 437 |
+
</div>
|
| 438 |
+
<div class="world-sign repo-sign"><small>SIMULATED REPOSITORY</small><strong>SNAPKITTYWEST</strong><b>/ pax-coder</b><em>★ 213 · ⑂ 45 · ◉ PUBLIC</em></div>
|
| 439 |
+
<div class="world-sign build-sign"><small>BUILD AGENTS</small><strong>3 ACTIVE</strong><b>WATCH THE CREW WORK</b></div>
|
| 440 |
+
</div>
|
| 441 |
+
</div>`;
|
| 442 |
+
|
| 443 |
+
viewport.querySelectorAll('[data-district-id]').forEach((building) => {
|
| 444 |
+
building.addEventListener('click', () => onBuildingSelect(building.dataset.districtId));
|
| 445 |
+
});
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
export function renderMinimap(root, state) {
|
| 449 |
+
const districts = state.districts.map((district) => `<button class="minimap-district" data-district-id="${district.id}" aria-label="Map ${escapeHtml(district.name)}" style="left:${district.x / 16}px;top:${district.y / 9.2}px;background:${district.accent}"></button>`).join('');
|
| 450 |
+
root.innerHTML = `<div class="minimap-grid" aria-label="GitWorld district map"><div class="map-road map-road-a"></div><div class="map-road map-road-b"></div>${districts}<span class="map-player" style="left:${state.player.x / 16}px;top:${state.player.y / 9.2}px"></span></div><div class="map-caption"><span>LIVE MAP</span><b>${state.selectedDistrictId.toUpperCase()}</b></div>`;
|
| 451 |
+
}
|
index.html
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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">
|
| 6 |
+
<meta name="theme-color" content="#061422">
|
| 7 |
+
<meta name="description" content="GitWorld: walk inside a simulated repository.">
|
| 8 |
+
<title>GitWorld — the world you walk into</title>
|
| 9 |
+
<link rel="stylesheet" href="./styles.css">
|
| 10 |
+
</head>
|
| 11 |
+
<body>
|
| 12 |
+
<section id="opening-scene" class="opening-scene" aria-label="GitWorld opening scene">
|
| 13 |
+
<video id="opening-video" class="opening-video" playsinline muted preload="auto"></video>
|
| 14 |
+
<div class="opening-vignette" aria-hidden="true"></div>
|
| 15 |
+
<div class="opening-copy">
|
| 16 |
+
<span class="section-kicker">VRCHAT WORLD · NEON REFLECTIONS</span>
|
| 17 |
+
<h1>GITWORLD</h1>
|
| 18 |
+
<p>Walk into the code. Every repository is a living world.</p>
|
| 19 |
+
<div class="opening-meta"><span><i></i> OPENING SCENE READY</span><span>LOCAL BROWSER RENDER</span><span>VR-STYLE VIEW</span></div>
|
| 20 |
+
<div class="opening-actions"><button class="opening-enter" data-action="enter-game">ENTER THE WORLD <b>→</b></button><button class="opening-sound" data-action="toggle-opening-sound">SOUND OFF</button></div>
|
| 21 |
+
<small id="opening-status">Loading 3099.mp4…</small>
|
| 22 |
+
</div>
|
| 23 |
+
</section>
|
| 24 |
+
<div id="game-shell" class="game-shell" data-view="city">
|
| 25 |
+
<div class="ambient ambient-one" aria-hidden="true"></div>
|
| 26 |
+
<div class="ambient ambient-two" aria-hidden="true"></div>
|
| 27 |
+
<header class="topbar">
|
| 28 |
+
<div class="brand-lockup"><span class="brand-mark">⌘</span><span><strong>GitWorld</strong><small>THE WORLD YOU WALK INTO</small></span></div>
|
| 29 |
+
<div class="repo-search-wrap"><label for="repo-search">Local repo search</label><input id="repo-search" type="search" placeholder="Search the city…" autocomplete="off"><kbd>⌘ K</kbd></div>
|
| 30 |
+
<div class="topbar-actions"><span class="simulated-pill"><i></i> BROWSER-LOCAL SIMULATION</span><button class="icon-button" data-action="open-terminal" aria-label="Open terminal phone">▣</button></div>
|
| 31 |
+
</header>
|
| 32 |
+
|
| 33 |
+
<aside class="left-rail panel-glass" aria-label="Welcome to GitWorld">
|
| 34 |
+
<div class="panel-title"><span class="section-kicker">WELCOME TO</span><h1>GITWORLD</h1><i></i></div>
|
| 35 |
+
<p class="welcome-copy">A virtual world where GitHub repositories are living places. Walk in. Explore. Watch agents build in real time.</p>
|
| 36 |
+
<p class="welcome-copy muted">Code is no longer abstract.<br><strong>It’s physical.</strong></p>
|
| 37 |
+
<div class="rail-divider"></div>
|
| 38 |
+
<div class="component-heading"><span>CORE COMPONENTS</span><small>THE CITY LEGEND</small></div>
|
| 39 |
+
<ul class="component-list">
|
| 40 |
+
<li><b>◉</b><span>Repositories are <strong>Worlds</strong></span></li>
|
| 41 |
+
<li><b>♙</b><span>Agents are <strong>Builders</strong></span></li>
|
| 42 |
+
<li><b>✦</b><span>Commits are <strong>Events</strong></span></li>
|
| 43 |
+
<li><b>↔</b><span>Pull Requests are <strong>Doors</strong></span></li>
|
| 44 |
+
<li><b>!</b><span>Issues are <strong>Quests</strong></span></li>
|
| 45 |
+
<li><b>⌘</b><span>Code is <strong>Physical</strong></span></li>
|
| 46 |
+
</ul>
|
| 47 |
+
<div class="how-it-works">
|
| 48 |
+
<div class="component-heading"><span>HOW IT WORKS</span><small>YOUR LOOP</small></div>
|
| 49 |
+
<ol><li>Walk inside a repository</li><li>Explore code as a world</li><li>Watch agents build</li><li>Inspect software objects</li><li>Build · test · deploy</li></ol>
|
| 50 |
+
</div>
|
| 51 |
+
</aside>
|
| 52 |
+
|
| 53 |
+
<main class="world-stage" aria-label="Playable GitWorld city">
|
| 54 |
+
<div class="stage-heading"><div><span class="section-kicker">ENTER REPOSITORY</span><h2>SNAPKITTYWEST <b>/ pax-coder</b></h2></div><div class="stage-state"><span id="repo-visibility">Public</span><span id="repo-branch">BRANCH / main</span></div></div>
|
| 55 |
+
<div id="world-viewport" class="world-viewport" tabindex="0" aria-label="Walkable repository city. Use WASD or arrow keys to move."></div>
|
| 56 |
+
<section id="reality-deck" class="reality-deck" aria-label="Browser reality capture scene references">
|
| 57 |
+
<div class="reality-deck-heading"><span><i class="live-dot"></i> REALITY DECK</span><small>NEON REFLECTIONS · BROWSER SCENE PLATES</small></div>
|
| 58 |
+
<div class="reality-deck-grid">
|
| 59 |
+
<video class="reality-plate" data-reference-image="NeonReflections/3089.webp" muted playsinline aria-label="Neon reflections social hub reference"></video>
|
| 60 |
+
<video class="reality-plate" data-reference-image="NeonReflections/3087.webp" muted playsinline aria-label="Arcade portal reference"></video>
|
| 61 |
+
<video class="reality-plate" data-reference-image="NeonReflections/3090.webp" muted playsinline aria-label="GitWorld spawn point reference"></video>
|
| 62 |
+
<video class="reality-plate" data-reference-image="NeonReflections/3092.jpg" muted playsinline aria-label="Reality capture avatar reference"></video>
|
| 63 |
+
</div>
|
| 64 |
+
</section>
|
| 65 |
+
<div id="nearby-prompt" class="nearby-prompt" aria-live="polite"><span>WALKABLE CITY</span><strong>NORTH</strong><small>WASD · ARROWS TO NAVIGATE</small></div>
|
| 66 |
+
<div class="city-reticle" aria-hidden="true"><i></i><b></b></div>
|
| 67 |
+
<div class="movement-help"><kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd><span>walk</span><kbd>E</kbd><span>enter</span></div>
|
| 68 |
+
<div class="feedback-line" aria-live="polite"><i></i><span id="feedback-line">Walk toward a district or click a glowing building.</span><time id="world-clock">00:00:00</time></div>
|
| 69 |
+
</main>
|
| 70 |
+
|
| 71 |
+
<aside class="right-rail" aria-label="GitWorld telemetry">
|
| 72 |
+
<section class="map-panel panel-glass">
|
| 73 |
+
<div class="panel-heading"><span><i class="live-dot"></i> GITWORLD MAP</span><button class="icon-button" data-action="close-inspector" aria-label="Collapse map">×</button></div>
|
| 74 |
+
<div id="minimap-viewport" class="minimap-viewport"></div>
|
| 75 |
+
<div class="map-legend"><span><i class="legend-player"></i> YOU</span><span><i class="legend-building"></i> DISTRICT</span><span><i class="legend-road"></i> ROAD</span></div>
|
| 76 |
+
</section>
|
| 77 |
+
<section class="agents-panel panel-glass">
|
| 78 |
+
<div class="panel-heading"><span>BUILD AGENTS <b class="count-badge" id="active-agent-count">3</b></span><small>LIVE SWARM</small></div>
|
| 79 |
+
<ul id="agent-list" class="agent-list"></ul>
|
| 80 |
+
<div class="swarm-flow"><span>WORLD ARCHITECT</span><i>↓</i><span>REPOSITORY BUILDER</span><i>↓</i><span>REALITY/UI INTEGRATOR</span></div>
|
| 81 |
+
</section>
|
| 82 |
+
<section class="event-panel panel-glass">
|
| 83 |
+
<div class="panel-heading"><span>CONSTRUCTION FEED</span><small id="event-count">3 EVENTS</small></div>
|
| 84 |
+
<ul id="event-feed" class="event-feed" aria-live="polite"></ul>
|
| 85 |
+
</section>
|
| 86 |
+
</aside>
|
| 87 |
+
|
| 88 |
+
<section id="inspector-drawer" class="inspector-drawer panel-glass" aria-label="Repository inspector">
|
| 89 |
+
<div class="drawer-heading"><div><span class="section-kicker">CONTEXTUAL INSPECT</span><strong>PHYSICAL SOFTWARE</strong></div><button class="icon-button" data-action="close-inspector" aria-label="Close inspector">×</button></div>
|
| 90 |
+
<div id="inspector-content" class="inspector-content"></div>
|
| 91 |
+
<div class="drawer-tabs"><button data-action="select-collection" data-collection="files">FILES</button><button data-action="select-collection" data-collection="commits">COMMITS</button><button data-action="select-collection" data-collection="issues">ISSUES</button><button data-action="select-collection" data-collection="prs">PRS</button><button data-action="select-collection" data-collection="build">CI</button></div>
|
| 92 |
+
</section>
|
| 93 |
+
|
| 94 |
+
<section id="terminal-phone" class="terminal-phone" aria-label="Simulated terminal phone" aria-hidden="true">
|
| 95 |
+
<div class="phone-speaker"></div><div class="terminal-top"><span>● local-shell</span><button class="icon-button" data-action="close-terminal" aria-label="Close terminal">×</button></div>
|
| 96 |
+
<div class="terminal-screen"><div class="terminal-header"><strong>_ bash</strong><small>pax-coder · main</small></div><div id="terminal-output" class="terminal-output"></div><form id="terminal-form" class="terminal-form"><span>$</span><input id="terminal-input" aria-label="Terminal command" placeholder="type a command…" autocomplete="off"><button type="submit" aria-label="Run command">↵</button></form><div class="terminal-shortcuts"><button data-action="command" data-command="ls">ls</button><button data-action="command" data-command="git status">git status</button><button data-action="command" data-command="cargo test">cargo test</button><button data-action="command" data-command="./build.sh">./build.sh</button></div></div><div class="phone-home"></div>
|
| 97 |
+
</section>
|
| 98 |
+
|
| 99 |
+
<nav id="toolbelt" class="toolbelt panel-glass" aria-label="Software toolbelt"></nav>
|
| 100 |
+
|
| 101 |
+
<section class="software-shelf" aria-labelledby="software-objects-title">
|
| 102 |
+
<div class="shelf-title"><span id="software-objects-title">SEE SOFTWARE AS PHYSICAL OBJECTS</span><small>CLICK A TOOL OR BUILDING TO INTERACT</small></div>
|
| 103 |
+
<div class="object-grid">
|
| 104 |
+
<button class="object-card" data-action="open-terminal"><span class="object-art phone-art">▣</span><strong>TERMINAL = PHONE</strong><small>1 · open shell</small></button>
|
| 105 |
+
<button class="object-card" data-action="tool" data-tool-id="bash"><span class="object-art hammer-art">⚒</span><strong>BASH = HAMMER</strong><small>2 · run operation</small></button>
|
| 106 |
+
<button class="object-card" data-action="tool" data-tool-id="git"><span class="object-art backpack-art">◈</span><strong>GIT = BACKPACK</strong><small>3 · view history</small></button>
|
| 107 |
+
<button class="object-card" data-action="tool" data-tool-id="docker"><span class="object-art box-art">▦</span><strong>DOCKER = BOX</strong><small>4 · open container</small></button>
|
| 108 |
+
<button class="object-card" data-action="tool" data-tool-id="code"><span class="object-art tablet-art">⌘</span><strong>CODE = TABLET</strong><small>5 · read source</small></button>
|
| 109 |
+
<button class="object-card" data-action="tool" data-tool-id="ssh"><span class="object-art key-art">⚿</span><strong>SSH = KEY</strong><small>6 · unlock gate</small></button>
|
| 110 |
+
<button class="object-card" data-action="tool" data-tool-id="database"><span class="object-art vault-art">▤</span><strong>DATABASE = VAULT</strong><small>7 · inspect data</small></button>
|
| 111 |
+
<button class="object-card" data-action="tool" data-tool-id="deploy"><span class="object-art rocket-art">↗</span><strong>DEPLOY = ROCKET</strong><small>8 · launch build</small></button>
|
| 112 |
+
</div>
|
| 113 |
+
</section>
|
| 114 |
+
|
| 115 |
+
<div id="touch-controls" class="touch-controls" aria-label="Touch movement controls">
|
| 116 |
+
<button data-action="move" data-dx="0" data-dy="-1" aria-label="Walk north">▲</button><div><button data-action="move" data-dx="-1" data-dy="0" aria-label="Walk west">◀</button><button data-action="move" data-dx="0" data-dy="1" aria-label="Walk south">▼</button><button data-action="move" data-dx="1" data-dy="0" aria-label="Walk east">▶</button></div>
|
| 117 |
+
</div>
|
| 118 |
+
<footer class="disclaimer"><span>SIMULATED REPOSITORY DATA</span><span>NO BACKEND · NO NETWORK · LOCAL DEMO</span><span id="player-level">LEVEL 12 DEV</span></footer>
|
| 119 |
+
</div>
|
| 120 |
+
<script type="module" src="./src/main.ts"></script>
|
| 121 |
+
</body>
|
| 122 |
+
</html>
|
launch.html
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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">
|
| 6 |
+
<title>GitWorld Launcher</title>
|
| 7 |
+
<style>
|
| 8 |
+
:root { color-scheme: dark; --cyan:#7de8ff; --violet:#b58cff; --panel:rgba(8,17,38,.86); }
|
| 9 |
+
* { box-sizing:border-box; }
|
| 10 |
+
body { margin:0; min-height:100vh; display:grid; place-items:center; overflow:hidden; color:#edf8ff; font:16px/1.5 "Segoe UI",system-ui,sans-serif; background:radial-gradient(circle at 50% 12%,#182e68 0,#08132d 42%,#02050d 100%); }
|
| 11 |
+
body:before { content:""; position:fixed; inset:0; pointer-events:none; opacity:.3; background-image:linear-gradient(rgba(125,232,255,.08) 1px,transparent 1px),linear-gradient(90deg,rgba(125,232,255,.08) 1px,transparent 1px); background-size:42px 42px; transform:perspective(500px) rotateX(58deg) scale(1.8) translateY(34%); transform-origin:center bottom; }
|
| 12 |
+
main { width:min(720px,calc(100% - 32px)); padding:40px; position:relative; border:1px solid rgba(125,232,255,.42); border-radius:22px; background:var(--panel); box-shadow:0 0 70px rgba(32,145,255,.22), inset 0 0 50px rgba(100,50,255,.08); }
|
| 13 |
+
.eyebrow { color:var(--cyan); letter-spacing:.2em; text-transform:uppercase; font-size:12px; }
|
| 14 |
+
h1 { margin:8px 0 10px; font-size:clamp(38px,8vw,72px); line-height:.95; letter-spacing:-.05em; text-shadow:0 0 24px rgba(181,140,255,.65); }
|
| 15 |
+
p { color:#b9cce0; max-width:58ch; }
|
| 16 |
+
.actions { display:grid; gap:14px; margin:28px 0; }
|
| 17 |
+
a { display:flex; align-items:center; justify-content:space-between; gap:18px; padding:17px 20px; color:#f4fbff; text-decoration:none; border:1px solid rgba(125,232,255,.3); border-radius:13px; background:linear-gradient(100deg,rgba(18,72,120,.58),rgba(31,16,76,.64)); transition:transform .18s ease,border-color .18s ease,box-shadow .18s ease; }
|
| 18 |
+
a:hover { transform:translateY(-2px); border-color:var(--cyan); box-shadow:0 0 22px rgba(125,232,255,.25); }
|
| 19 |
+
a strong { display:block; font-size:17px; } a span { color:#9eb4cd; font-size:13px; } .arrow { color:var(--cyan); font-size:24px; }
|
| 20 |
+
.note { margin:0; padding:14px 16px; border-left:3px solid var(--violet); background:rgba(181,140,255,.08); color:#d4ddf2; font-size:13px; }
|
| 21 |
+
code { color:#a6f1ff; }
|
| 22 |
+
</style>
|
| 23 |
+
</head>
|
| 24 |
+
<body>
|
| 25 |
+
<main>
|
| 26 |
+
<div class="eyebrow">SNAPKITTYWEST · local build</div>
|
| 27 |
+
<h1>GitWorld</h1>
|
| 28 |
+
<p>Choose the playable build. The browser build opens immediately. The Unreal project is wired with the opening scene, VR pawn, repository worlds, portals, and the 8K rendering profile.</p>
|
| 29 |
+
<div class="actions">
|
| 30 |
+
<a href="dist/index.html"><div><strong>Play browser build</strong><span>Walkable local prototype · opens now</span></div><div class="arrow">→</div></a>
|
| 31 |
+
<a href="phase2-unreal/GitWorldUnreal.uproject"><div><strong>Open Unreal project</strong><span>Launches the project through the installed Unreal association</span></div><div class="arrow">→</div></a>
|
| 32 |
+
<a href="Launch-GitWorld.cmd"><div><strong>Run Unreal VR launcher</strong><span>Uses the local UE 5.x editor and starts with OpenXR VR</span></div><div class="arrow">→</div></a>
|
| 33 |
+
</div>
|
| 34 |
+
<p class="note"><strong>Desktop fallback:</strong> in Unreal Play-in-Editor use <code>WASD</code> to move, <code>E</code> to interact, <code>Space</code> to teleport, and <code>Esc</code> to skip the opening.</p>
|
| 35 |
+
</main>
|
| 36 |
+
</body>
|
| 37 |
+
</html>
|
package.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "gitworld-local-prototype",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"private": true,
|
| 5 |
+
"type": "module",
|
| 6 |
+
"description": "A browser-local walkable repository city prototype.",
|
| 7 |
+
"scripts": {
|
| 8 |
+
"build": "node scripts/build.mjs",
|
| 9 |
+
"test": "node --test tests/*.test.mjs",
|
| 10 |
+
"check": "node --check src/shared.js && node --check src/world-architect.js && node --check src/ui-reality.js && node --check scripts/build.mjs"
|
| 11 |
+
}
|
| 12 |
+
}
|
phase2-unreal/.gitignore
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Binaries/
|
| 2 |
+
DerivedDataCache/
|
| 3 |
+
Intermediate/
|
| 4 |
+
Saved/
|
| 5 |
+
Compute/build/
|
| 6 |
+
RealityCapture/Output/
|
| 7 |
+
Saved/Autosaves/
|
| 8 |
+
ReferenceSource/Output/
|
| 9 |
+
*.sln
|
| 10 |
+
*.suo
|
phase2-unreal/Compute/CMakeLists.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
cmake_minimum_required(VERSION 3.24)
|
| 2 |
+
project(GitWorldCuda LANGUAGES CXX CUDA)
|
| 3 |
+
|
| 4 |
+
add_library(GitWorldCuda SHARED GitWorldBuildPass.cu)
|
| 5 |
+
target_compile_features(GitWorldCuda PRIVATE cxx_std_17)
|
| 6 |
+
set_target_properties(GitWorldCuda PROPERTIES
|
| 7 |
+
CUDA_SEPARABLE_COMPILATION ON
|
| 8 |
+
WINDOWS_EXPORT_ALL_SYMBOLS ON
|
| 9 |
+
OUTPUT_NAME "GitWorldCuda"
|
| 10 |
+
)
|
phase2-unreal/Compute/GitWorldBuildPass.cu
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include "GitWorldBuildPass.h"
|
| 2 |
+
|
| 3 |
+
#include <cuda_runtime.h>
|
| 4 |
+
|
| 5 |
+
namespace
|
| 6 |
+
{
|
| 7 |
+
__global__ void BuildFieldKernel(const float* Input, float* Output, int Count)
|
| 8 |
+
{
|
| 9 |
+
const int Index = blockIdx.x * blockDim.x + threadIdx.x;
|
| 10 |
+
if (Index < Count)
|
| 11 |
+
{
|
| 12 |
+
Output[Index] = Input[Index] * (1.0f + static_cast<float>(Index) * 0.125f);
|
| 13 |
+
}
|
| 14 |
+
}
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
extern "C" GITWORLD_CUDA_API int GW_CudaBuildPass(
|
| 18 |
+
const float* Input,
|
| 19 |
+
float* Output,
|
| 20 |
+
int Count,
|
| 21 |
+
int* DeviceIndex)
|
| 22 |
+
{
|
| 23 |
+
if (Input == nullptr || Output == nullptr || Count <= 0 || DeviceIndex == nullptr)
|
| 24 |
+
{
|
| 25 |
+
return 0;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
int DeviceCount = 0;
|
| 29 |
+
if (cudaGetDeviceCount(&DeviceCount) != cudaSuccess || DeviceCount == 0)
|
| 30 |
+
{
|
| 31 |
+
return 0;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
*DeviceIndex = 0;
|
| 35 |
+
if (cudaSetDevice(*DeviceIndex) != cudaSuccess)
|
| 36 |
+
{
|
| 37 |
+
return 0;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
float* DeviceInput = nullptr;
|
| 41 |
+
float* DeviceOutput = nullptr;
|
| 42 |
+
const size_t Bytes = sizeof(float) * static_cast<size_t>(Count);
|
| 43 |
+
if (cudaMalloc(&DeviceInput, Bytes) != cudaSuccess
|
| 44 |
+
|| cudaMalloc(&DeviceOutput, Bytes) != cudaSuccess)
|
| 45 |
+
{
|
| 46 |
+
if (DeviceInput != nullptr) cudaFree(DeviceInput);
|
| 47 |
+
if (DeviceOutput != nullptr) cudaFree(DeviceOutput);
|
| 48 |
+
return 0;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
const bool CopiedIn = cudaMemcpy(DeviceInput, Input, Bytes, cudaMemcpyHostToDevice) == cudaSuccess;
|
| 52 |
+
if (CopiedIn)
|
| 53 |
+
{
|
| 54 |
+
BuildFieldKernel<<<(Count + 127) / 128, 128>>>(DeviceInput, DeviceOutput, Count);
|
| 55 |
+
}
|
| 56 |
+
const bool Computed = CopiedIn
|
| 57 |
+
&& cudaGetLastError() == cudaSuccess
|
| 58 |
+
&& cudaDeviceSynchronize() == cudaSuccess;
|
| 59 |
+
const bool CopiedOut = Computed
|
| 60 |
+
&& cudaMemcpy(Output, DeviceOutput, Bytes, cudaMemcpyDeviceToHost) == cudaSuccess;
|
| 61 |
+
|
| 62 |
+
cudaFree(DeviceInput);
|
| 63 |
+
cudaFree(DeviceOutput);
|
| 64 |
+
return CopiedOut ? 1 : 0;
|
| 65 |
+
}
|
phase2-unreal/Compute/GitWorldBuildPass.h
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#pragma once
|
| 2 |
+
|
| 3 |
+
#if defined(_WIN32)
|
| 4 |
+
#define GITWORLD_CUDA_API __declspec(dllexport)
|
| 5 |
+
#else
|
| 6 |
+
#define GITWORLD_CUDA_API
|
| 7 |
+
#endif
|
| 8 |
+
|
| 9 |
+
extern "C"
|
| 10 |
+
{
|
| 11 |
+
GITWORLD_CUDA_API int GW_CudaBuildPass(
|
| 12 |
+
const float* Input,
|
| 13 |
+
float* Output,
|
| 14 |
+
int Count,
|
| 15 |
+
int* DeviceIndex);
|
| 16 |
+
}
|
phase2-unreal/Compute/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Optional CUDA build-field pass
|
| 2 |
+
|
| 3 |
+
This DLL is not the Unreal renderer. Unreal renders the world through DX12, Nanite, Lumen, and hardware ray tracing when the project is run on a supported configuration. The DLL provides a small GPU compute pass for repository build-field weights and is loaded dynamically by `FGitWorldCudaBridge`.
|
| 4 |
+
|
| 5 |
+
Build from a Visual Studio Developer Command Prompt with the CUDA toolkit installed:
|
| 6 |
+
|
| 7 |
+
```text
|
| 8 |
+
cmake -S Compute -B Compute/build -G "Visual Studio 17 2022" -A x64
|
| 9 |
+
cmake --build Compute/build --config Release
|
| 10 |
+
```
|
| 11 |
+
|
| 12 |
+
Copy `Compute/build/Release/GitWorldCuda.dll` into `Binaries/Win64/`, or set `GITWORLD_CUDA_DLL` to an absolute path before launching Unreal. If the DLL is absent or the device pass fails, the world remains playable using the deterministic CPU fallback.
|
phase2-unreal/Config/DefaultEngine.ini
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[/Script/EngineSettings.GameMapsSettings]
|
| 2 |
+
GameDefaultMap=/Engine/Maps/Entry
|
| 3 |
+
EditorStartupMap=/Engine/Maps/Entry
|
| 4 |
+
GlobalDefaultGameMode=/Script/GitWorldUnreal.GitWorldGameMode
|
| 5 |
+
|
| 6 |
+
[/Script/Engine.RendererSettings]
|
| 7 |
+
r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True
|
| 8 |
+
r.DynamicGlobalIlluminationMethod=1
|
| 9 |
+
r.ReflectionMethod=1
|
| 10 |
+
r.Lumen.HardwareRayTracing=1
|
| 11 |
+
r.RayTracing=True
|
| 12 |
+
r.Nanite.ProjectEnabled=True
|
| 13 |
+
r.Shadow.Virtual.Enable=1
|
| 14 |
+
r.GenerateMeshDistanceFields=True
|
| 15 |
+
r.AllowStaticLighting=False
|
| 16 |
+
r.DefaultFeature.MotionBlur=False
|
| 17 |
+
r.DefaultFeature.AntiAliasing=4
|
| 18 |
+
r.TemporalAA.Upsampling=1
|
| 19 |
+
|
| 20 |
+
[DevOptions.Shaders]
|
| 21 |
+
TargetedRHIs=PCD3D_SM6
|
| 22 |
+
|
| 23 |
+
[/Script/Engine.Engine]
|
| 24 |
+
+ActiveGameNameRedirects=(OldGameName="TP_ThirdPerson",NewGameName="/Script/GitWorldUnreal")
|
| 25 |
+
|
| 26 |
+
[/Script/Engine.GameUserSettings]
|
| 27 |
+
ResolutionSizeX=7680
|
| 28 |
+
ResolutionSizeY=4320
|
| 29 |
+
LastUserConfirmedResolutionSizeX=7680
|
| 30 |
+
LastUserConfirmedResolutionSizeY=4320
|
| 31 |
+
WindowPosX=-1
|
| 32 |
+
WindowPosY=-1
|
| 33 |
+
bUseHDRDisplayOutput=False
|
| 34 |
+
HDRDisplayOutputNits=1000
|
| 35 |
+
|
| 36 |
+
[ConsoleVariables]
|
| 37 |
+
r.ScreenPercentage=100
|
| 38 |
+
r.SecondaryScreenPercentage.GameViewport=100
|
| 39 |
+
r.DynamicRes.OperationMode=0
|
| 40 |
+
r.ViewDistanceScale=2
|
| 41 |
+
r.Shadow.Virtual.MaxPhysicalPages=4096
|
| 42 |
+
r.Nanite.MaxPixelsPerEdge=1
|
| 43 |
+
r.Streaming.PoolSize=0
|
| 44 |
+
t.MaxFPS=60
|
phase2-unreal/Config/DefaultGame.ini
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[/Script/GitWorldUnreal.GitWorldGameMode]
|
| 2 |
+
GameSessionClass=/Script/Engine.GameSession
|
phase2-unreal/Config/DefaultInput.ini
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[/Script/Engine.InputSettings]
|
| 2 |
+
+AxisMappings=(AxisName="MoveForward",Key=W,Scale=1.0)
|
| 3 |
+
+AxisMappings=(AxisName="MoveForward",Key=S,Scale=-1.0)
|
| 4 |
+
+AxisMappings=(AxisName="MoveForward",Key=Gamepad_LeftY,Scale=1.0)
|
| 5 |
+
+AxisMappings=(AxisName="MoveForward",Key=MotionController_Left_Thumbstick_Y,Scale=1.0)
|
| 6 |
+
+AxisMappings=(AxisName="MoveRight",Key=D,Scale=1.0)
|
| 7 |
+
+AxisMappings=(AxisName="MoveRight",Key=A,Scale=-1.0)
|
| 8 |
+
+AxisMappings=(AxisName="MoveRight",Key=Gamepad_LeftX,Scale=1.0)
|
| 9 |
+
+AxisMappings=(AxisName="MoveRight",Key=MotionController_Left_Thumbstick_X,Scale=1.0)
|
| 10 |
+
+AxisMappings=(AxisName="Turn",Key=MouseX,Scale=1.0)
|
| 11 |
+
+AxisMappings=(AxisName="Turn",Key=Gamepad_RightX,Scale=1.0)
|
| 12 |
+
+ActionMappings=(ActionName="Interact",Key=E)
|
| 13 |
+
+ActionMappings=(ActionName="Interact",Key=MotionController_Right_Trigger)
|
| 14 |
+
+ActionMappings=(ActionName="Teleport",Key=SpaceBar)
|
| 15 |
+
+ActionMappings=(ActionName="Teleport",Key=MotionController_Right_Thumbstick)
|
| 16 |
+
+ActionMappings=(ActionName="SkipOpening",Key=Escape)
|
| 17 |
+
+ActionMappings=(ActionName="SkipOpening",Key=MotionController_Left_Menu)
|
phase2-unreal/Content/RealityScene/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Reality scene import target
|
| 2 |
+
|
| 3 |
+
Import one FBX per repository here in Unreal:
|
| 4 |
+
|
| 5 |
+
```text
|
| 6 |
+
/Game/RealityScene/<repository-id>/<repository-id>.fbx
|
| 7 |
+
```
|
| 8 |
+
|
| 9 |
+
Assign the imported static mesh to `RealityCaptureMesh` on the matching `AGitWorldRepoWorld` actor. The actor keeps repository state isolated while the RealityCapture mesh supplies the physical scene surface.
|
phase2-unreal/Content/VR/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GitWorld VR mode
|
| 2 |
+
|
| 3 |
+
The project enables OpenXR and uses `AGitWorldVRPawn` as the default pawn. The pawn provides head tracking, tracked left/right controllers, thumbstick or WASD movement, controller-directed teleport, portal interaction, and opening-video skip.
|
| 4 |
+
|
| 5 |
+
Controls:
|
| 6 |
+
|
| 7 |
+
- Left thumbstick / WASD: smooth movement
|
| 8 |
+
- Right trigger / E: interact with a portal or repository world
|
| 9 |
+
- Right thumbstick click / Space: aim and release to teleport
|
| 10 |
+
- Left menu / Escape: skip the `3099.mp4` opening sequence
|
| 11 |
+
|
| 12 |
+
OpenXR must be installed/enabled by the Unreal Engine installation. The project’s 7680×4320 settings are for cinematic output; VR should use the headset’s native per-eye render target and a measured frame-time budget.
|
phase2-unreal/EditorScripts/import_gitworld_opening.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Run inside the Unreal Editor Python environment.
|
| 2 |
+
|
| 3 |
+
Creates a Media Framework asset for 3099.mp4, builds an emissive screen
|
| 4 |
+
material, and places the AGitWorldOpeningScene actor in the current level.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import pathlib
|
| 8 |
+
|
| 9 |
+
import unreal
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
PROJECT_DIR = pathlib.Path(unreal.Paths.project_dir())
|
| 13 |
+
VIDEO_FILE = PROJECT_DIR / "ReferenceSource" / "Opening" / "3099.mp4"
|
| 14 |
+
MEDIA_PATH = "/Game/GitWorld/Media"
|
| 15 |
+
MATERIAL_PATH = "/Game/GitWorld/Materials"
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def create_asset(asset_tools, name, package_path, asset_class, factory):
|
| 19 |
+
existing = unreal.EditorAssetLibrary.load_asset(package_path + "/" + name)
|
| 20 |
+
if existing:
|
| 21 |
+
return existing
|
| 22 |
+
return asset_tools.create_asset(name, package_path, asset_class, factory)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def create_media_assets():
|
| 26 |
+
if not VIDEO_FILE.is_file():
|
| 27 |
+
raise RuntimeError("Opening video not found: %s" % VIDEO_FILE)
|
| 28 |
+
|
| 29 |
+
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
| 30 |
+
source = create_asset(
|
| 31 |
+
asset_tools,
|
| 32 |
+
"MS_GitWorldOpening",
|
| 33 |
+
MEDIA_PATH,
|
| 34 |
+
unreal.FileMediaSource,
|
| 35 |
+
unreal.FileMediaSourceFactoryNew(),
|
| 36 |
+
)
|
| 37 |
+
source.set_editor_property("file_path", str(VIDEO_FILE))
|
| 38 |
+
unreal.EditorAssetLibrary.save_loaded_asset(source)
|
| 39 |
+
|
| 40 |
+
player = create_asset(
|
| 41 |
+
asset_tools,
|
| 42 |
+
"MP_GitWorldOpening",
|
| 43 |
+
MEDIA_PATH,
|
| 44 |
+
unreal.MediaPlayer,
|
| 45 |
+
unreal.MediaPlayerFactoryNew(),
|
| 46 |
+
)
|
| 47 |
+
player.set_editor_property("loop", False)
|
| 48 |
+
player.set_editor_property("play_on_open", True)
|
| 49 |
+
player.set_editor_property("media_source", source)
|
| 50 |
+
unreal.EditorAssetLibrary.save_loaded_asset(player)
|
| 51 |
+
|
| 52 |
+
texture = create_asset(
|
| 53 |
+
asset_tools,
|
| 54 |
+
"MT_GitWorldOpening",
|
| 55 |
+
MEDIA_PATH,
|
| 56 |
+
unreal.MediaTexture,
|
| 57 |
+
unreal.MediaTextureFactoryNew(),
|
| 58 |
+
)
|
| 59 |
+
texture.set_editor_property("media_player", player)
|
| 60 |
+
unreal.EditorAssetLibrary.save_loaded_asset(texture)
|
| 61 |
+
return texture
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def create_screen_material(texture):
|
| 65 |
+
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
| 66 |
+
material = create_asset(
|
| 67 |
+
asset_tools,
|
| 68 |
+
"M_GitWorldOpeningScreen",
|
| 69 |
+
MATERIAL_PATH,
|
| 70 |
+
unreal.Material,
|
| 71 |
+
unreal.MaterialFactoryNew(),
|
| 72 |
+
)
|
| 73 |
+
material.set_editor_property("two_sided", True)
|
| 74 |
+
material.set_editor_property("shading_model", unreal.MaterialShadingModel.MSM_UNLIT)
|
| 75 |
+
sample = unreal.MaterialEditingLibrary.create_material_expression(
|
| 76 |
+
material, unreal.MaterialExpressionTextureSampleParameter2D, -400, 0
|
| 77 |
+
)
|
| 78 |
+
sample.set_editor_property("parameter_name", "OpeningVideo")
|
| 79 |
+
sample.set_editor_property("texture", texture)
|
| 80 |
+
unreal.MaterialEditingLibrary.connect_material_property(
|
| 81 |
+
sample, "RGB", unreal.MaterialProperty.MP_EMISSIVE_COLOR
|
| 82 |
+
)
|
| 83 |
+
unreal.MaterialEditingLibrary.layout_material_expressions(material)
|
| 84 |
+
unreal.EditorAssetLibrary.save_loaded_asset(material)
|
| 85 |
+
return material
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def place_opening_actor(material):
|
| 89 |
+
opening_class = unreal.load_class(None, "/Script/GitWorldUnreal.GitWorldOpeningScene")
|
| 90 |
+
if not opening_class:
|
| 91 |
+
raise RuntimeError("GitWorldOpeningScene class is unavailable; build the Unreal module first")
|
| 92 |
+
actor = unreal.EditorLevelLibrary.spawn_actor_from_class(
|
| 93 |
+
opening_class, unreal.Vector(0.0, 0.0, 1050.0), unreal.Rotator(0.0, 0.0, 0.0)
|
| 94 |
+
)
|
| 95 |
+
actor.set_actor_label("GitWorld_Opening_3099")
|
| 96 |
+
actor.set_editor_property("opening_video_file", "ReferenceSource/Opening/3099.mp4")
|
| 97 |
+
actor.set_editor_property("screen_material", material)
|
| 98 |
+
actor.set_editor_property("b_play_on_begin", True)
|
| 99 |
+
return actor
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def run():
|
| 103 |
+
texture = create_media_assets()
|
| 104 |
+
material = create_screen_material(texture)
|
| 105 |
+
place_opening_actor(material)
|
| 106 |
+
unreal.EditorLevelLibrary.save_current_level()
|
| 107 |
+
unreal.log("GitWorld opening scene wired: 3099.mp4")
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
run()
|
phase2-unreal/EditorScripts/import_gitworld_reference_scene.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Run inside Unreal Editor's Python console or Execute Python Script.
|
| 2 |
+
|
| 3 |
+
Imports the supplied Phase 3 reference plates and places them as emissive
|
| 4 |
+
gallery panels in the pax-coder repository sandbox. This is a visual reference
|
| 5 |
+
scene, not a photogrammetry reconstruction.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import pathlib
|
| 9 |
+
|
| 10 |
+
import unreal
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
PROJECT_DIR = pathlib.Path(unreal.Paths.project_dir())
|
| 14 |
+
SOURCE_DIR = PROJECT_DIR / "ReferenceSource" / "NeonReflections"
|
| 15 |
+
DESTINATION = "/Game/RealityScene/NeonReflections"
|
| 16 |
+
REPOSITORY_ID = "pax-coder"
|
| 17 |
+
REFERENCE_SET_ID = "neon-reflections-v15"
|
| 18 |
+
IMAGE_FILES = ["3087.webp", "3089.webp", "3090.webp", "3092.jpg"]
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def import_reference_images():
|
| 22 |
+
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
| 23 |
+
imported = []
|
| 24 |
+
for file_name in IMAGE_FILES:
|
| 25 |
+
source_file = SOURCE_DIR / file_name
|
| 26 |
+
if not source_file.is_file():
|
| 27 |
+
unreal.log_warning("Missing reference plate: %s" % source_file)
|
| 28 |
+
continue
|
| 29 |
+
task = unreal.AssetImportTask()
|
| 30 |
+
task.set_editor_property("filename", str(source_file))
|
| 31 |
+
task.set_editor_property("destination_path", DESTINATION)
|
| 32 |
+
task.set_editor_property("destination_name", pathlib.Path(file_name).stem)
|
| 33 |
+
task.set_editor_property("automated", True)
|
| 34 |
+
task.set_editor_property("replace_existing", True)
|
| 35 |
+
task.set_editor_property("save", True)
|
| 36 |
+
asset_tools.import_asset_tasks([task])
|
| 37 |
+
imported.extend(task.get_editor_property("imported_object_paths"))
|
| 38 |
+
return [unreal.load_asset(path) for path in imported]
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def make_emissive_material(texture, name):
|
| 42 |
+
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
| 43 |
+
package_path = DESTINATION + "/Materials"
|
| 44 |
+
factory = unreal.MaterialFactoryNew()
|
| 45 |
+
material = asset_tools.create_asset(name, package_path, unreal.Material, factory)
|
| 46 |
+
if not material:
|
| 47 |
+
return None
|
| 48 |
+
material.set_editor_property("two_sided", True)
|
| 49 |
+
material.set_editor_property("shading_model", unreal.MaterialShadingModel.MSM_UNLIT)
|
| 50 |
+
sample = unreal.MaterialEditingLibrary.create_material_expression(
|
| 51 |
+
material, unreal.MaterialExpressionTextureSampleParameter2D, -400, 0
|
| 52 |
+
)
|
| 53 |
+
sample.set_editor_property("parameter_name", "ReferenceTexture")
|
| 54 |
+
sample.set_editor_property("texture", texture)
|
| 55 |
+
unreal.MaterialEditingLibrary.connect_material_property(
|
| 56 |
+
sample, "RGB", unreal.MaterialProperty.MP_EMISSIVE_COLOR
|
| 57 |
+
)
|
| 58 |
+
unreal.MaterialEditingLibrary.layout_material_expressions(material)
|
| 59 |
+
unreal.EditorAssetLibrary.save_loaded_asset(material)
|
| 60 |
+
return material
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def place_gallery_panel(mesh, material, location, rotation, scale, label):
|
| 64 |
+
actor = unreal.EditorLevelLibrary.spawn_actor_from_object(
|
| 65 |
+
mesh, unreal.Vector(*location), unreal.Rotator(*rotation)
|
| 66 |
+
)
|
| 67 |
+
actor.set_actor_label(label)
|
| 68 |
+
actor.set_actor_scale3d(unreal.Vector(*scale))
|
| 69 |
+
component = actor.get_component_by_class(unreal.StaticMeshComponent)
|
| 70 |
+
if component and material:
|
| 71 |
+
component.set_material(0, material)
|
| 72 |
+
return actor
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def run():
|
| 76 |
+
textures = import_reference_images()
|
| 77 |
+
if not textures:
|
| 78 |
+
raise RuntimeError("No reference plates imported from %s" % SOURCE_DIR)
|
| 79 |
+
|
| 80 |
+
plane = unreal.load_asset("/Engine/BasicShapes/Plane.Plane")
|
| 81 |
+
if not plane:
|
| 82 |
+
raise RuntimeError("Engine plane mesh is unavailable")
|
| 83 |
+
|
| 84 |
+
placements = [
|
| 85 |
+
((0.0, 1800.0, 900.0), (90.0, 0.0, 0.0), (12.0, 8.0), "NeonRef_North"),
|
| 86 |
+
((0.0, -1800.0, 900.0), (-90.0, 0.0, 180.0), (12.0, 8.0), "NeonRef_South"),
|
| 87 |
+
((1800.0, 0.0, 900.0), (90.0, 0.0, 90.0), (12.0, 8.0), "NeonRef_East"),
|
| 88 |
+
((-1800.0, 0.0, 900.0), (90.0, 0.0, -90.0), (12.0, 8.0), "NeonRef_West"),
|
| 89 |
+
]
|
| 90 |
+
|
| 91 |
+
for texture, placement in zip(textures, placements):
|
| 92 |
+
material = make_emissive_material(texture, "MI_" + texture.get_name())
|
| 93 |
+
place_gallery_panel(plane, material, *placement)
|
| 94 |
+
|
| 95 |
+
repo_class = unreal.load_class(None, "/Script/GitWorldUnreal.GitWorldRepoWorld")
|
| 96 |
+
if repo_class:
|
| 97 |
+
repo_actor = next(
|
| 98 |
+
(
|
| 99 |
+
actor
|
| 100 |
+
for actor in unreal.EditorLevelLibrary.get_all_level_actors()
|
| 101 |
+
if actor.is_a(repo_class)
|
| 102 |
+
and actor.get_editor_property("repository_id") == REPOSITORY_ID
|
| 103 |
+
),
|
| 104 |
+
None,
|
| 105 |
+
)
|
| 106 |
+
if repo_actor:
|
| 107 |
+
repo_actor.set_editor_property("reference_set_id", REFERENCE_SET_ID)
|
| 108 |
+
repo_actor.set_editor_property("reference_images", textures)
|
| 109 |
+
unreal.EditorAssetLibrary.save_loaded_asset(repo_actor)
|
| 110 |
+
|
| 111 |
+
unreal.EditorLevelLibrary.save_current_level()
|
| 112 |
+
unreal.log("GitWorld Phase 3 reference gallery placed: %s" % REFERENCE_SET_ID)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
run()
|
phase2-unreal/GitWorldUnreal.uproject
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"FileVersion": 3,
|
| 3 |
+
"EngineAssociation": "",
|
| 4 |
+
"Category": "Games",
|
| 5 |
+
"Description": "GitWorld Phase 2: walkable repository worlds with an optional CUDA build-field compute pass.",
|
| 6 |
+
"Modules": [
|
| 7 |
+
{
|
| 8 |
+
"Name": "GitWorldUnreal",
|
| 9 |
+
"Type": "Runtime",
|
| 10 |
+
"LoadingPhase": "Default"
|
| 11 |
+
}
|
| 12 |
+
],
|
| 13 |
+
"Plugins": [
|
| 14 |
+
{
|
| 15 |
+
"Name": "Niagara",
|
| 16 |
+
"Enabled": true
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"Name": "OpenXR",
|
| 20 |
+
"Enabled": true
|
| 21 |
+
}
|
| 22 |
+
]
|
| 23 |
+
}
|
phase2-unreal/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GitWorld Phase 2 — Unreal walkable worlds
|
| 2 |
+
|
| 3 |
+
## Objective
|
| 4 |
+
|
| 5 |
+
Move the completed browser-local GitWorld concept into a real-time Unreal project with a walkable third-person space, repository-owned sandbox state, visible build progress, and a high-resolution rendering profile.
|
| 6 |
+
|
| 7 |
+
## Player experience
|
| 8 |
+
|
| 9 |
+
The player spawns in a GitWorld map containing repository districts. Each repository is an `AGitWorldRepoWorld` actor with its own deterministic snapshot, geometry, events, and terminal transcript. Walking into an `AGitWorldRepoPortal` activates that repository in the world subsystem. Build ticks change district geometry and the sign above the world, so construction is visible in-world rather than represented only by UI.
|
| 10 |
+
|
| 11 |
+
## Architecture
|
| 12 |
+
|
| 13 |
+
- `GitWorldSimulation.*` — deterministic, headless repository state transitions and terminal output.
|
| 14 |
+
- `AGitWorldRepoWorld` — one sandboxed repository world; owns its snapshot, generated blocks, sign, and compute result.
|
| 15 |
+
- `AGitWorldRepoPortal` — overlap trigger that switches the active repository when the player walks in.
|
| 16 |
+
- `UGitWorldWorldSubsystem` — registry and active-world selection; no repository actor mutates another repository’s state.
|
| 17 |
+
- `FGitWorldCudaBridge` — optional dynamic CUDA DLL boundary with a deterministic CPU fallback.
|
| 18 |
+
- `AGitWorldGameMode` — temporary demo catalog for `pax-coder`, `agent-minefield`, and `cuda-lab`; replace with a data asset in Phase 3.
|
| 19 |
+
|
| 20 |
+
## Rendering and 8K target
|
| 21 |
+
|
| 22 |
+
`Config/DefaultEngine.ini` enables a DX12-oriented Nanite/Lumen profile, virtual shadow maps, hardware ray tracing, and a 7680×4320 game-user resolution target. Use `r.SetRes 7680x4320f` for a desktop viewport or Movie Render Queue for a high-resolution capture. A real 8K interactive session is a performance target, not a promise that every GPU can sustain 60 FPS at native resolution.
|
| 23 |
+
|
| 24 |
+
The local machine currently exposes an RTX 3080 with 10,240 MiB VRAM. Unreal Editor and `nvcc` were not available on PATH during scaffold validation, so Unreal compilation, CUDA device execution, and native 8K frame-time measurement remain pending until those tools are installed.
|
| 25 |
+
|
| 26 |
+
## Run when Unreal is installed
|
| 27 |
+
|
| 28 |
+
1. Right-click `GitWorldUnreal.uproject` and generate Visual Studio project files.
|
| 29 |
+
2. Build `GitWorldUnrealEditor` in Development Editor / Win64.
|
| 30 |
+
3. Open the project, press Play, and walk with the default pawn.
|
| 31 |
+
4. Confirm the repository sign, build tick, portal activation, and terminal transcript.
|
| 32 |
+
5. If desired, build `Compute/GitWorldCuda.dll` and place it in `Binaries/Win64/` to enable the CUDA compute pass.
|
| 33 |
+
|
| 34 |
+
## Local verification
|
| 35 |
+
|
| 36 |
+
From the parent `gitworld` folder:
|
| 37 |
+
|
| 38 |
+
```text
|
| 39 |
+
node tools/verify-phase2.mjs
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
This verifies the project manifest, Unreal source contract, isolated repository identifiers, CUDA boundary, and 8K settings without requiring Unreal or CUDA to be installed.
|
phase2-unreal/RealityCapture/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# RealityCapture 1.5 → Unreal reality scene
|
| 2 |
+
|
| 3 |
+
RealityCapture 1.5.1 is installed locally at:
|
| 4 |
+
|
| 5 |
+
```text
|
| 6 |
+
C:\Program Files\Capturing Reality\RealityCapture\RealityCapture.exe
|
| 7 |
+
```
|
| 8 |
+
|
| 9 |
+
The reconstruction pipeline is in `Tools/reconstruct.ps1`. It creates one isolated RealityCapture project and FBX output per repository. The resulting FBX is imported into Unreal as a `UStaticMesh`, then assigned to the `RealityCaptureMesh` property of that repository’s `AGitWorldRepoWorld` actor.
|
| 10 |
+
|
| 11 |
+
## Input requirement
|
| 12 |
+
|
| 13 |
+
Provide a deliberate capture folder containing at least three overlapping photos of the same physical scene. The current Downloads folder contains mixed personal/media files, so it is not selected automatically.
|
| 14 |
+
|
| 15 |
+
## Reconstruct a repository scene
|
| 16 |
+
|
| 17 |
+
From this `phase2-unreal` directory:
|
| 18 |
+
|
| 19 |
+
```powershell
|
| 20 |
+
& .\RealityCapture\Tools\reconstruct.ps1 `
|
| 21 |
+
-RepositoryId pax-coder `
|
| 22 |
+
-InputFolder 'D:\Captures\pax-coder-plaza'
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
The script runs `-addFolder`, `-align`, `-selectMaximalComponent`, automatic reconstruction-region setup, model calculation, texturing, FBX export, and project save. RealityCapture’s command-line workflow and these reconstruction/export commands are documented by Capturing Reality: [CLI](https://rchelp.capturingreality.com/en-US/tutorials/commandline.htm), [reconstruction examples](https://rchelp.capturingreality.com/en-US/tutorials/commandline_2.htm), and [model export](https://rchelp.capturingreality.com/en-US/tutorials/commandline_3.htm).
|
| 26 |
+
|
| 27 |
+
## Import into Unreal
|
| 28 |
+
|
| 29 |
+
1. Generate Visual Studio project files from `GitWorldUnreal.uproject`.
|
| 30 |
+
2. Import `RealityCapture/Output/<repository-id>/<repository-id>.fbx` into `/Game/RealityScene/<repository-id>`.
|
| 31 |
+
3. Enable Nanite on the imported static mesh and create a collision setup appropriate for walking.
|
| 32 |
+
4. Assign the mesh to `RealityCaptureMesh` on the matching `AGitWorldRepoWorld` actor.
|
| 33 |
+
5. Adjust `RealitySceneTransform` for centimeters/origin alignment; keep the repo’s walkable bounds around the imported scene.
|
| 34 |
+
6. Press Play and walk through the physical scene while build blocks, signs, portals, and events remain attached to the repository sandbox.
|
| 35 |
+
|
| 36 |
+
The Unreal renderer remains DX12/Nanite/Lumen. CUDA is an optional compute pass for build-field weights; RealityCapture’s photogrammetry reconstruction is run by its own installed application.
|
phase2-unreal/RealityCapture/Tools/reconstruct.ps1
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[CmdletBinding()]
|
| 2 |
+
param(
|
| 3 |
+
[Parameter(Mandatory = $true)]
|
| 4 |
+
[ValidatePattern('^[a-z0-9][a-z0-9-]*$')]
|
| 5 |
+
[string]$RepositoryId,
|
| 6 |
+
|
| 7 |
+
[Parameter(Mandatory = $true)]
|
| 8 |
+
[string]$InputFolder,
|
| 9 |
+
|
| 10 |
+
[ValidateSet('Preview', 'Normal', 'High')]
|
| 11 |
+
[string]$Detail = 'Normal',
|
| 12 |
+
|
| 13 |
+
[switch]$ValidateOnly
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
$ErrorActionPreference = 'Stop'
|
| 17 |
+
|
| 18 |
+
$realityCapture = 'C:\Program Files\Capturing Reality\RealityCapture\RealityCapture.exe'
|
| 19 |
+
$scriptRoot = Split-Path -Parent $PSScriptRoot
|
| 20 |
+
$outputRoot = Join-Path $scriptRoot 'Output'
|
| 21 |
+
$repositoryOutput = Join-Path $outputRoot $RepositoryId
|
| 22 |
+
$projectFile = Join-Path $repositoryOutput ($RepositoryId + '.rcproj')
|
| 23 |
+
$modelFile = Join-Path $repositoryOutput ($RepositoryId + '.fbx')
|
| 24 |
+
|
| 25 |
+
if (-not (Test-Path -LiteralPath $realityCapture -PathType Leaf)) {
|
| 26 |
+
throw "RealityCapture 1.5 executable not found at $realityCapture"
|
| 27 |
+
}
|
| 28 |
+
if (-not (Test-Path -LiteralPath $InputFolder -PathType Container)) {
|
| 29 |
+
throw "Capture folder not found: $InputFolder"
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
$imageExtensions = @('.jpg', '.jpeg', '.png', '.tif', '.tiff', '.webp')
|
| 33 |
+
$images = @(Get-ChildItem -LiteralPath $InputFolder -File -Recurse | Where-Object {
|
| 34 |
+
$imageExtensions -contains $_.Extension.ToLowerInvariant()
|
| 35 |
+
})
|
| 36 |
+
if ($images.Count -lt 3) {
|
| 37 |
+
throw "RealityCapture needs at least 3 overlapping image files; found $($images.Count) in $InputFolder"
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
New-Item -ItemType Directory -Path $repositoryOutput -Force | Out-Null
|
| 41 |
+
|
| 42 |
+
if ($ValidateOnly) {
|
| 43 |
+
Write-Output "RealityCapture input PASS: $($images.Count) image files for $RepositoryId"
|
| 44 |
+
exit 0
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
$modelCommand = switch ($Detail) {
|
| 48 |
+
'Preview' { '-calculatePreviewModel' }
|
| 49 |
+
'High' { '-calculateHighModel' }
|
| 50 |
+
default { '-calculateNormalModel' }
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
$arguments = @(
|
| 54 |
+
'-newScene',
|
| 55 |
+
'-addFolder', $InputFolder,
|
| 56 |
+
'-align',
|
| 57 |
+
'-selectMaximalComponent',
|
| 58 |
+
'-setReconstructionRegionAuto',
|
| 59 |
+
$modelCommand,
|
| 60 |
+
'-calculateTexture',
|
| 61 |
+
'-exportSelectedModel', $modelFile,
|
| 62 |
+
'-save', $projectFile,
|
| 63 |
+
'-quit'
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
Write-Output "Starting RealityCapture 1.5 reconstruction for $RepositoryId with $($images.Count) images..."
|
| 67 |
+
& $realityCapture @arguments
|
| 68 |
+
if ($LASTEXITCODE -ne 0) {
|
| 69 |
+
throw "RealityCapture exited with code $LASTEXITCODE"
|
| 70 |
+
}
|
| 71 |
+
if (-not (Test-Path -LiteralPath $modelFile -PathType Leaf)) {
|
| 72 |
+
throw "RealityCapture completed without producing $modelFile"
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
Write-Output "Reality scene ready: $modelFile"
|
phase2-unreal/ReferenceSource/NeonReflections/3087.webp
ADDED
|
Git LFS Details
|
phase2-unreal/ReferenceSource/NeonReflections/3089.webp
ADDED
|
Git LFS Details
|
phase2-unreal/ReferenceSource/NeonReflections/3090.webp
ADDED
|
Git LFS Details
|
phase2-unreal/ReferenceSource/NeonReflections/3092.jpg
ADDED
|
Git LFS Details
|
phase2-unreal/ReferenceSource/Opening/3099.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:eaaea396db131ac4681bd51cb4dc258ba7cae15efeebab77d752e335ca486ae0
|
| 3 |
+
size 5190235
|
phase2-unreal/ReferenceSource/README.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GitWorld Phase 3 reference set
|
| 2 |
+
|
| 3 |
+
The four user-selected files in `NeonReflections/` are wired as visual reference plates for the `pax-coder` GitWorld sandbox. They establish the neon social-hub, portal, avatar, and VR interface look for the Unreal scene.
|
| 4 |
+
|
| 5 |
+
They are intentionally not passed to the photogrammetry reconstruction script: the frames are generated/stylized renders from different compositions, not a camera sweep around one real subject. RealityCapture would need a separate folder of overlapping photographs to produce a valid 3D mesh.
|
| 6 |
+
|
| 7 |
+
The Unreal Editor script imports the plates, creates unlit display materials, places them on vertical gallery planes around the repository world, and assigns the imported textures to the world actor’s `ReferenceImages` property. This gives the project a visible scene using the supplied art while preserving the separate true-photogrammetry path.
|
phase2-unreal/ReferenceSource/manifest.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"referenceSetId": "neon-reflections-v15",
|
| 3 |
+
"repositoryWorld": "pax-coder",
|
| 4 |
+
"sourceType": "visual-reference-plates",
|
| 5 |
+
"photogrammetryReady": false,
|
| 6 |
+
"reason": "The supplied frames depict different stylized scenes and a character turntable; they are not overlapping views of one physical location.",
|
| 7 |
+
"openingScene": {
|
| 8 |
+
"file": "Opening/3099.mp4",
|
| 9 |
+
"role": "gitworld-opening-cinematic",
|
| 10 |
+
"autoplay": true
|
| 11 |
+
},
|
| 12 |
+
"assets": [
|
| 13 |
+
{
|
| 14 |
+
"file": "3087.webp",
|
| 15 |
+
"role": "arcade-portal-reference",
|
| 16 |
+
"placement": "east-gallery"
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"file": "3089.webp",
|
| 20 |
+
"role": "social-hub-pov-reference",
|
| 21 |
+
"placement": "north-gallery"
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"file": "3090.webp",
|
| 25 |
+
"role": "social-hub-spawn-reference",
|
| 26 |
+
"placement": "west-gallery"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "3092.jpg",
|
| 30 |
+
"role": "avatar-turntable-reference",
|
| 31 |
+
"placement": "avatar-gallery"
|
| 32 |
+
}
|
| 33 |
+
]
|
| 34 |
+
}
|
phase2-unreal/Source/GitWorldUnreal.Target.cs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using UnrealBuildTool;
|
| 2 |
+
using System.Collections.Generic;
|
| 3 |
+
|
| 4 |
+
public class GitWorldUnrealTarget : TargetRules
|
| 5 |
+
{
|
| 6 |
+
public GitWorldUnrealTarget(TargetInfo Target) : base(Target)
|
| 7 |
+
{
|
| 8 |
+
Type = TargetType.Game;
|
| 9 |
+
DefaultBuildSettings = BuildSettingsVersion.V5;
|
| 10 |
+
ExtraModuleNames.Add("GitWorldUnreal");
|
| 11 |
+
}
|
| 12 |
+
}
|
phase2-unreal/Source/GitWorldUnreal/GitWorldUnreal.Build.cs
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using UnrealBuildTool;
|
| 2 |
+
using System;
|
| 3 |
+
using System.IO;
|
| 4 |
+
|
| 5 |
+
public class GitWorldUnreal : ModuleRules
|
| 6 |
+
{
|
| 7 |
+
public GitWorldUnreal(ReadOnlyTargetRules Target) : base(Target)
|
| 8 |
+
{
|
| 9 |
+
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
| 10 |
+
PublicDependencyModuleNames.AddRange(new string[]
|
| 11 |
+
{
|
| 12 |
+
"Core",
|
| 13 |
+
"CoreUObject",
|
| 14 |
+
"Engine",
|
| 15 |
+
"InputCore",
|
| 16 |
+
"MediaAssets",
|
| 17 |
+
"HeadMountedDisplay"
|
| 18 |
+
});
|
| 19 |
+
PrivateDependencyModuleNames.AddRange(new string[] { "Projects" });
|
| 20 |
+
|
| 21 |
+
// CUDA is deliberately optional. The game still builds and runs with the
|
| 22 |
+
// deterministic CPU implementation when the CUDA DLL is absent.
|
| 23 |
+
PublicDefinitions.Add("GITWORLD_WITH_CUDA=0");
|
| 24 |
+
string cudaLibrary = Environment.GetEnvironmentVariable("GITWORLD_CUDA_DLL");
|
| 25 |
+
if (!string.IsNullOrWhiteSpace(cudaLibrary))
|
| 26 |
+
{
|
| 27 |
+
PublicDefinitions.Add("GITWORLD_CUDA_DLL_CONFIGURED=1");
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
string openingVideo = Path.GetFullPath(Path.Combine(
|
| 31 |
+
ModuleDirectory,
|
| 32 |
+
"..",
|
| 33 |
+
"..",
|
| 34 |
+
"..",
|
| 35 |
+
"ReferenceSource",
|
| 36 |
+
"Opening",
|
| 37 |
+
"3099.mp4"));
|
| 38 |
+
if (File.Exists(openingVideo))
|
| 39 |
+
{
|
| 40 |
+
RuntimeDependencies.Add(
|
| 41 |
+
"$(ProjectDir)/ReferenceSource/Opening/3099.mp4",
|
| 42 |
+
openingVideo,
|
| 43 |
+
StagedFileType.NonUFS);
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
}
|