diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index e962f73..e5a53b2 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -1,6 +1,7 @@
name: Deploy to GitHub Pages
on:
+ pull_request:
push:
branches: [main]
workflow_dispatch:
@@ -12,7 +13,7 @@ permissions:
# Let a running deploy finish rather than cancelling it mid-publish.
concurrency:
- group: pages
+ group: pages-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: false
jobs:
@@ -40,6 +41,26 @@ jobs:
- name: Build
run: npm run build
+ - name: Cache Godot toolchain
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.cache/universal-ai/godot/4.7.2
+ ~/.local/share/godot/export_templates/4.7.2.stable
+ key: godot-web-linux-4.7.2
+
+ - name: Set up Godot
+ run: npm run godot:setup
+
+ - name: Test Godot prototype
+ run: npm run godot:test
+
+ - name: Export Godot prototype
+ run: npm run godot:build
+
+ - name: Stage prototype at /seed/
+ run: npm run godot:stage
+
# The hashed entry filename is this build's fingerprint. `verify` asserts
# the live site references exactly this one, which is the only way to tell
# "the deploy worked" apart from "something else published something else".
@@ -54,13 +75,46 @@ jobs:
echo "entry=$entry" >> "$GITHUB_OUTPUT"
echo "Built entry: $entry"
+ - name: Install test browsers
+ run: |
+ npx playwright install --with-deps chromium firefox
+ sudo apt-get install -y xvfb libgl1-mesa-dri libegl-mesa0 mesa-utils
+
+ - name: Test chooser and classic browser flows
+ run: npx playwright test
+
+ - name: Test Godot in Chromium and Firefox
+ env:
+ GODOT_BROWSER_HEADED: '1'
+ GODOT_BROWSER_ANGLE: gl
+ LIBGL_ALWAYS_SOFTWARE: '1'
+ run: |
+ xvfb-run --auto-servernum glxinfo -B
+ xvfb-run --auto-servernum --server-args="-screen 0 1440x1000x24" npx playwright test --config playwright.godot.config.ts --max-failures=1 --reporter=list
+
+ - name: Save browser checks and chooser screenshots
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: browser-checks
+ path: |
+ test-results/
+ build/test-results-godot/
+ retention-days: 7
+
+ - name: Record complete release
+ run: python scripts/verify_pages.py record --commit "$GITHUB_SHA"
+
- uses: actions/configure-pages@v5
+ if: github.event_name != 'pull_request'
- uses: actions/upload-pages-artifact@v3
+ if: github.event_name != 'pull_request'
with:
path: dist
deploy:
+ if: github.event_name != 'pull_request'
needs: build
runs-on: ubuntu-latest
environment:
@@ -72,46 +126,12 @@ jobs:
- id: deployment
uses: actions/deploy-pages@v4
- # A green deploy job only means the artifact was accepted. It does not mean the
- # artifact is what the domain serves: Pages will also publish the raw repository
- # from a branch if its source is set that way, and that publish can land after
- # this one and win. When it does, visitors get index.html with `src/main.tsx` in
- # it, no bundle, and a grey screen — while every check here stays green. This
- # job is what makes that condition fail out loud instead.
verify:
needs: [build, deploy]
runs-on: ubuntu-latest
steps:
- - name: Assert the live site is the build we just published
+ - uses: actions/checkout@v4
+ - name: Verify all three live entry points and Godot assets
env:
PAGE_URL: ${{ needs.deploy.outputs.page_url }}
- ENTRY: ${{ needs.build.outputs.entry }}
- run: |
- url="${PAGE_URL:-https://paperclips.opsvibe.systems/}"
- echo "Expecting $url to reference $ENTRY"
-
- # Pages fronts the site with a CDN; a fresh publish takes a moment to
- # reach the edge. Poll rather than race it.
- for attempt in $(seq 1 12); do
- html=$(curl -fsSL --max-time 30 "$url?cachebust=$GITHUB_RUN_ID-$attempt" || true)
-
- if printf '%s' "$html" | grep -qF "$ENTRY"; then
- echo "OK — live site is serving $ENTRY"
- exit 0
- fi
-
- if printf '%s' "$html" | grep -qF 'src/main.tsx'; then
- echo "Attempt $attempt: still serving the unbuilt source index.html"
- else
- echo "Attempt $attempt: $ENTRY not present yet"
- fi
- sleep 20
- done
-
- echo "::error::$url is not serving this build's entry script ($ENTRY)."
- if printf '%s' "$html" | grep -qF 'src/main.tsx'; then
- echo "::error::It is serving the repository's source index.html, which loads /src/main.tsx — a file browsers cannot execute. That is the grey screen."
- echo "::error::Cause: GitHub Pages is publishing from a branch, so the pages-build-deployment workflow uploads the repo root and overwrites this artifact."
- echo "::error::Fix: Settings -> Pages -> Build and deployment -> Source -> GitHub Actions."
- fi
- exit 1
+ run: python scripts/verify_pages.py check --commit "$GITHUB_SHA" --url "$PAGE_URL"
diff --git a/.gitignore b/.gitignore
index 5a86d2a..b639c5f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,9 @@ coverage/
*.log
.env*
!.env.example
+playwright-report/
+test-results/
+
+# Godot imported resources are rebuilt from the source assets.
+godot/.godot/
+godot/.godot-export/
diff --git a/README.md b/README.md
index d56f61f..767607c 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,18 @@
An idle game about optimization, and about what happens when you stop being the
one doing it.
-**Play it: [paperclips.opsvibe.systems](https://paperclips.opsvibe.systems)**
+**Choose a version: [paperclips.opsvibe.systems](https://paperclips.opsvibe.systems)**
+
+The shared entrance describes both games and their contributors:
+
+- `/classic/`: the full React/TypeScript game, with the recent observatory UI.
+- `/seed/`: The Seed, the independent Godot opening prototype.
+
+**Godot performance is still under investigation.** Severe slowdowns persisted
+on the development laptop after initial fixes. Testing from another machine
+is pending. Read the [performance and development handoff](docs/PERFORMANCE-HANDOFF.md)
+before resuming local tests; local servers and browsers were shut down at the
+user’s request.
You start by etching NPU chips one at a time. You buy a fab, then fifty. You set
a price, chase demand, and earn trust. Then you hand the wheel to an autonomous
@@ -17,6 +28,18 @@ story. Play the original first.
---
+## Godot prototype: The Seed
+
+A new playable 3D fabrication room lives in [`godot/`](godot/README.md): moving
+etch heads, physical chip output, six autonomous fabs, synthesized sound, and a
+district reveal. Run `npm run godot:build && npm run godot:serve`, then open
+http://localhost:4180. Use `npm run godot:editor` to open the native project.
+
+The Pages workflow includes it at `/seed/`, alongside `/classic/` and the
+lightweight chooser at `/`. This is a standalone opening-loop
+prototype with its own save; the full React game below remains available. See
+the [Godot guide](godot/README.md) for controls, setup, tests, and current scope.
+
## Running it
```bash
@@ -29,6 +52,7 @@ npm run lint # tsc --noEmit, strict
npm test # vitest
npm run build # static files in dist/
npm run preview # serve the real build with the real CSP
+npm run test:browser # build + Chromium browser regression tests
```
No API keys. No `.env`. No backend. `npm run build` emits static files and
@@ -36,6 +60,28 @@ that's the entire deployment.
---
+## The observatory
+
+The interface is a live instrument: a floating processor above an etched silicon
+wafer, a planet being converted, and finally a luminous interstellar swarm.
+These are procedural Canvas scenes, driven by your actual game state. Alignment
+changes their light; new fabs join the wafer network; harvesting consumes the
+globe. They are schematics, not literal maps or one dot per probe.
+
+Fabricate directly from the observatory, build your first fab from its capital
+objective, or release the Overseer. Expand the view for a closer look; Escape
+returns to the controls. Animation can be paused independently of the game and
+respects reduced-motion preferences.
+
+A live production trace measures actual chips per elapsed second. Session
+transmissions record first fabrication, factory purchases, trust increases,
+projects, phase transitions, and directive overrides. Neither invents activity
+while the system is idle. These instruments reset on reload; your game save does
+not.
+
+The layout is designed for phones as well as desktops. All visuals and fonts
+are local, and the default engine needs no downloads beyond the app itself.
+
## The Overseer
You can play the whole game by hand. But the interesting part is Overseer mode,
@@ -179,13 +225,14 @@ can confirm by watching, and by reading
widens one way only. Within a phase it's still a fairly static grid.
- **Offline progress is capped at 8 hours**, so a laptop left shut for a month
isn't an instant win.
-- **The mobile layout is functional, not designed.** It works; it isn't nice.
+- **The observatory is a schematic.** It compresses huge populations into bounded
+ visual samples; use the numerical telemetry for exact quantities.
---
## How it's built
-React 19 + TypeScript + Vite + Tailwind 4. Canvas pixel-art renderer, SVG radar,
+React 19 + TypeScript + Vite + Tailwind 4. Procedural Canvas observatory, SVG production trace and radar,
Web Audio synthesizer. Deployed to GitHub Pages from `main` by
[`.github/workflows/deploy.yml`](.github/workflows/deploy.yml), which typechecks
and tests before it builds — and then fetches the live URL and fails unless it's
@@ -218,6 +265,15 @@ codebase carrying eighty-nine.
---
+## Browser checks
+
+Install Chromium once with `npx playwright install chromium`, then run
+`npm run test:browser`. The suite covers fabrication, the first fab, all three
+phases on mobile, the Overseer, animation controls, reduced motion, save
+restoration, production CSP, offline reload, and model-cache preservation.
+It starts preview on port 4173 and development on port 3000 when needed.
+For an existing system browser, set `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH`.
+
## Docs
- **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** — how the code is laid out,
diff --git a/classic/index.html b/classic/index.html
new file mode 100644
index 0000000..c16c929
--- /dev/null
+++ b/classic/index.html
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+ Universal AI — an idle game about optimization
+
+
+
+
+
+
+
+
+
+
+
+
+
+
UNIVERSAL AI
+
Loading…
+
+ The application bundle did not load.
+ Nothing is wrong with your browser — this page was served without its
+ compiled assets. Reloading will not help. Please report it at
+ github.com/TechLuddite/Universal-AI/issues .
+
+
+
+
+
+
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 8977dbf..daa9296 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -4,8 +4,21 @@ Orientation for anyone (human or agent) picking this up cold.
## Shape of the thing
-A single-page React app with no backend, no router, and no server state. The
-whole deployment is `npm run build` → static files → GitHub Pages.
+One static GitHub Pages deployment, with three document entry points:
+
+- `/`: a lightweight HTML/CSS chooser, original SVG artwork, and contributor credits.
+- `/classic/`: the complete React game. Vite builds both this and the chooser.
+- `/seed/`: the independent Godot export, copied into `dist/seed/` after Vite builds.
+
+Neither game is mounted by the chooser. Its small script only handles service
+worker updates and the local development link. No router, backend, or shared
+game state is required. Relative links/assets support custom domains and GitHub
+project paths. The root service worker precaches the chooser and classic app
+shell, but never the Godot engine. Classic localStorage keys are unchanged by
+the path move. Godot saves remain separate.
+
+See [PERFORMANCE-HANDOFF.md](PERFORMANCE-HANDOFF.md) before performance testing;
+the local shutdown instruction remains in force.
```
src/
@@ -28,11 +41,13 @@ src/
worker.ts WebLLM inference worker
components/ presentation. Props in, callbacks out.
+ WorldStage.tsx observatory canvas, chapter narrative, objectives, telemetry
+ SystemSignal.tsx measured production trace and bounded session event log
data/
upgrades.ts 32 upgrades; each has an effect(state) => Partial
decisionBranches.ts narrative forks, same effect shape
utils/
- pixelArt.ts canvas renderer
+ worldRenderer.ts procedural wafer / planet / swarm renderer
sound.ts Web Audio synthesizer
```
@@ -142,9 +157,31 @@ outgoing phase's panels mounted for `PHASE_DEMOLITION_MS`, and gives them
`renderedPhase` directly so loading into Phase 3 doesn't demolish panels the
player never had open.
+## Observatory
+
+`WorldStage.tsx` owns one animation loop and supplies current state through a
+ref to `utils/worldRenderer.ts`. ResizeObserver tracks the canvas size; device
+pixel ratio is capped at 2. The scene draws at 30 fps, suspends painting while
+hidden/offscreen, and paints static state twice a second when paused or when
+reduced motion is requested. Visual populations are bounded independently of
+game populations. The renderer does not mutate the simulation.
+
+The expanded view traps keyboard focus, makes the background inert, and restores
+focus on exit. Its controls call the same actions used by the operation panels.
+`SystemSignal.tsx` samples actual production deltas against elapsed time, retaining
+60 samples and 16 observed session transmissions. Neither is persisted.
+
+`tests/observatory.browser.ts` tests these user-facing claims in Chromium,
+including the production CSP and offline app shell. Run `npm run test:browser`.
+
## Saves
`save.ts` writes a versioned envelope to `localStorage` (`universal_ai_save_v1`).
+App restores the save in its state initializer, before autosave effects mount.
+Restoring in an effect allowed React StrictMode's cleanup to save the fresh
+initial state over the loaded run. A browser regression covers development
+startup and reload into a later phase.
+
Loading spreads over `createInitialState()`, so a save written before a field
existed loads with that field's default instead of crashing.
@@ -183,6 +220,12 @@ HMR's WebSocket works. Production ships the policy exactly as written — so
Generated at build time by a plugin in `vite.config.ts`. Precaches the **app
shell only** — entry chunks by `isEntry`, plus the manifest and icon.
+Each shell cache includes a hash of its HTML and asset list, so two local builds
+at the same commit cannot share incompatible HTML and bundles. Activation removes
+only previous `universal-ai-` caches. Shell lookups ignore `Vary`, because these
+are public static resources and preview's `Vary: Origin` would otherwise prevent
+module/style requests from matching the precached entries while offline.
+
It explicitly does not cache WebLLM's dynamic chunk, its worker, or anything
cross-origin. WebLLM manages its own multi-hundred-megabyte weight cache and a
service worker competing with it would be a disaster.
@@ -272,3 +315,11 @@ the healthy path is worse than an outage.
`space_exploration_initiative` is the only door to Phase 3. The tick never
changes `phase`, and the cosmic decision branch decides what the launched
swarm is *for*, not whether it launches.
+
+## Godot opening prototype
+
+`godot/` is an independent Godot 4.7.2 application exported to `dist/seed/`.
+Its pure `SeedSimulation` owns the small six-fab economy; the scene drives it
+at fixed 60 Hz and translates returned events into mesh animation and sound.
+It has a separate versioned browser save and no WebLLM dependency. See
+[`godot/README.md`](../godot/README.md) for its source map and verification.
diff --git a/docs/PERFORMANCE-HANDOFF.md b/docs/PERFORMANCE-HANDOFF.md
new file mode 100644
index 0000000..e07964b
--- /dev/null
+++ b/docs/PERFORMANCE-HANDOFF.md
@@ -0,0 +1,61 @@
+# Performance investigation — OPEN
+
+Updated 2026-09-10. **Do not treat the Godot performance problem as resolved.**
+
+The development laptop experienced severe system-wide slowdowns, initially
+near the first fab and later near the second. The user still reported excessive
+resource use after the audio fix and requested shutdown. All project servers
+and test browsers were stopped. Do not restart local servers, browsers, Godot,
+or local performance runs on that machine without a new explicit instruction.
+Remote GitHub Actions builds/tests and deployment are authorized.
+
+## What is established
+
+- Kernel logs recorded memory-allocation failures during the reported episodes.
+- The original frame loop assigned `ambience.stream_paused = false` every frame.
+ The Godot 4.7.2 web sample backend restarts an audio source on that assignment.
+ A guarded Firefox reproduction with sound crossed 1.6 GB almost immediately;
+ the muted comparison stayed around 800 MB. These are test-process-group
+ measurements, not an isolated game heap measurement.
+- The assignment now occurs only on a real sound toggle. Chromium and Firefox
+ passed the two-fab audio regression, including multiple ambient loops and
+ repeated mute/unmute. The isolated final Firefox test remained around
+ 1.1 GB for approximately 55 seconds.
+- A combined browser suite reached its conservative memory guard between test
+ cases; the final Firefox case was run separately. Browser sessions, test
+ instrumentation, file cache, and the game must be measured separately before
+ interpreting aggregate memory growth.
+- Machines/effects are prepared and pooled. Rendering is capped at 30 fps and
+ 1440 × 900 internally. MSAA and full-screen bloom are disabled. These changes
+ reduce work; they are not proof that the remaining problem is fixed.
+
+## Next investigation
+
+1. **Test the deployed site from another machine.** Record OS, browser/version,
+ GPU, available RAM, display resolution/scaling, and whether hardware
+ acceleration is enabled. Compare Firefox and Chromium where practical.
+2. Start one game tab from a fresh run. Record baseline CPU, GPU, process memory,
+ swap, and frame rate; then compare manual etching, one fab, two fabs, and later
+ expansion. Record elapsed time as well as which purchase preceded a dip.
+3. Compare sound on/off, background vs foreground, and reload vs a fresh browser
+ process. Watch beyond the short regression-test window. Stop the test if
+ system responsiveness or memory availability deteriorates.
+4. Distinguish the game tab from browser GPU/audio processes, test harnesses,
+ desktop shell, other tabs, and unrelated workloads. Avoid concurrent browser
+ test suites on the affected laptop. Use an isolated process memory limit for
+ intentional reproductions.
+5. Keep the landing page's experimental/performance notice until sustained
+ cross-machine evidence supports removing it. Do not close this investigation
+ solely because CI, simulation tests, or audio allocation checks pass.
+
+## Resuming development elsewhere
+
+Clone `https://github.com/TechLuddite/Universal-AI`, check out `main`, and read
+`CLAUDE.md`, `docs/ARCHITECTURE.md`, this file, and `godot/README.md`. The site
+chooser is `/`, the full React game is `/classic/`, and the Godot prototype is
+`/seed/`. Development source, original procedural assets, tests, and the Pages
+workflow are tracked; generated engine binaries and web exports are not.
+
+The games have separate browser-local saves. Changing browser, machine, or
+origin does not transfer progress automatically. Git transfers the project,
+not a running game's browser storage.
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index 869ff0f..42bf142 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -1,5 +1,15 @@
# Roadmap
+## Current priority: unresolved Godot performance
+
+Severe resource-use issues remain open after the initial rendering and audio
+fixes. The user requested that all local services and test browsers stay off.
+Test the deployed game from another machine before drawing further conclusions.
+See [PERFORMANCE-HANDOFF.md](PERFORMANCE-HANDOFF.md) for evidence, constraints,
+and the cross-machine investigation plan. The shared landing page keeps the
+complete React game and experimental Godot version available separately.
+
+
Stages 1–6 (repair, honesty pass, GitHub Pages) are done. Stage 7 (7.1–7.3) is
done, and Stage 8 (the cohesion pass and currency rescale) after it. What
follows is what's left, plus a record of what Stage 7 chose *not* to build and
@@ -175,7 +185,7 @@ it is a pure read. `utility.test.ts` now asserts that — no state mutation, ful
deterministic, and it never consumes the context's `rng` (drift rolls dice at
decision time, never at ranking time).
-### 7.5 Smaller wins — **done except mobile**
+### 7.5 Smaller wins — **done**
- **"While you were away" summary** — **done.** A proper card now
(`OfflineReportCard.tsx`): time away, chips produced, average rate, and an
@@ -186,7 +196,8 @@ decision time, never at ranking time).
"verified" badge — a page cannot prove its own integrity, and the comment in
`DevSupportModal.tsx` says so. What it offers instead is the pointer to check
from outside: the public Actions run, or build-and-diff.
-- **Mobile layout.** Functional, not designed. Still open.
+- **Mobile layout** — **done in the observatory pass.** Dedicated stacked scene,
+ two-column telemetry, responsive controls, and production browser coverage.
- **Canvas polish** — **done.** The rAF loop reads live values through a ref and
is created once, instead of being torn down and rebuilt ~10×/second by its own
dependency array; rendering is scaled by `devicePixelRatio` so it's no longer
@@ -241,3 +252,15 @@ honesty pass; the details live in the invariants sections of `CLAUDE.md` and
- Root `CNAME` and `public/CNAME` are duplicates. Only `public/` reaches the
build artifact; the root one was created by GitHub's UI. Harmless while they
agree — worth collapsing to one.
+
+## Observatory pass — complete
+
+- Procedural wafer, conversion globe, and cosmic swarm driven by game state.
+- New command header, chapter narrative, alignment telemetry, and actionable
+ first-fab objective.
+- Expanded observatory view with focus management, animation pause, reduced
+ motion, and bounded rendering work.
+- Real production history and a session transmission log.
+- Desktop and mobile layouts, plus Chromium regression coverage under the real CSP.
+- Save restoration before effects mount; app-shell cache isolation and offline
+ preview reload; preservation of separately owned model caches.
diff --git a/godot/README.md b/godot/README.md
new file mode 100644
index 0000000..a0d8734
--- /dev/null
+++ b/godot/README.md
@@ -0,0 +1,134 @@
+# Universal AI: The Seed
+
+**Performance investigation remains open.** The affected development laptop
+was shut down from testing at the user’s request. Further testing from another
+machine is pending; see [the handoff](../docs/PERFORMANCE-HANDOFF.md). Do not
+interpret the audio regression fix or passing tests as a complete resolution.
+
+
+A playable Godot 4.7.2 prototype of Universal AI's opening: turn one silicon
+wafer into a chip, install the first autonomous fab, fill six bays, research
+faster production, automate procurement, and connect the surrounding district.
+A focused run takes roughly three minutes. Production continues after the uplink.
+
+This is a standalone economy and save, not yet a port of the original game's
+three phases, alignment system, market, or optional local language model. The
+supply controller uses explicit deterministic rules; it is not an LLM.
+
+## Play locally
+
+From the repository root, with Python 3.11+:
+
+```sh
+npm run godot:setup
+npm run godot:build
+npm run godot:serve
+```
+
+Open http://localhost:4180. For native editing, use `npm run godot:editor` or
+import `godot/project.godot` into Godot 4.7.2. The Linux x86-64 helper downloads
+the official editor and export templates, verifies their SHA-256 checksums,
+and caches them outside the repository. The first template download is about
+1.3 GB because Godot distributes all platforms together; only web templates
+are retained. On other platforms, install the matching editor and set
+`GODOT_BIN` to its executable.
+
+## Controls
+
+| Input | Action |
+| --- | --- |
+| Space (hold to repeat), click the central machine, or Etch button | Fabricate and sell a chip |
+| B / R | Build a fab / order wafers |
+| O / A / U | Research overclock / toggle supply controller / district uplink |
+| Drag / mouse wheel | Orbit / zoom |
+| Click a fab / C | Inspect machine / toggle close-up |
+| Click the highlighted empty bay | Build the next fab |
+| F / Escape | Toggle cinema view / leave cinema view |
+| M / Home | Toggle sound / reset camera |
+| New Run | Confirm and reset this prototype's save |
+
+Touch users can use the action dock and drag the scene. Each etch consumes one
+wafer and pays $100 when finished. Twelve chips finance the first fab. Reserve
+$600 for a shipment of 30 wafers. If you run out of both silicon and money,
+procurement offers three reclaimed wafers so the run cannot become stranded.
+
+Progress saves automatically to Godot's browser storage, separately from the
+React game. Clearing site data removes it. There is no offline catch-up;
+suspended tabs pause production. Sound begins after player interaction.
+
+## What moves
+
+Each machine has a rotating iridescent wafer, traversing etch head, timed laser,
+loader arm, sparks, and a belt that carries each finished chip out. New machines
+rise into their bays. The camera opens on the manual workstation, pulls back
+with the first fab, and reveals a surrounding district at the uplink. Geometry,
+shaders, and synthesized WAV sound effects are original procedural assets.
+Static geometry is batched by material, with floor tiles instanced separately.
+All six machines and fixed pools of chips and sparks are prepared during loading;
+purchasing and production reuse those objects. Machine monitors show cycle state
+instead of rebuilding percentage text throughout every cycle.
+
+Rendering is capped at 30 fps and at a 1440 × 900 internal viewport (preserving
+aspect ratio on smaller or portrait screens). MSAA and the full-screen mipmap
+bloom pass are disabled to reduce GPU load. This trades some edge sharpness and
+glow for a lower rendering budget on laptops. The simulation still advances by
+elapsed time; the cap does not halve production speed.
+
+## Build and verification
+
+```sh
+npm run godot:test # 23 economy checks plus a headless scene smoke test
+npm run godot:test:browser # export, then Chromium and Firefox integration tests
+npm run build # existing React application
+npm run godot:stage # copy the previously exported game into dist/seed/
+```
+
+Install test browsers with `npx playwright install chromium firefox` if needed.
+For a system browser, set `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH`. On this Linux
+machine, `GODOT_BROWSER_ANGLE=gl` enables the hardware renderer in headless QA.
+Software WebGL rendering can be much slower than normal browser GPU rendering.
+GitHub CI uses a virtual display with Mesa and `GODOT_BROWSER_HEADED=1`, plus
+the GL backend, because headless Firefox lacks WebGL 2 on that runner and its
+default Chromium software backend misses the interactive timing budgets.
+
+The browser tests exercise actual fabrication, purchasing, automatic production,
+camera controls, reload persistence, and portrait touch controls. They also check
+that building and producing do not grow the scene node count, the frame-rate
+cap is applied, and large browser windows respect the internal resolution limit.
+Both Chromium and Firefox run an audio regression through two working fabs,
+multiple ambience loops, and repeated mute toggles. It counts actual Web Audio
+sources: scene node counts alone cannot detect browser audio allocations.
+
+Ambient pause state must only be assigned when the player toggles sound. In the
+4.7.2 web sample backend, repeatedly assigning `stream_paused = false` restarts
+the audio source even when already playing. The previous per-frame assignment
+created runaway sources and reproduced rapid memory growth in Firefox. The
+fix removes that assignment from the frame loop; it does not disable sound. The simulation
+test completes the entire economy from a fresh state without free resources.
+A separate manual browser playthrough also reached six fabs and the uplink.
+
+`?test=1` exposes a read-only `window.__seed` snapshot for browser assertions and
+disables saving. `?test=1&persist=1` enables saving for the reload test. There are
+no browser resource-grant or arbitrary game-action debug hooks.
+
+The Pages workflow builds both applications and publishes this prototype at
+`/seed/`, linked from the React header. The export uses single-threaded
+WebAssembly and WebGL 2 Compatibility rendering, so it needs no special
+cross-origin isolation headers or backend. All runtime assets are same-origin.
+The initial export is approximately 39 MB before transport compression.
+Godot and third-party engine notices ship in `licenses/`.
+
+## Source map
+
+- `scripts/simulation.gd`: independent state, actions, fixed-time production, saves.
+- `scripts/factory.gd`: input, fixed-step driver, camera, sound, persistence.
+- `scripts/machine.gd`: animated fabrication machinery and emitted chips.
+- `scripts/chamber.gd`: room, six bays, district reveal.
+- `scripts/geometry.gd`: procedural mesh helpers and static batching.
+- `scripts/interface.gd`: responsive Godot HUD and action availability.
+- `shaders/`: wafer surface and subtle screen finish.
+- `web/`: accessible loading/error screen, control reference, CSP-safe boot.
+
+The next substantial step is porting the original simulation's systems into
+this presentation, with explicit save migration and visual designs for each
+later phase. This prototype establishes the room and production loop first.
diff --git a/godot/assets/audio/build.wav b/godot/assets/audio/build.wav
new file mode 100644
index 0000000..2644637
Binary files /dev/null and b/godot/assets/audio/build.wav differ
diff --git a/godot/assets/audio/build.wav.import b/godot/assets/audio/build.wav.import
new file mode 100644
index 0000000..64f4f2a
--- /dev/null
+++ b/godot/assets/audio/build.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://d4fo1efai1der"
+path="res://.godot/imported/build.wav-f0f37dcc9742a49a9d642f149ad65bb7.sample"
+
+[deps]
+
+source_file="res://assets/audio/build.wav"
+dest_files=["res://.godot/imported/build.wav-f0f37dcc9742a49a9d642f149ad65bb7.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=2
diff --git a/godot/assets/audio/chip.wav b/godot/assets/audio/chip.wav
new file mode 100644
index 0000000..67af802
Binary files /dev/null and b/godot/assets/audio/chip.wav differ
diff --git a/godot/assets/audio/chip.wav.import b/godot/assets/audio/chip.wav.import
new file mode 100644
index 0000000..f1fa67f
--- /dev/null
+++ b/godot/assets/audio/chip.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://btcfc2loowb0"
+path="res://.godot/imported/chip.wav-7b79617eba4c7265636dafa75ae884e4.sample"
+
+[deps]
+
+source_file="res://assets/audio/chip.wav"
+dest_files=["res://.godot/imported/chip.wav-7b79617eba4c7265636dafa75ae884e4.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=2
diff --git a/godot/assets/audio/etch.wav b/godot/assets/audio/etch.wav
new file mode 100644
index 0000000..20a5295
Binary files /dev/null and b/godot/assets/audio/etch.wav differ
diff --git a/godot/assets/audio/etch.wav.import b/godot/assets/audio/etch.wav.import
new file mode 100644
index 0000000..d562df8
--- /dev/null
+++ b/godot/assets/audio/etch.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://da2d0ls4lr2f6"
+path="res://.godot/imported/etch.wav-854450636d791830a13ee2ac59cd4795.sample"
+
+[deps]
+
+source_file="res://assets/audio/etch.wav"
+dest_files=["res://.godot/imported/etch.wav-854450636d791830a13ee2ac59cd4795.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=2
diff --git a/godot/assets/audio/room.wav b/godot/assets/audio/room.wav
new file mode 100644
index 0000000..1e0bb7b
Binary files /dev/null and b/godot/assets/audio/room.wav differ
diff --git a/godot/assets/audio/room.wav.import b/godot/assets/audio/room.wav.import
new file mode 100644
index 0000000..a0ce488
--- /dev/null
+++ b/godot/assets/audio/room.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://djtdm8y0g5a5"
+path="res://.godot/imported/room.wav-7c6b2997a991d1898d4fbdd87c2251c4.sample"
+
+[deps]
+
+source_file="res://assets/audio/room.wav"
+dest_files=["res://.godot/imported/room.wav-7c6b2997a991d1898d4fbdd87c2251c4.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=2
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/godot/assets/audio/supply.wav b/godot/assets/audio/supply.wav
new file mode 100644
index 0000000..b8829f5
Binary files /dev/null and b/godot/assets/audio/supply.wav differ
diff --git a/godot/assets/audio/supply.wav.import b/godot/assets/audio/supply.wav.import
new file mode 100644
index 0000000..3255e16
--- /dev/null
+++ b/godot/assets/audio/supply.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://c002itmp2xnks"
+path="res://.godot/imported/supply.wav-da960aaafab25fafff3939617008d471.sample"
+
+[deps]
+
+source_file="res://assets/audio/supply.wav"
+dest_files=["res://.godot/imported/supply.wav-da960aaafab25fafff3939617008d471.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=2
diff --git a/godot/assets/audio/uplink.wav b/godot/assets/audio/uplink.wav
new file mode 100644
index 0000000..1ed9d9d
Binary files /dev/null and b/godot/assets/audio/uplink.wav differ
diff --git a/godot/assets/audio/uplink.wav.import b/godot/assets/audio/uplink.wav.import
new file mode 100644
index 0000000..10c08b5
--- /dev/null
+++ b/godot/assets/audio/uplink.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://buuy1yh1kxsvt"
+path="res://.godot/imported/uplink.wav-2928ce298d4a72892b79c1ce9504c5da.sample"
+
+[deps]
+
+source_file="res://assets/audio/uplink.wav"
+dest_files=["res://.godot/imported/uplink.wav-2928ce298d4a72892b79c1ce9504c5da.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=2
diff --git a/godot/export_presets.cfg b/godot/export_presets.cfg
new file mode 100644
index 0000000..e608866
--- /dev/null
+++ b/godot/export_presets.cfg
@@ -0,0 +1,30 @@
+[preset.0]
+name="Web"
+platform="Web"
+runnable=true
+dedicated_server=false
+custom_features=""
+export_filter="all_resources"
+include_filter=""
+exclude_filter="tests/*,web/*"
+export_path="../build/godot/index.html"
+encryption_include_filters=""
+encryption_exclude_filters=""
+encrypt_pck=false
+encrypt_directory=false
+script_export_mode=2
+
+[preset.0.options]
+custom_template/debug=""
+custom_template/release=""
+variant/extensions_support=false
+variant/thread_support=false
+vram_texture_compression/for_desktop=true
+vram_texture_compression/for_mobile=false
+html/export_icon=true
+html/custom_html_shell="res://web/shell.html"
+html/head_include=""
+html/canvas_resize_policy=2
+html/focus_canvas_on_start=true
+html/experimental_virtual_keyboard=false
+progressive_web_app/enabled=false
diff --git a/godot/icon.svg b/godot/icon.svg
new file mode 100644
index 0000000..bedab52
--- /dev/null
+++ b/godot/icon.svg
@@ -0,0 +1 @@
+
diff --git a/godot/icon.svg.import b/godot/icon.svg.import
new file mode 100644
index 0000000..e530e66
--- /dev/null
+++ b/godot/icon.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dk3c7aoel4i6y"
+path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icon.svg"
+dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/godot/licenses/GODOT-COPYRIGHT.txt b/godot/licenses/GODOT-COPYRIGHT.txt
new file mode 100644
index 0000000..758917a
--- /dev/null
+++ b/godot/licenses/GODOT-COPYRIGHT.txt
@@ -0,0 +1,2379 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Comment:
+ Exhaustive licensing information for files in the Godot Engine repository
+ =========================================================================
+ .
+ This file aims at documenting the copyright and license for every source
+ file in the Godot Engine repository, and especially outline the files
+ whose license differs from the MIT/Expat license used by Godot Engine.
+ .
+ It is written as a machine-readable format following the debian/copyright
+ specification. Globbing patterns (e.g. "Files: *") mean that they affect
+ all corresponding files (also recursively in subfolders), apart from those
+ with a more explicit copyright statement.
+ .
+ Licenses are given with their debian/copyright short name (or SPDX identifier
+ if no standard short name exists) and are all included in plain text at the
+ end of this file (in alphabetical order).
+ .
+ Disclaimer for thirdparty libraries:
+ ------------------------------------
+ .
+ Licensing details for thirdparty libraries in the 'thirdparty/' directory
+ are given in summarized form, i.e. with only the "main" license described
+ in the library's license statement. Different licenses of single files or
+ code snippets in thirdparty libraries are not documented here.
+ For example:
+ Files: thirdparty/zlib/*
+ Copyright: 1995-2017, Jean-loup Gailly and Mark Adler
+ License: Zlib
+ The exact copyright for each file in that library *may* differ, and some
+ files or code snippets might be distributed under other compatible licenses
+ (e.g. a public domain dedication), but as far as Godot Engine is concerned
+ the library is considered as a whole under the Zlib license.
+ .
+ Note: When linking dynamically against thirdparty libraries instead of
+ building them into the Godot binary, you may remove the corresponding
+ license details from this file.
+Upstream-Name: Godot Engine
+Upstream-Contact: Rémi Verschelde
+Source: https://github.com/godotengine/godot
+
+Files: *
+Comment: Godot Engine
+Copyright: 2014-present, Godot Engine contributors
+ 2007-2014, Juan Linietsky, Ariel Manzur
+License: Expat
+
+Files: core/math/convex_hull.cpp
+ core/math/convex_hull.h
+Comment: Bullet Continuous Collision Detection and Physics Library
+Copyright: 2011, Ole Kniemeyer, MAXON, www.maxon.net
+ 2014-present, Godot Engine contributors
+ 2007-2014, Juan Linietsky, Ariel Manzur
+License: Expat and Zlib
+
+Files: misc/dist/linux/org.godotengine.Godot.appdata.xml
+Comment: Linux AppStream Metadata File
+Copyright: 2017-2022, Rémi Verschelde
+License: CC0-1.0
+
+Files: misc/logo/*
+Comment: Godot Engine logo
+Copyright: 2017, Andrea Calabró
+License: CC-BY-4.0
+
+Files: modules/betsy/alpha_stitch.glsl
+ modules/betsy/bc1.glsl
+ modules/betsy/bc4.glsl
+ modules/betsy/bc6h.glsl
+Comment: Betsy
+Copyright: 2020-2022, Matias N. Goldberg
+License: Expat
+
+Files: modules/godot_physics_2d/godot_joints_2d.cpp
+Comment: Chipmunk2D Joint Constraints
+Copyright: 2007, Scott Lembcke
+License: Expat
+
+Files: modules/godot_physics_3d/gjk_epa.cpp
+ modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp
+ modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.h
+ modules/godot_physics_3d/joints/godot_hinge_joint_3d.cpp
+ modules/godot_physics_3d/joints/godot_hinge_joint_3d.h
+ modules/godot_physics_3d/joints/godot_jacobian_entry_3d.h
+ modules/godot_physics_3d/joints/godot_pin_joint_3d.cpp
+ modules/godot_physics_3d/joints/godot_pin_joint_3d.h
+ modules/godot_physics_3d/joints/godot_slider_joint_3d.cpp
+ modules/godot_physics_3d/joints/godot_slider_joint_3d.h
+ modules/godot_physics_3d/godot_soft_body_3d.cpp
+ modules/godot_physics_3d/godot_soft_body_3d.h
+ modules/godot_physics_3d/godot_shape_3d.cpp
+ modules/godot_physics_3d/godot_shape_3d.h
+Comment: Bullet Continuous Collision Detection and Physics Library
+Copyright: 2003-2008, Erwin Coumans
+ 2014-present, Godot Engine contributors
+ 2007-2014, Juan Linietsky, Ariel Manzur
+License: Expat and Zlib
+
+Files: modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp
+Comment: Open Dynamics Engine
+Copyright: 2001-2003, Russell L. Smith, Alen Ladavac, Nguyen Binh
+License: BSD-3-clause
+
+Files: modules/godot_physics_3d/joints/godot_cone_twist_joint_3d.cpp
+ modules/godot_physics_3d/joints/godot_cone_twist_joint_3d.h
+Comment: Bullet Continuous Collision Detection and Physics Library
+Copyright: 2007, Starbreeze Studios
+ 2014-present, Godot Engine contributors
+ 2007-2014, Juan Linietsky, Ariel Manzur
+License: Expat and Zlib
+
+Files: modules/jolt_physics/spaces/jolt_temp_allocator.cpp
+Comment: Jolt Physics
+Copyright: 2021, Jorrit Rouwe
+ 2014-present, Godot Engine contributors
+ 2007-2014, Juan Linietsky, Ariel Manzur
+License: Expat
+
+Files: modules/lightmapper_rd/lm_compute.glsl
+Comment: Joint Non-Local Means (JNLM) denoiser
+Copyright: 2020, Manuel Prandini
+ 2014-present, Godot Engine contributors
+ 2007-2014, Juan Linietsky, Ariel Manzur
+License: Expat
+
+Files: platform/android/java/editor/src/main/java/com/android/*
+ platform/android/java/lib/src/main/aidl/com/android/*
+ platform/android/java/lib/src/main/res/layout/status_bar_ongoing_event_progress_bar.xml
+ platform/android/java/lib/src/main/java/com/google/android/*
+Comment: The Android Open Source Project
+Copyright: 2008-2016, The Android Open Source Project
+ 2002, Google, Inc.
+License: Apache-2.0
+
+Files: platform/android/java/lib/src/main/java/org/godotengine/godot/utils/ProcessPhoenix.java
+Comment: ProcessPhoenix
+Copyright: 2015, Jake Wharton
+License: Apache-2.0
+
+Files: scene/animation/easing_equations.h
+Comment: Robert Penner's Easing Functions
+Copyright: 2001, Robert Penner
+ 2014-present, Godot Engine contributors
+ 2007-2014, Juan Linietsky, Ariel Manzur
+License: Expat
+
+Files: servers/audio/effects/audio_effect_pitch_shift.cpp
+Copyright: 2014-present Godot Engine contributors
+ 2007-2014 Juan Linietsky, Ariel Manzur
+ 1999-2015 Stephan M. Bernsee
+License: Expat and WOL
+
+Files: servers/rendering/renderer_rd/shaders/effects/tonemap.glsl
+Comment: NVidia's FXAA 3.11, simplified by Simon Rodriguez
+Copyright: 2014-2015, NVIDIA CORPORATION
+ 2017 Simon Rodriguez
+License: BSD-3-clause and Expat
+
+Files: servers/rendering/renderer_rd/shaders/effects/ss_effects_downsample.glsl
+ servers/rendering/renderer_rd/shaders/effects/ssao_blur.glsl
+ servers/rendering/renderer_rd/shaders/effects/ssao_importance_map.glsl
+ servers/rendering/renderer_rd/shaders/effects/ssao_interleave.glsl
+ servers/rendering/renderer_rd/shaders/effects/ssao.glsl
+ servers/rendering/renderer_rd/shaders/effects/ssil_blur.glsl
+ servers/rendering/renderer_rd/shaders/effects/ssil_importance_map.glsl
+ servers/rendering/renderer_rd/shaders/effects/ssil_interleave.glsl
+ servers/rendering/renderer_rd/shaders/effects/ssil.glsl
+Comment: Intel ASSAO and related files
+Copyright: 2016, Intel Corporation
+License: Expat
+
+Files: servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl
+Comment: Temporal Anti-Aliasing resolve implementation
+Copyright: 2016, Panos Karabelas
+License: Expat
+
+Files: servers/rendering/renderer_rd/shaders/effects/smaa_blending.glsl
+ servers/rendering/renderer_rd/shaders/effects/smaa_weight_calculation.glsl
+ servers/rendering/renderer_rd/shaders/effects/smaa_edge_detection.glsl
+ thirdparty/smaa/*
+Comment: Subpixel Morphological Antialiasing
+Copyright: 2013, Jorge Jimenez
+ 2013, Jose I. Echevarria
+ 2013, Belen Masia
+ 2013, Fernando Navarro
+ 2013, Diego Gutierrez
+License: Expat
+
+Files: thirdparty/accesskit/*
+Comment: AccessKit
+Copyright: 2023, The AccessKit Authors.
+License: Expat
+
+Files: thirdparty/amd-fsr/*
+Comment: AMD FidelityFX Super Resolution
+Copyright: 2021, Advanced Micro Devices, Inc.
+License: Expat
+
+Files: thirdparty/amd-fsr2/*
+Comment: AMD FidelityFX Super Resolution 2
+Copyright: 2022-2023, Advanced Micro Devices, Inc.
+License: Expat
+
+Files: thirdparty/angle/*
+Comment: ANGLE
+Copyright: 2018, The ANGLE Project Authors.
+License: BSD-3-clause
+
+Files: thirdparty/astcenc/*
+Comment: Arm ASTC Encoder
+Copyright: 2011-2025, Arm Limited
+License: Apache-2.0
+
+Files: thirdparty/basis_universal/*
+Comment: Basis Universal
+Copyright: 2019-2025, Binomial LLC.
+License: Apache-2.0
+
+Files: thirdparty/brotli/*
+Comment: Brotli
+Copyright: 2009, 2010, 2013-2016 by the Brotli Authors.
+License: Expat
+
+Files: thirdparty/certs/ca-bundle.crt
+Comment: CA certificates
+Copyright: Mozilla Contributors
+License: MPL-2.0
+
+Files: thirdparty/clipper2/*
+Comment: Clipper2
+Copyright: 2010-2025, Angus Johnson
+License: BSL-1.0
+
+Files: thirdparty/cvtt/*
+Comment: Convection Texture Tools Stand-Alone Kernels
+Copyright: 2018, Eric Lasota
+ 2018, Microsoft Corp.
+License: Expat
+
+Files: thirdparty/d3d12ma/*
+Comment: D3D12 Memory Allocator
+Copyright: 2019-2022 Advanced Micro Devices, Inc.
+License: Expat
+
+Files: thirdparty/directx_headers/*
+Comment: DirectX Headers
+Copyright: Microsoft Corporation
+License: Expat
+
+Files: thirdparty/doctest/*
+Comment: doctest
+Copyright: 2016-2023, Viktor Kirilov
+License: Expat
+
+Files: thirdparty/dr_libs/*
+Comment: dr_libs
+Copyright: 2020, David Reid
+License: Unlicense or MIT-0
+
+Files: thirdparty/embree/*
+Comment: Embree
+Copyright: 2009-2021 Intel Corporation
+License: Apache-2.0
+
+Files: thirdparty/enet/*
+Comment: ENet
+Copyright: 2002-2024, Lee Salzman
+License: Expat
+
+Files: thirdparty/etcpak/*
+Comment: etcpak
+Copyright: 2013-2022, Bartosz Taudul
+License: BSD-3-clause
+
+Files: thirdparty/fonts/DroidSans*.woff2
+Comment: DroidSans font
+Copyright: 2008, The Android Open Source Project
+License: Apache-2.0
+
+Files: thirdparty/fonts/Inter*.woff2
+Comment: Inter font
+Copyright: 2016, The Inter Project Authors
+License: OFL-1.1
+
+Files: thirdparty/fonts/JetBrainsMono_Regular.woff2
+Comment: JetBrains Mono font
+Copyright: 2020, JetBrains s.r.o.
+License: OFL-1.1
+
+Files: thirdparty/fonts/NotoSans*.woff2
+Comment: Noto Sans font
+Copyright: 2012, Google Inc.
+License: OFL-1.1
+
+Files: thirdparty/fonts/OpenSans*.woff2
+Comment: Open Sans font
+Copyright: 2020, The Open Sans Project Authors
+License: OFL-1.1
+
+Files: thirdparty/fonts/Vazirmatn*.woff2
+Comment: Vazirmatn font
+Copyright: 2015, The Vazirmatn Project Authors.
+License: OFL-1.1
+
+Files: thirdparty/freetype/*
+Comment: The FreeType Project
+Copyright: 1996-2025, David Turner, Robert Wilhelm, and Werner Lemberg.
+License: FTL
+
+Files: thirdparty/gamepadmotionhelpers/*
+Comment: GamepadMotionHelpers
+Copyright: 2020-2023, Julian "Jibb" Smart
+License: Expat
+
+Files: thirdparty/glad/*
+Comment: glad
+Copyright: 2013-2022, David Herberth
+ 2013-2020, The Khronos Group Inc.
+License: CC0-1.0 and Apache-2.0
+
+Files: thirdparty/glslang/*
+Comment: glslang
+Copyright: 2015-2020, Google, Inc.
+ 2014-2020, The Khronos Group Inc
+ 2002, NVIDIA Corporation.
+License: glslang
+
+Files: thirdparty/graphite/*
+Comment: Graphite engine
+Copyright: 2010, SIL International
+License: Expat
+
+Files: thirdparty/grisu2/*
+Comment: Grisu2 float serialization algorithm
+Copyright: 2009, Florian Loitsch
+ 2018-2023, The simdjson authors
+License: Expat and Apache-2.0
+
+Files: thirdparty/harfbuzz/*
+Comment: HarfBuzz text shaping library
+Copyright: 2010-2022, Google, Inc.
+ 2015-2020, Ebrahim Byagowi
+ 2019,2020, Facebook, Inc.
+ 2012, 2015, Mozilla Foundation
+ 2011, Codethink Limited
+ 2008, 2010, Nokia Corporation and/or its subsidiary(-ies)
+ 2009, Keith Stribley
+ 2011, Martin Hosken and SIL International
+ 2007, Chris Wilson
+ 2005-2006, 2020-2023, Behdad Esfahbod
+ 2004, 2007-2010, 2013, 2021-2023, Red Hat, Inc.
+ 1998-2005, David Turner and Werner Lemberg
+ 2016, Igalia, S.L.
+ 2022, Matthias Clasen
+ 2018, 2021, Khaled Hosny
+ 2018-2020, Adobe, Inc.
+ 2013-2015, Alexei Podtelezhnikov
+License: HarfBuzz
+
+Files: thirdparty/icu4c/*
+Comment: International Components for Unicode
+Copyright: 2016-2024, Unicode, Inc.
+License: Unicode
+
+Files: thirdparty/jolt_physics/*
+Comment: Jolt Physics
+Copyright: 2021, Jorrit Rouwe
+License: Expat
+
+Files: thirdparty/libbacktrace/*
+Comment: libbacktrace
+Copyright: 2012-2021, Free Software Foundation, Inc.
+License: BSD-3-clause
+
+Files: thirdparty/libjpeg-turbo/*
+Comment: libjpeg-turbo
+Copyright: 2009-2025, D. R. Commander
+ 2015, Viktor Szathmáry.
+ 1991-2020, Thomas G. Lane, Guido Vollbeding
+License: BSD-3-clause and IJG
+
+Files: thirdparty/libktx/*
+Comment: KTX
+Copyright: 2013-2020, Mark Callow
+ 2010-2020 The Khronos Group, Inc.
+License: Apache-2.0
+
+Files: thirdparty/libogg/*
+Comment: OggVorbis
+Copyright: 2002, Xiph.org Foundation
+License: BSD-3-clause
+
+Files: thirdparty/libpng/*
+Comment: libpng
+Copyright: 1995-2026, The PNG Reference Library Authors.
+ 2018-2026, Cosmin Truta.
+ 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson.
+ 1996-1997, Andreas Dilger.
+ 1995-1996, Guy Eric Schalnat, Group 42, Inc.
+License: Zlib
+
+Files: thirdparty/libtheora/*
+Comment: OggTheora
+Copyright: 2002-2009, Xiph.org Foundation
+License: BSD-3-clause
+
+Files: thirdparty/libvorbis/*
+Comment: OggVorbis
+Copyright: 2002-2015, Xiph.org Foundation
+License: BSD-3-clause
+
+Files: thirdparty/libwebp/*
+Comment: WebP codec
+Copyright: 2010, Google Inc.
+License: BSD-3-clause
+
+Files: thirdparty/manifold/*
+Comment: Manifold
+Copyright: 2020-2025, The Manifold Authors
+License: Apache-2.0
+
+Files: thirdparty/mbedtls/*
+Comment: Mbed TLS
+Copyright: The Mbed TLS Contributors
+License: Apache-2.0
+
+Files: thirdparty/meshoptimizer/*
+Comment: meshoptimizer
+Copyright: 2016-2026, Arseny Kapoulkine
+License: Expat
+
+Files: thirdparty/metal-cpp/*
+Comment: metal-cpp
+Copyright: 2024, Apple Inc.
+License: Apache-2.0
+
+Files: thirdparty/mingw-std-threads/*
+Comment: mingw-std-threads
+Copyright: 2016, Mega Limited
+License: BSD-2-clause
+
+Files: thirdparty/miniupnpc/*
+Comment: MiniUPnP Project
+Copyright: 2005-2025, Thomas Bernard
+License: BSD-3-clause
+
+Files: thirdparty/minizip/*
+Comment: MiniZip
+Copyright: 1998-2026, Gilles Vollant
+ 2007-2008, Even Rouault
+ 2009-2010, Mathias Svensson
+License: Zlib
+
+Files: thirdparty/misc/bcdec.h
+Comment: bcdec
+Copyright: 2022, Sergii Kudlai
+License: Expat
+
+Files: thirdparty/misc/cubemap_coeffs.h
+Comment: Fast Filtering of Reflection Probes
+Copyright: 2016, Activision Publishing, Inc.
+License: Expat
+
+Files: thirdparty/misc/fastlz.c
+ thirdparty/misc/fastlz.h
+Comment: FastLZ
+Copyright: 2005-2020, Ariya Hidayat
+License: Expat
+
+Files: thirdparty/misc/FastNoiseLite.h
+Comment: FastNoise Lite
+Copyright: 2023, Jordan Peck and contributors
+License: Expat
+
+Files: thirdparty/misc/ifaddrs-android.cc
+ thirdparty/misc/ifaddrs-android.h
+Comment: libjingle
+Copyright: 2012-2013, Google Inc.
+License: BSD-3-clause
+
+Files: thirdparty/misc/mikktspace.c
+ thirdparty/misc/mikktspace.h
+Comment: Tangent Space Normal Maps implementation
+Copyright: 2011, Morten S. Mikkelsen
+License: Zlib
+
+Files: thirdparty/misc/nvapi_minimal.h
+Comment: NVIDIA NVAPI (minimal excerpt)
+Copyright: 2019-2022, NVIDIA Corporation
+License: Expat
+
+Files: thirdparty/misc/ok_color.h
+ thirdparty/misc/ok_color_shader.h
+Comment: OK Lab color space
+Copyright: 2021, Björn Ottosson
+License: Expat
+
+Files: thirdparty/misc/pcg.cpp
+ thirdparty/misc/pcg.h
+Comment: Minimal PCG32 implementation
+Copyright: 2014, M.E. O'Neill
+License: Apache-2.0
+
+Files: thirdparty/misc/polypartition.cpp
+ thirdparty/misc/polypartition.h
+Comment: PolyPartition / Triangulator
+Copyright: 2011-2021, Ivan Fratric and contributors
+License: Expat
+
+Files: thirdparty/misc/qoa.h
+Comment: Quite OK Audio Format
+Copyright: 2023, Dominic Szablewski
+License: Expat
+
+Files: thirdparty/misc/r128.c
+ thirdparty/misc/r128.h
+Comment: r128 library
+Copyright: Alan Hickman
+License: Unlicense
+
+Files: thirdparty/misc/smaz.c
+ thirdparty/misc/smaz.h
+Comment: SMAZ
+Copyright: 2006-2009, Salvatore Sanfilippo
+License: BSD-3-clause
+
+Files: thirdparty/misc/smolv.cpp
+ thirdparty/misc/smolv.h
+Comment: SMOL-V
+Copyright: 2016-2024, Aras Pranckevicius
+License: Unlicense or Expat
+
+Files: thirdparty/misc/stb_rect_pack.h
+Comment: stb libraries
+Copyright: Sean Barrett
+License: Unlicense or Expat
+
+Files: thirdparty/misc/yuv2rgb.h
+Comment: YUV2RGB
+Copyright: 2008-2011, Robin Watts
+License: BSD-2-clause
+
+Files: thirdparty/msdfgen/*
+Comment: Multi-channel signed distance field generator
+Copyright: 2014-2025, Viktor Chlumsky
+License: Expat
+
+Files: thirdparty/openxr/*
+Comment: OpenXR Loader
+Copyright: 2020-2025, The Khronos Group Inc.
+License: Apache-2.0
+
+Files: thirdparty/pcre2/*
+Comment: PCRE2
+Copyright: 1997-2024, University of Cambridge
+ 2009-2024, Zoltan Herczeg
+License: BSD-3-clause
+
+Files: thirdparty/recastnavigation/*
+Comment: Recast
+Copyright: 2009, Mikko Mononen
+License: Zlib
+
+Files: thirdparty/rvo2/*
+Comment: RVO2
+Copyright: 2016, University of North Carolina at Chapel Hill
+License: Apache-2.0
+
+Files: thirdparty/sdl/*
+Comment: SDL
+Copyright: 1997-2025, Sam Lantinga
+License: Zlib
+
+Files: thirdparty/sdl/hidapi/*
+Comment: hidapi
+Copyright: 2010, Alan Ott, Signal 11 Software
+License: BSD-3-clause
+
+Files: thirdparty/spirv-cross/*
+Comment: SPIRV-Cross
+Copyright: 2015-2021, Arm Limited
+License: Apache-2.0 or Expat
+
+Files: thirdparty/spirv-headers/*
+Comment: SPIRV-Headers
+Copyright: 2015-2024, The Khronos Group Inc.
+License: Expat
+
+Files: thirdparty/spirv-reflect/*
+Comment: SPIRV-Reflect
+Copyright: 2017-2022, Google Inc.
+License: Apache-2.0
+
+Files: thirdparty/swappy-frame-pacing/*
+Comment: Swappy
+Copyright: 2019, The Android Open Source Project
+License: Apache-2.0
+
+Files: thirdparty/thorvg/*
+Comment: ThorVG
+Copyright: 2020-2026, The ThorVG Project
+License: Expat
+
+Files: thirdparty/tinyexr/*
+Comment: TinyEXR
+Copyright: 2014-2021, Syoyo Fujita
+ 2002, Industrial Light & Magic, a division of Lucas Digital Ltd. LLC
+License: BSD-3-clause
+
+Files: thirdparty/ufbx/*
+Comment: ufbx
+Copyright: 2020, Samuli Raivio
+License: Expat
+
+Files: thirdparty/vhacd/*
+Comment: V-HACD
+Copyright: 2011, Khaled Mamou
+ 2003-2009, Erwin Coumans
+License: BSD-3-clause
+
+Files: thirdparty/volk/*
+Comment: volk
+Copyright: 2018-2025, Arseny Kapoulkine
+License: Expat
+
+Files: thirdparty/vulkan/*
+Comment: Vulkan Headers
+Copyright: 2015-2025, The Khronos Group Inc.
+ 2015-2025, Valve Corporation
+ 2015-2025, LunarG, Inc.
+License: Apache-2.0
+
+Files: thirdparty/vulkan/vk_mem_alloc.h
+Comment: Vulkan Memory Allocator
+Copyright: 2017-2025, Advanced Micro Devices, Inc.
+License: Expat
+
+Files: thirdparty/wayland/*
+Comment: Wayland core protocol
+Copyright: 2008-2012, Kristian Høgsberg
+ 2010-2012, Intel Corporation
+ 2011, Benjamin Franzke
+ 2012, Collabora, Ltd.
+License: Expat
+
+Files: thirdparty/wayland-protocols/*
+Comment: Wayland protocols
+Copyright: 2008-2013, Kristian Høgsberg
+ 2010-2013, Intel Corporation
+ 2013, Rafael Antognolli
+ 2013, Jasper St. Pierre
+ 2014, Jonas Ådahl
+ 2014, Jason Ekstrand
+ 2014-2015, Collabora, Ltd.
+ 2015, Red Hat Inc.
+License: Expat
+
+Files: thirdparty/wayland-protocols/mesa/wayland-drm.xml
+Comment: Mesa Wayland protocols
+Copyright: 2008-2011, Kristian Høgsberg
+ 2010-2011, Intel Corporation
+License: X11
+
+Files: thirdparty/wslay/*
+Comment: Wslay
+Copyright: 2011, 2012, 2015, Tatsuhiro Tsujikawa
+License: Expat
+
+Files: thirdparty/xatlas/*
+Comment: xatlas
+Copyright: 2018-2020, Jonathan Young
+ 2013, Thekla, Inc
+ 2006, NVIDIA Corporation, Ignacio Castano
+License: Expat
+
+Files: thirdparty/zlib/*
+Comment: zlib
+Copyright: 1995-2026, Jean-loup Gailly and Mark Adler
+License: Zlib
+
+Files: thirdparty/zstd/*
+Comment: Zstandard
+Copyright: Meta Platforms, Inc. and affiliates.
+License: BSD-3-clause
+
+
+
+License: Apache-2.0
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+ .
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+ .
+ 1. Definitions.
+ .
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+ .
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+ .
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+ .
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+ .
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+ .
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+ .
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+ .
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+ .
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+ .
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+ .
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+ .
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+ .
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+ .
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+ .
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+ .
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+ .
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+ .
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+ .
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+ .
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+ .
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+ .
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+ .
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+ .
+ END OF TERMS AND CONDITIONS
+ .
+ APPENDIX: How to apply the Apache License to your work.
+ .
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+ .
+ Copyright [yyyy] [name of copyright owner]
+ .
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ .
+ http://www.apache.org/licenses/LICENSE-2.0
+ .
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+License: BSD-2-clause
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ .
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ .
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+License: BSD-3-clause
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ .
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ .
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ .
+ 3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+
+License: BSL-1.0
+ Boost Software License - Version 1.0 - August 17th, 2003
+ .
+ Permission is hereby granted, free of charge, to any person or organization
+ obtaining a copy of the software and accompanying documentation covered by
+ this license (the "Software") to use, reproduce, display, distribute,
+ execute, and transmit the Software, and to prepare derivative works of the
+ Software, and to permit third-parties to whom the Software is furnished to
+ do so, all subject to the following:
+ .
+ The copyright notices in the Software and this entire statement, including
+ the above license grant, this restriction and the following disclaimer,
+ must be included in all copies of the Software, in whole or in part, and
+ all derivative works of the Software, unless such copies or derivative
+ works are solely in the form of machine-executable object code generated by
+ a source language processor.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+
+License: CC0-1.0
+ CC0 1.0 Universal
+ .
+ Statement of Purpose
+ .
+ The laws of most jurisdictions throughout the world automatically confer
+ exclusive Copyright and Related Rights (defined below) upon the creator and
+ subsequent owner(s) (each and all, an "owner") of an original work of
+ authorship and/or a database (each, a "Work").
+ .
+ Certain owners wish to permanently relinquish those rights to a Work for the
+ purpose of contributing to a commons of creative, cultural and scientific
+ works ("Commons") that the public can reliably and without fear of later
+ claims of infringement build upon, modify, incorporate in other works, reuse
+ and redistribute as freely as possible in any form whatsoever and for any
+ purposes, including without limitation commercial purposes. These owners may
+ contribute to the Commons to promote the ideal of a free culture and the
+ further production of creative, cultural and scientific works, or to gain
+ reputation or greater distribution for their Work in part through the use and
+ efforts of others.
+ .
+ For these and/or other purposes and motivations, and without any expectation
+ of additional consideration or compensation, the person associating CC0 with a
+ Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
+ and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
+ and publicly distribute the Work under its terms, with knowledge of his or her
+ Copyright and Related Rights in the Work and the meaning and intended legal
+ effect of CC0 on those rights.
+ .
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
+ protected by copyright and related or neighboring rights ("Copyright and
+ Related Rights"). Copyright and Related Rights include, but are not limited
+ to, the following:
+ .
+ i. the right to reproduce, adapt, distribute, perform, display, communicate,
+ and translate a Work;
+ .
+ ii. moral rights retained by the original author(s) and/or performer(s);
+ .
+ iii. publicity and privacy rights pertaining to a person's image or likeness
+ depicted in a Work;
+ .
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ .
+ v. rights protecting the extraction, dissemination, use and reuse of data in
+ a Work;
+ .
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation thereof,
+ including any amended or successor version of such directive); and
+ .
+ vii. other similar, equivalent or corresponding rights throughout the world
+ based on applicable law or treaty, and any national implementations thereof.
+ .
+ 2. Waiver. To the greatest extent permitted by, but not in contravention of,
+ applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
+ unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
+ and Related Rights and associated claims and causes of action, whether now
+ known or unknown (including existing as well as future claims and causes of
+ action), in the Work (i) in all territories worldwide, (ii) for the maximum
+ duration provided by applicable law or treaty (including future time
+ extensions), (iii) in any current or future medium and for any number of
+ copies, and (iv) for any purpose whatsoever, including without limitation
+ commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
+ the Waiver for the benefit of each member of the public at large and to the
+ detriment of Affirmer's heirs and successors, fully intending that such Waiver
+ shall not be subject to revocation, rescission, cancellation, termination, or
+ any other legal or equitable action to disrupt the quiet enjoyment of the Work
+ by the public as contemplated by Affirmer's express Statement of Purpose.
+ .
+ 3. Public License Fallback. Should any part of the Waiver for any reason be
+ judged legally invalid or ineffective under applicable law, then the Waiver
+ shall be preserved to the maximum extent permitted taking into account
+ Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
+ is so judged Affirmer hereby grants to each affected person a royalty-free,
+ non transferable, non sublicensable, non exclusive, irrevocable and
+ unconditional license to exercise Affirmer's Copyright and Related Rights in
+ the Work (i) in all territories worldwide, (ii) for the maximum duration
+ provided by applicable law or treaty (including future time extensions), (iii)
+ in any current or future medium and for any number of copies, and (iv) for any
+ purpose whatsoever, including without limitation commercial, advertising or
+ promotional purposes (the "License"). The License shall be deemed effective as
+ of the date CC0 was applied by Affirmer to the Work. Should any part of the
+ License for any reason be judged legally invalid or ineffective under
+ applicable law, such partial invalidity or ineffectiveness shall not
+ invalidate the remainder of the License, and in such case Affirmer hereby
+ affirms that he or she will not (i) exercise any of his or her remaining
+ Copyright and Related Rights in the Work or (ii) assert any associated claims
+ and causes of action with respect to the Work, in either case contrary to
+ Affirmer's express Statement of Purpose.
+ .
+ 4. Limitations and Disclaimers.
+ .
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ .
+ b. Affirmer offers the Work as-is and makes no representations or warranties
+ of any kind concerning the Work, express, implied, statutory or otherwise,
+ including without limitation warranties of title, merchantability, fitness
+ for a particular purpose, non infringement, or the absence of latent or
+ other defects, accuracy, or the present or absence of errors, whether or not
+ discoverable, all to the greatest extent permissible under applicable law.
+ .
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without limitation
+ any person's Copyright and Related Rights in the Work. Further, Affirmer
+ disclaims responsibility for obtaining any necessary consents, permissions
+ or other rights required for any use of the Work.
+ .
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to this
+ CC0 or use of the Work.
+
+License: CC-BY-4.0
+ Creative Commons Attribution 4.0 International Public License
+ .
+ By exercising the Licensed Rights (defined below), You accept and agree
+ to be bound by the terms and conditions of this Creative Commons
+ Attribution 4.0 International Public License ("Public
+ License"). To the extent this Public License may be interpreted as a
+ contract, You are granted the Licensed Rights in consideration of Your
+ acceptance of these terms and conditions, and the Licensor grants You
+ such rights in consideration of benefits the Licensor receives from
+ making the Licensed Material available under these terms and
+ conditions.
+ .
+ Section 1 -- Definitions.
+ .
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+ .
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+ .
+ c. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+ .
+ d. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+ .
+ e. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+ .
+ f. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+ .
+ g. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+ .
+ h. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+ .
+ i. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+ .
+ j. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+ .
+ k. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+ .
+ Section 2 -- Scope.
+ .
+ a. License grant.
+ .
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+ .
+ a. reproduce and Share the Licensed Material, in whole or
+ in part; and
+ .
+ b. produce, reproduce, and Share Adapted Material.
+ .
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+ .
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+ .
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+ .
+ 5. Downstream recipients.
+ .
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+ .
+ b. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+ .
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+ .
+ b. Other rights.
+ .
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+ .
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+ .
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties.
+ .
+ Section 3 -- License Conditions.
+ .
+ Your exercise of the Licensed Rights is expressly made subject to the
+ following conditions.
+ .
+ a. Attribution.
+ .
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+ .
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+ .
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+ .
+ ii. a copyright notice;
+ .
+ iii. a notice that refers to this Public License;
+ .
+ iv. a notice that refers to the disclaimer of
+ warranties;
+ .
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+ .
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+ .
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+ .
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+ .
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+ .
+ 4. If You Share Adapted Material You produce, the Adapter's
+ License You apply must not prevent recipients of the Adapted
+ Material from complying with this Public License.
+ .
+ Section 4 -- Sui Generis Database Rights.
+ .
+ Where the Licensed Rights include Sui Generis Database Rights that
+ apply to Your use of the Licensed Material:
+ .
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database;
+ .
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material; and
+ .
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+ .
+ For the avoidance of doubt, this Section 4 supplements and does not
+ replace Your obligations under this Public License where the Licensed
+ Rights include other Copyright and Similar Rights.
+ .
+ Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+ .
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+ .
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+ .
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+ .
+ Section 6 -- Term and Termination.
+ .
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+ .
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+ .
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+ .
+ 2. upon express reinstatement by the Licensor.
+ .
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+ .
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+ .
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+ .
+ Section 7 -- Other Terms and Conditions.
+ .
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+ .
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+ .
+ Section 8 -- Interpretation.
+ .
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+ .
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+ .
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+ .
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+License: Expat
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+ .
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+License: glslang
+ Here, glslang proper means core GLSL parsing, HLSL parsing, and SPIR-V code
+ generation. Glslang proper requires use of a number of licenses, one that covers
+ preprocessing and others that covers non-preprocessing.
+ .
+ Bison was removed long ago. You can build glslang from the source grammar,
+ using tools of your choice, without using bison or any bison files.
+ .
+ Other parts, outside of glslang proper, include:
+ .
+ - gl_types.h, only needed for OpenGL-like reflection, and can be left out of
+ a parse and codegen project. See it for its license.
+ .
+ - update_glslang_sources.py, which is not part of the project proper and does
+ not need to be used.
+ .
+ - the SPIR-V "remapper", which is optional, but has the same license as
+ glslang proper
+ .
+ - Google tests and SPIR-V tools, and anything in the external subdirectory
+ are external and optional; see them for their respective licenses.
+ .
+ --------------------------------------------------------------------------------
+ .
+ The core of glslang-proper, minus the preprocessor is licenced as follows:
+ .
+ --------------------------------------------------------------------------------
+ 3-Clause BSD License
+ --------------------------------------------------------------------------------
+ .
+ Copyright (C) 2015-2018 Google, Inc.
+ Copyright (C)
+ .
+ All rights reserved.
+ .
+ See: .
+ .
+ --------------------------------------------------------------------------------
+ 2-Clause BSD License
+ --------------------------------------------------------------------------------
+ .
+ Copyright 2020 The Khronos Group Inc
+ .
+ See: .
+ .
+ --------------------------------------------------------------------------------
+ The MIT License
+ --------------------------------------------------------------------------------
+ .
+ Copyright 2020 The Khronos Group Inc
+ .
+ See: .
+ .
+ --------------------------------------------------------------------------------
+ APACHE LICENSE, VERSION 2.0
+ --------------------------------------------------------------------------------
+ .
+ See: .
+ .
+ --------------------------------------------------------------------------------
+ GPL 3 with special bison exception
+ --------------------------------------------------------------------------------
+ .
+ Bison implementation for Yacc-like parsers in C
+ .
+ Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc.
+ .
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ .
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+ .
+ As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+ .
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison.
+ .
+ --------------------------------------------------------------------------------
+ ================================================================================
+ --------------------------------------------------------------------------------
+ .
+ The preprocessor has the core licenses stated above, plus an additional licence:
+ .
+ Copyright (c) 2002, NVIDIA Corporation.
+ .
+ NVIDIA Corporation("NVIDIA") supplies this software to you in
+ consideration of your agreement to the following terms, and your use,
+ installation, modification or redistribution of this NVIDIA software
+ constitutes acceptance of these terms. If you do not agree with these
+ terms, please do not use, install, modify or redistribute this NVIDIA
+ software.
+ .
+ In consideration of your agreement to abide by the following terms, and
+ subject to these terms, NVIDIA grants you a personal, non-exclusive
+ license, under NVIDIA's copyrights in this original NVIDIA software (the
+ "NVIDIA Software"), to use, reproduce, modify and redistribute the
+ NVIDIA Software, with or without modifications, in source and/or binary
+ forms; provided that if you redistribute the NVIDIA Software, you must
+ retain the copyright notice of NVIDIA, this notice and the following
+ text and disclaimers in all such redistributions of the NVIDIA Software.
+ Neither the name, trademarks, service marks nor logos of NVIDIA
+ Corporation may be used to endorse or promote products derived from the
+ NVIDIA Software without specific prior written permission from NVIDIA.
+ Except as expressly stated in this notice, no other rights or licenses
+ express or implied, are granted by NVIDIA herein, including but not
+ limited to any patent rights that may be infringed by your derivative
+ works or by other works in which the NVIDIA Software may be
+ incorporated. No hardware is licensed hereunder.
+ .
+ THE NVIDIA SOFTWARE IS BEING PROVIDED ON AN "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED,
+ INCLUDING WITHOUT LIMITATION, WARRANTIES OR CONDITIONS OF TITLE,
+ NON-INFRINGEMENT, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+ ITS USE AND OPERATION EITHER ALONE OR IN COMBINATION WITH OTHER
+ PRODUCTS.
+ .
+ IN NO EVENT SHALL NVIDIA BE LIABLE FOR ANY SPECIAL, INDIRECT,
+ INCIDENTAL, EXEMPLARY, CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ TO, LOST PROFITS; PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) OR ARISING IN ANY WAY
+ OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE
+ NVIDIA SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT,
+ TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF
+ NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+License: FTL
+ The FreeType Project LICENSE
+ ----------------------------
+ .
+ 2006-Jan-27
+ .
+ Copyright 1996-2002, 2006 by
+ David Turner, Robert Wilhelm, and Werner Lemberg
+ .
+ .
+ .
+ Introduction
+ ============
+ .
+ The FreeType Project is distributed in several archive packages;
+ some of them may contain, in addition to the FreeType font engine,
+ various tools and contributions which rely on, or relate to, the
+ FreeType Project.
+ .
+ This license applies to all files found in such packages, and
+ which do not fall under their own explicit license. The license
+ affects thus the FreeType font engine, the test programs,
+ documentation and makefiles, at the very least.
+ .
+ This license was inspired by the BSD, Artistic, and IJG
+ (Independent JPEG Group) licenses, which all encourage inclusion
+ and use of free software in commercial and freeware products
+ alike. As a consequence, its main points are that:
+ .
+ o We don't promise that this software works. However, we will be
+ interested in any kind of bug reports. (`as is' distribution)
+ .
+ o You can use this software for whatever you want, in parts or
+ full form, without having to pay us. (`royalty-free' usage)
+ .
+ o You may not pretend that you wrote this software. If you use
+ it, or only parts of it, in a program, you must acknowledge
+ somewhere in your documentation that you have used the
+ FreeType code. (`credits')
+ .
+ We specifically permit and encourage the inclusion of this
+ software, with or without modifications, in commercial products.
+ We disclaim all warranties covering The FreeType Project and
+ assume no liability related to The FreeType Project.
+ .
+ .
+ Finally, many people asked us for a preferred form for a
+ credit/disclaimer to use in compliance with this license. We thus
+ encourage you to use the following text:
+ .
+ """
+ Portions of this software are copyright © The FreeType
+ Project (https://freetype.org). All rights reserved.
+ """
+ .
+ Please replace with the value from the FreeType version you
+ actually use.
+ .
+ .
+ Legal Terms
+ ===========
+ .
+ 0. Definitions
+ --------------
+ .
+ Throughout this license, the terms `package', `FreeType Project',
+ and `FreeType archive' refer to the set of files originally
+ distributed by the authors (David Turner, Robert Wilhelm, and
+ Werner Lemberg) as the `FreeType Project', be they named as alpha,
+ beta or final release.
+ .
+ `You' refers to the licensee, or person using the project, where
+ `using' is a generic term including compiling the project's source
+ code as well as linking it to form a `program' or `executable'.
+ This program is referred to as `a program using the FreeType
+ engine'.
+ .
+ This license applies to all files distributed in the original
+ FreeType Project, including all source code, binaries and
+ documentation, unless otherwise stated in the file in its
+ original, unmodified form as distributed in the original archive.
+ If you are unsure whether or not a particular file is covered by
+ this license, you must contact us to verify this.
+ .
+ The FreeType Project is copyright (C) 1996-2000 by David Turner,
+ Robert Wilhelm, and Werner Lemberg. All rights reserved except as
+ specified below.
+ .
+ 1. No Warranty
+ --------------
+ .
+ THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO
+ USE, OF THE FREETYPE PROJECT.
+ .
+ 2. Redistribution
+ -----------------
+ .
+ This license grants a worldwide, royalty-free, perpetual and
+ irrevocable right and license to use, execute, perform, compile,
+ display, copy, create derivative works of, distribute and
+ sublicense the FreeType Project (in both source and object code
+ forms) and derivative works thereof for any purpose; and to
+ authorize others to exercise some or all of the rights granted
+ herein, subject to the following conditions:
+ .
+ o Redistribution of source code must retain this license file
+ (`FTL.TXT') unaltered; any additions, deletions or changes to
+ the original files must be clearly indicated in accompanying
+ documentation. The copyright notices of the unaltered,
+ original files must be preserved in all copies of source
+ files.
+ .
+ o Redistribution in binary form must provide a disclaimer that
+ states that the software is based in part of the work of the
+ FreeType Team, in the distribution documentation. We also
+ encourage you to put an URL to the FreeType web page in your
+ documentation, though this isn't mandatory.
+ .
+ These conditions apply to any software derived from or based on
+ the FreeType Project, not just the unmodified files. If you use
+ our work, you must acknowledge us. However, no fee need be paid
+ to us.
+ .
+ 3. Advertising
+ --------------
+ .
+ Neither the FreeType authors and contributors nor you shall use
+ the name of the other for commercial, advertising, or promotional
+ purposes without specific prior written permission.
+ .
+ We suggest, but do not require, that you use one or more of the
+ following phrases to refer to this software in your documentation
+ or advertising materials: `FreeType Project', `FreeType Engine',
+ `FreeType library', or `FreeType Distribution'.
+ .
+ As you have not signed this license, you are not required to
+ accept it. However, as the FreeType Project is copyrighted
+ material, only this license, or another one contracted with the
+ authors, grants you the right to use, distribute, and modify it.
+ Therefore, by using, distributing, or modifying the FreeType
+ Project, you indicate that you understand and accept all the terms
+ of this license.
+ .
+ 4. Contacts
+ -----------
+ .
+ There are two mailing lists related to FreeType:
+ .
+ o freetype@nongnu.org
+ .
+ Discusses general use and applications of FreeType, as well as
+ future and wanted additions to the library and distribution.
+ If you are looking for support, start in this list if you
+ haven't found anything to help you in the documentation.
+ .
+ o freetype-devel@nongnu.org
+ .
+ Discusses bugs, as well as engine internals, design issues,
+ specific licenses, porting, etc.
+ .
+ Our home page can be found at
+ .
+ https://freetype.org
+
+License: HarfBuzz
+ HarfBuzz is licensed under the so-called "Old MIT" license. Details follow.
+ For parts of HarfBuzz that are licensed under different licenses see individual
+ files names COPYING in subdirectories where applicable.
+ .
+ Copyright (C) 2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020 Google, Inc.
+ Copyright (C) 2018,2019,2020 Ebrahim Byagowi
+ Copyright (C) 2019,2020 Facebook, Inc.
+ Copyright (C) 2012 Mozilla Foundation
+ Copyright (C) 2011 Codethink Limited
+ Copyright (C) 2008,2010 Nokia Corporation and/or its subsidiary(-ies)
+ Copyright (C) 2009 Keith Stribley
+ Copyright (C) 2009 Martin Hosken and SIL International
+ Copyright (C) 2007 Chris Wilson
+ Copyright (C) 2005,2006,2020,2021 Behdad Esfahbod
+ Copyright (C) 2005 David Turner
+ Copyright (C) 2004,2007,2008,2009,2010 Red Hat, Inc.
+ Copyright (C) 1998-2004 David Turner and Werner Lemberg
+ .
+ For full copyright notices consult the individual files in the package.
+ .
+ .
+ Permission is hereby granted, without written agreement and without
+ license or royalty fees, to use, copy, modify, and distribute this
+ software and its documentation for any purpose, provided that the
+ above copyright notice and the following two paragraphs appear in
+ all copies of this software.
+ .
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ DAMAGE.
+ .
+ THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
+ ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+
+License: IJG
+ The authors make NO WARRANTY or representation, either express or implied,
+ with respect to this software, its quality, accuracy, merchantability, or
+ fitness for a particular purpose. This software is provided "AS IS", and you,
+ its user, assume the entire risk as to its quality and accuracy.
+ .
+ This software is copyright (C) 1991-2020, Thomas G. Lane, Guido Vollbeding.
+ All Rights Reserved except as specified below.
+ .
+ Permission is hereby granted to use, copy, modify, and distribute this
+ software (or portions thereof) for any purpose, without fee, subject to these
+ conditions:
+ (1) If any part of the source code for this software is distributed, then this
+ README file must be included, with this copyright and no-warranty notice
+ unaltered; and any additions, deletions, or changes to the original files
+ must be clearly indicated in accompanying documentation.
+ (2) If only executable code is distributed, then the accompanying
+ documentation must state that "this software is based in part on the work of
+ the Independent JPEG Group".
+ (3) Permission for use of this software is granted only if the user accepts
+ full responsibility for any undesirable consequences; the authors accept
+ NO LIABILITY for damages of any kind.
+ .
+ These conditions apply to any software derived from or based on the IJG code,
+ not just to the unmodified library. If you use our work, you ought to
+ acknowledge us.
+ .
+ Permission is NOT granted for the use of any IJG author's name or company name
+ in advertising or publicity relating to this software or products derived from
+ it. This software may be referred to only as "the Independent JPEG Group's
+ software".
+ .
+ We specifically permit and encourage the use of this software as the basis of
+ commercial products, provided that all warranty or liability claims are
+ assumed by the product vendor.
+
+License: MPL-2.0
+ Mozilla Public License Version 2.0
+ ==================================
+ .
+ 1. Definitions
+ --------------
+ .
+ 1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+ .
+ 1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+ .
+ 1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+ .
+ 1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+ .
+ 1.5. "Incompatible With Secondary Licenses"
+ means
+ .
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+ .
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+ .
+ 1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+ .
+ 1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+ .
+ 1.8. "License"
+ means this document.
+ .
+ 1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+ .
+ 1.10. "Modifications"
+ means any of the following:
+ .
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+ .
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+ .
+ 1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+ .
+ 1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+ .
+ 1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+ .
+ 1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+ .
+ 2. License Grants and Conditions
+ --------------------------------
+ .
+ 2.1. Grants
+ .
+ Each Contributor hereby grants You a world-wide, royalty-free,
+ non-exclusive license:
+ .
+ (a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+ .
+ (b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+ .
+ 2.2. Effective Date
+ .
+ The licenses granted in Section 2.1 with respect to any Contribution
+ become effective for each Contribution on the date the Contributor first
+ distributes such Contribution.
+ .
+ 2.3. Limitations on Grant Scope
+ .
+ The licenses granted in this Section 2 are the only rights granted under
+ this License. No additional rights or licenses will be implied from the
+ distribution or licensing of Covered Software under this License.
+ Notwithstanding Section 2.1(b) above, no patent license is granted by a
+ Contributor:
+ .
+ (a) for any code that a Contributor has removed from Covered Software;
+ or
+ .
+ (b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+ .
+ (c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+ .
+ This License does not grant any rights in the trademarks, service marks,
+ or logos of any Contributor (except as may be necessary to comply with
+ the notice requirements in Section 3.4).
+ .
+ 2.4. Subsequent Licenses
+ .
+ No Contributor makes additional grants as a result of Your choice to
+ distribute the Covered Software under a subsequent version of this
+ License (see Section 10.2) or under the terms of a Secondary License (if
+ permitted under the terms of Section 3.3).
+ .
+ 2.5. Representation
+ .
+ Each Contributor represents that the Contributor believes its
+ Contributions are its original creation(s) or it has sufficient rights
+ to grant the rights to its Contributions conveyed by this License.
+ .
+ 2.6. Fair Use
+ .
+ This License is not intended to limit any rights You have under
+ applicable copyright doctrines of fair use, fair dealing, or other
+ equivalents.
+ .
+ 2.7. Conditions
+ .
+ Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+ in Section 2.1.
+ .
+ 3. Responsibilities
+ -------------------
+ .
+ 3.1. Distribution of Source Form
+ .
+ All distribution of Covered Software in Source Code Form, including any
+ Modifications that You create or to which You contribute, must be under
+ the terms of this License. You must inform recipients that the Source
+ Code Form of the Covered Software is governed by the terms of this
+ License, and how they can obtain a copy of this License. You may not
+ attempt to alter or restrict the recipients' rights in the Source Code
+ Form.
+ .
+ 3.2. Distribution of Executable Form
+ .
+ If You distribute Covered Software in Executable Form then:
+ .
+ (a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+ .
+ (b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+ .
+ 3.3. Distribution of a Larger Work
+ .
+ You may create and distribute a Larger Work under terms of Your choice,
+ provided that You also comply with the requirements of this License for
+ the Covered Software. If the Larger Work is a combination of Covered
+ Software with a work governed by one or more Secondary Licenses, and the
+ Covered Software is not Incompatible With Secondary Licenses, this
+ License permits You to additionally distribute such Covered Software
+ under the terms of such Secondary License(s), so that the recipient of
+ the Larger Work may, at their option, further distribute the Covered
+ Software under the terms of either this License or such Secondary
+ License(s).
+ .
+ 3.4. Notices
+ .
+ You may not remove or alter the substance of any license notices
+ (including copyright notices, patent notices, disclaimers of warranty,
+ or limitations of liability) contained within the Source Code Form of
+ the Covered Software, except that You may alter any license notices to
+ the extent required to remedy known factual inaccuracies.
+ .
+ 3.5. Application of Additional Terms
+ .
+ You may choose to offer, and to charge a fee for, warranty, support,
+ indemnity or liability obligations to one or more recipients of Covered
+ Software. However, You may do so only on Your own behalf, and not on
+ behalf of any Contributor. You must make it absolutely clear that any
+ such warranty, support, indemnity, or liability obligation is offered by
+ You alone, and You hereby agree to indemnify every Contributor for any
+ liability incurred by such Contributor as a result of warranty, support,
+ indemnity or liability terms You offer. You may include additional
+ disclaimers of warranty and limitations of liability specific to any
+ jurisdiction.
+ .
+ 4. Inability to Comply Due to Statute or Regulation
+ ---------------------------------------------------
+ .
+ If it is impossible for You to comply with any of the terms of this
+ License with respect to some or all of the Covered Software due to
+ statute, judicial order, or regulation then You must: (a) comply with
+ the terms of this License to the maximum extent possible; and (b)
+ describe the limitations and the code they affect. Such description must
+ be placed in a text file included with all distributions of the Covered
+ Software under this License. Except to the extent prohibited by statute
+ or regulation, such description must be sufficiently detailed for a
+ recipient of ordinary skill to be able to understand it.
+ .
+ 5. Termination
+ --------------
+ .
+ 5.1. The rights granted under this License will terminate automatically
+ if You fail to comply with any of its terms. However, if You become
+ compliant, then the rights granted under this License from a particular
+ Contributor are reinstated (a) provisionally, unless and until such
+ Contributor explicitly and finally terminates Your grants, and (b) on an
+ ongoing basis, if such Contributor fails to notify You of the
+ non-compliance by some reasonable means prior to 60 days after You have
+ come back into compliance. Moreover, Your grants from a particular
+ Contributor are reinstated on an ongoing basis if such Contributor
+ notifies You of the non-compliance by some reasonable means, this is the
+ first time You have received notice of non-compliance with this License
+ from such Contributor, and You become compliant prior to 30 days after
+ Your receipt of the notice.
+ .
+ 5.2. If You initiate litigation against any entity by asserting a patent
+ infringement claim (excluding declaratory judgment actions,
+ counter-claims, and cross-claims) alleging that a Contributor Version
+ directly or indirectly infringes any patent, then the rights granted to
+ You by any and all Contributors for the Covered Software under Section
+ 2.1 of this License shall terminate.
+ .
+ 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+ end user license agreements (excluding distributors and resellers) which
+ have been validly granted by You or Your distributors under this License
+ prior to termination shall survive termination.
+ .
+ ************************************************************************
+ * *
+ * 6. Disclaimer of Warranty *
+ * ------------------------- *
+ * *
+ * Covered Software is provided under this License on an "as is" *
+ * basis, without warranty of any kind, either expressed, implied, or *
+ * statutory, including, without limitation, warranties that the *
+ * Covered Software is free of defects, merchantable, fit for a *
+ * particular purpose or non-infringing. The entire risk as to the *
+ * quality and performance of the Covered Software is with You. *
+ * Should any Covered Software prove defective in any respect, You *
+ * (not any Contributor) assume the cost of any necessary servicing, *
+ * repair, or correction. This disclaimer of warranty constitutes an *
+ * essential part of this License. No use of any Covered Software is *
+ * authorized under this License except under this disclaimer. *
+ * *
+ ************************************************************************
+ .
+ ************************************************************************
+ * *
+ * 7. Limitation of Liability *
+ * -------------------------- *
+ * *
+ * Under no circumstances and under no legal theory, whether tort *
+ * (including negligence), contract, or otherwise, shall any *
+ * Contributor, or anyone who distributes Covered Software as *
+ * permitted above, be liable to You for any direct, indirect, *
+ * special, incidental, or consequential damages of any character *
+ * including, without limitation, damages for lost profits, loss of *
+ * goodwill, work stoppage, computer failure or malfunction, or any *
+ * and all other commercial damages or losses, even if such party *
+ * shall have been informed of the possibility of such damages. This *
+ * limitation of liability shall not apply to liability for death or *
+ * personal injury resulting from such party's negligence to the *
+ * extent applicable law prohibits such limitation. Some *
+ * jurisdictions do not allow the exclusion or limitation of *
+ * incidental or consequential damages, so this exclusion and *
+ * limitation may not apply to You. *
+ * *
+ ************************************************************************
+ .
+ 8. Litigation
+ -------------
+ .
+ Any litigation relating to this License may be brought only in the
+ courts of a jurisdiction where the defendant maintains its principal
+ place of business and such litigation shall be governed by laws of that
+ jurisdiction, without reference to its conflict-of-law provisions.
+ Nothing in this Section shall prevent a party's ability to bring
+ cross-claims or counter-claims.
+ .
+ 9. Miscellaneous
+ ----------------
+ .
+ This License represents the complete agreement concerning the subject
+ matter hereof. If any provision of this License is held to be
+ unenforceable, such provision shall be reformed only to the extent
+ necessary to make it enforceable. Any law or regulation which provides
+ that the language of a contract shall be construed against the drafter
+ shall not be used to construe this License against a Contributor.
+ .
+ 10. Versions of the License
+ ---------------------------
+ .
+ 10.1. New Versions
+ .
+ Mozilla Foundation is the license steward. Except as provided in Section
+ 10.3, no one other than the license steward has the right to modify or
+ publish new versions of this License. Each version will be given a
+ distinguishing version number.
+ .
+ 10.2. Effect of New Versions
+ .
+ You may distribute the Covered Software under the terms of the version
+ of the License under which You originally received the Covered Software,
+ or under the terms of any subsequent version published by the license
+ steward.
+ .
+ 10.3. Modified Versions
+ .
+ If you create software not governed by this License, and you want to
+ create a new license for such software, you may create and use a
+ modified version of this License if you rename the license and remove
+ any references to the name of the license steward (except to note that
+ such modified license differs from this License).
+ .
+ 10.4. Distributing Source Code Form that is Incompatible With Secondary
+ Licenses
+ .
+ If You choose to distribute Source Code Form that is Incompatible With
+ Secondary Licenses under the terms of this version of the License, the
+ notice described in Exhibit B of this License must be attached.
+ .
+ Exhibit A - Source Code Form License Notice
+ -------------------------------------------
+ .
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ .
+ If it is not possible or desirable to put the notice in a particular
+ file, then You may include the notice in a location (such as a LICENSE
+ file in a relevant directory) where a recipient would be likely to look
+ for such a notice.
+ .
+ You may add additional accurate notices of copyright ownership.
+ .
+ Exhibit B - "Incompatible With Secondary Licenses" Notice
+ ---------------------------------------------------------
+ .
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
+
+License: MIT-0
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+License: OFL-1.1
+ PREAMBLE
+ The goals of the Open Font License (OFL) are to stimulate worldwide
+ development of collaborative font projects, to support the font creation
+ efforts of academic and linguistic communities, and to provide a free and
+ open framework in which fonts may be shared and improved in partnership
+ with others.
+ .
+ The OFL allows the licensed fonts to be used, studied, modified and
+ redistributed freely as long as they are not sold by themselves. The
+ fonts, including any derivative works, can be bundled, embedded,
+ redistributed and/or sold with any software provided that any reserved
+ names are not used by derivative works. The fonts and derivatives,
+ however, cannot be released under any other type of license. The
+ requirement for fonts to remain under this license does not apply
+ to any document created using the fonts or their derivatives.
+ .
+ DEFINITIONS
+ "Font Software" refers to the set of files released by the Copyright
+ Holder(s) under this license and clearly marked as such. This may
+ include source files, build scripts and documentation.
+ .
+ "Reserved Font Name" refers to any names specified as such after the
+ copyright statement(s).
+ .
+ "Original Version" refers to the collection of Font Software components as
+ distributed by the Copyright Holder(s).
+ .
+ "Modified Version" refers to any derivative made by adding to, deleting,
+ or substituting -- in part or in whole -- any of the components of the
+ Original Version, by changing formats or by porting the Font Software to a
+ new environment.
+ .
+ "Author" refers to any designer, engineer, programmer, technical
+ writer or other person who contributed to the Font Software.
+ .
+ PERMISSION & CONDITIONS
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
+ redistribute, and sell modified and unmodified copies of the Font
+ Software, subject to the following conditions:
+ .
+ 1) Neither the Font Software nor any of its individual components,
+ in Original or Modified Versions, may be sold by itself.
+ .
+ 2) Original or Modified Versions of the Font Software may be bundled,
+ redistributed and/or sold with any software, provided that each copy
+ contains the above copyright notice and this license. These can be
+ included either as stand-alone text files, human-readable headers or
+ in the appropriate machine-readable metadata fields within text or
+ binary files as long as those fields can be easily viewed by the user.
+ .
+ 3) No Modified Version of the Font Software may use the Reserved Font
+ Name(s) unless explicit written permission is granted by the corresponding
+ Copyright Holder. This restriction only applies to the primary font name as
+ presented to the users.
+ .
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+ Software shall not be used to promote, endorse or advertise any
+ Modified Version, except to acknowledge the contribution(s) of the
+ Copyright Holder(s) and the Author(s) or with their explicit written
+ permission.
+ .
+ 5) The Font Software, modified or unmodified, in part or in whole,
+ must be distributed entirely under this license, and must not be
+ distributed under any other license. The requirement for fonts to
+ remain under this license does not apply to any document created
+ using the Font Software.
+ .
+ TERMINATION
+ This license becomes null and void if any of the above conditions are
+ not met.
+ .
+ DISCLAIMER
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE.
+
+License: Unicode
+ COPYRIGHT AND PERMISSION NOTICE (ICU 58 and later)
+ .
+ Copyright (C) 1991-2020 Unicode, Inc. All rights reserved.
+ Distributed under the Terms of Use in https://www.unicode.org/copyright.html.
+ .
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of the Unicode data files and any associated documentation
+ (the "Data Files") or Unicode software and any associated documentation
+ (the "Software") to deal in the Data Files or Software
+ without restriction, including without limitation the rights to use,
+ copy, modify, merge, publish, distribute, and/or sell copies of
+ the Data Files or Software, and to permit persons to whom the Data Files
+ or Software are furnished to do so, provided that either
+ (a) this copyright and permission notice appear with all copies
+ of the Data Files or Software, or
+ (b) this copyright and permission notice appear in associated
+ Documentation.
+ .
+ THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF
+ ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS
+ NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
+ DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ PERFORMANCE OF THE DATA FILES OR SOFTWARE.
+ .
+ Except as contained in this notice, the name of a copyright holder
+ shall not be used in advertising or otherwise to promote the sale,
+ use or other dealings in these Data Files or Software without prior
+ written authorization of the copyright holder.
+
+License: Unlicense
+ This is free and unencumbered software released into the public domain.
+ .
+ Anyone is free to copy, modify, publish, use, compile, sell, or
+ distribute this software, either in source code form or as a compiled
+ binary, for any purpose, commercial or non-commercial, and by any
+ means.
+ .
+ In jurisdictions that recognize copyright laws, the author or authors
+ of this software dedicate any and all copyright interest in the
+ software to the public domain. We make this dedication for the benefit
+ of the public at large and to the detriment of our heirs and
+ successors. We intend this dedication to be an overt act of
+ relinquishment in perpetuity of all present and future rights to this
+ software under copyright law.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+ .
+ For more information, please refer to
+
+License: WOL
+ The Wide Open License (WOL)
+ .
+ Permission to use, copy, modify, distribute and sell this software and its
+ documentation for any purpose is hereby granted without fee, provided that
+ the above copyright notice and this license appear in all source copies.
+ THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF
+ ANY KIND. See https://dspguru.com/wide-open-license/ for more information.
+
+License: X11
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that\n the above copyright notice appear in
+ all copies and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+ .
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+
+License: Zlib
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+ .
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+ .
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
diff --git a/godot/licenses/GODOT-LICENSE.txt b/godot/licenses/GODOT-LICENSE.txt
new file mode 100644
index 0000000..0e3ba08
--- /dev/null
+++ b/godot/licenses/GODOT-LICENSE.txt
@@ -0,0 +1,20 @@
+Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md).
+Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/godot/project.godot b/godot/project.godot
new file mode 100644
index 0000000..9ae4fff
--- /dev/null
+++ b/godot/project.godot
@@ -0,0 +1,36 @@
+; Universal AI: The Seed — a browser-first Godot prototype.
+config_version=5
+
+[application]
+config/name="Universal AI — The Seed"
+run/main_scene="res://scenes/factory.tscn"
+config/features=PackedStringArray("4.7", "GL Compatibility")
+config/description="First, a chip. Then, everything."
+config/icon="res://icon.svg"
+
+[display]
+window/size/viewport_width=1440
+window/size/viewport_height=900
+window/size/window_width_override=1440
+window/size/window_height_override=900
+window/stretch/mode="disabled"
+window/stretch/aspect="expand"
+window/handheld/orientation=0
+
+[rendering]
+renderer/rendering_method="gl_compatibility"
+renderer/rendering_method.mobile="gl_compatibility"
+textures/default_filters/use_nearest_mipmap_filter=false
+textures/default_filters/anisotropic_filtering_level=2
+anti_aliasing/quality/msaa_3d=0
+environment/defaults/default_clear_color=Color(0.025, 0.04, 0.065, 1)
+lights_and_shadows/directional_shadow/size=1024
+
+[audio]
+driver/driver.web="Web"
+
+[gui]
+theme/default_font_multichannel_signed_distance_field=true
+
+[debug]
+gdscript/warnings/untyped_declaration=0
diff --git a/godot/scenes/factory.tscn b/godot/scenes/factory.tscn
new file mode 100644
index 0000000..cb85100
--- /dev/null
+++ b/godot/scenes/factory.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3]
+
+[ext_resource type="Script" path="res://scripts/factory.gd" id="1"]
+
+[node name="TheSeed" type="Node3D"]
+script = ExtResource("1")
diff --git a/godot/scripts/chamber.gd b/godot/scripts/chamber.gd
new file mode 100644
index 0000000..6896a5d
--- /dev/null
+++ b/godot/scripts/chamber.gd
@@ -0,0 +1,159 @@
+class_name FactoryChamber
+extends Node3D
+
+const Geo = preload("res://scripts/geometry.gd")
+const BAY_POSITIONS: Array[Vector3] = [Vector3(-4.4,0,-4.5),Vector3(4.4,0,-4.5),Vector3(-4.4,0,0),Vector3(4.4,0,0),Vector3(-4.4,0,4.5),Vector3(4.4,0,4.5)]
+var pads: Array[Node3D] = []
+var pad_labels: Array[Label3D] = []
+var strip_nodes: Array[MeshInstance3D] = []
+var fans: Array[Node3D] = []
+var district: Node3D
+var beacon: MeshInstance3D
+var district_progress: float = 0.0
+var signal_material: StandardMaterial3D
+var muted_material: StandardMaterial3D
+
+func construct() -> void:
+ var floor_mat := Geo.material(Color("263a48"),0.28,0.8)
+ var concrete := Geo.material(Color("4c6570"),0.1,0.85)
+ var dark := Geo.material(Color("101f2b"),0.4,0.5)
+ var trim := Geo.material(Color("81969d"),0.65,0.4)
+ var yellow := Geo.material(Color("e0aa4d"),0.25,0.6)
+ var gold := Geo.material(Color("f6cb7c"),0.1,0.3,1.5)
+ var cyan := Geo.material(Color("7ae5e1"),0.2,0.4,1.4)
+ signal_material = cyan
+ muted_material = Geo.material(Color("2d5360"),0.1,0.7,0.15)
+ # Floating architectural slab. The game grows out of a physical foundation.
+ Geo.box(self,Vector3(0,-0.68,0),Vector3(19.5,1.25,19.7),dark)
+ Geo.box(self,Vector3(0,-0.07,0),Vector3(19.8,0.15,20),trim)
+ # One MultiMesh for all floor tiles rather than hundreds of separate draws.
+ var tile := BoxMesh.new()
+ tile.size=Vector3(1.56,0.09,1.56)
+ var batch := MultiMesh.new()
+ batch.transform_format=MultiMesh.TRANSFORM_3D
+ batch.mesh=tile
+ batch.instance_count=144
+ for x in range(12):
+ for z in range(12):
+ batch.set_instance_transform(x*12+z,Transform3D(Basis.IDENTITY,Vector3((x-5.5)*1.6,0.025,(z-5.5)*1.6)))
+ var floor_mesh := MultiMeshInstance3D.new()
+ floor_mesh.multimesh=batch
+ floor_mesh.material_override=floor_mat
+ add_child(floor_mesh)
+ # Central aisle and recessed data channels.
+ for x in [-1.85,1.85]:
+ Geo.box(self,Vector3(x,0.08,0),Vector3(0.08,0.025,18.8),yellow)
+ strip_nodes.append(Geo.box(self,Vector3(x+0.13,0.09,0),Vector3(0.035,0.025,18.8),muted_material))
+ for z in [-7.1,2.35,7.1]:
+ Geo.box(self,Vector3(0,0.075,z),Vector3(17.5,0.025,0.065),yellow)
+ # Machine foundations and corner brackets are real build locations.
+ for i in range(6):
+ var pad := Node3D.new()
+ pad.position=BAY_POSITIONS[i]
+ add_child(pad)
+ Geo.box(pad,Vector3(0,0.10,0.5),Vector3(3.5,0.12,3.7),dark)
+ for x in [-1.6,1.6]:
+ for z in [-1.1,2.1]:
+ Geo.box(pad,Vector3(x,0.18,z),Vector3(0.07,0.04,0.38),yellow)
+ Geo.box(pad,Vector3(x-signf(x)*0.16,0.18,z-signf(z)*0.16),Vector3(0.38,0.04,0.07),yellow)
+ var text := Geo.label(pad,"BAY %02d"%(i+1),Vector3(0,0.2,0.2),48,Color("4b7581"))
+ text.rotation.x=-PI/2
+ pads.append(pad)
+ pad_labels.append(text)
+ # Back wall, high service doors, illuminated windows and exposed structure.
+ Geo.box(self,Vector3(0,1.8,-9.6),Vector3(19.5,3.6,0.3),concrete)
+ Geo.box(self,Vector3(0,0.65,-9.36),Vector3(19.5,1.1,0.15),dark)
+ for x in [-9.3,-6.2,-3.1,3.1,6.2,9.3]:
+ Geo.box(self,Vector3(x,2.45,-9.25),Vector3(0.22,4.9,0.4),trim)
+ Geo.box(self,Vector3(0,4.85,-9.25),Vector3(19.3,0.24,0.45),dark)
+ for x in [-7.75,-4.65,4.65,7.75]:
+ Geo.box(self,Vector3(x,2.55,-9.4),Vector3(2.7,1.36,0.12),dark)
+ Geo.box(self,Vector3(x,2.55,-9.30),Vector3(2.45,1.08,0.035),Geo.material(Color("183a49"),0.3,0.3,0.4))
+ for n in [-0.75,0,0.75]:
+ Geo.box(self,Vector3(x+n,2.55,-9.25),Vector3(0.04,1.12,0.03),trim)
+ Geo.box(self,Vector3(x,3.85,-9.08),Vector3(2.2,0.065,0.06),gold)
+ # Shipping gate and a large environmental title.
+ Geo.box(self,Vector3(0,1.8,-9.28),Vector3(4.3,3.5,0.12),dark)
+ for y in range(11):
+ Geo.box(self,Vector3(0,0.3+y*0.27,-9.14),Vector3(3.65,0.22,0.12),floor_mat)
+ for x in [-2.04,2.04]:
+ Geo.box(self,Vector3(x,1.75,-9.02),Vector3(0.07,3.3,0.07),cyan)
+ Geo.label(self,"UNIVERSAL / 01",Vector3(0,4.15,-9.0),88,Color("dfc695"))
+ Geo.label(self,"PRECISION LITHOGRAPHY • SECTOR ZERO",Vector3(0,3.62,-9.0),25,Color("819d9f"))
+ # Cutaway side wall and safety railing.
+ for x in [-9.6,9.6]:
+ Geo.box(self,Vector3(x,0.37,0),Vector3(0.22,0.7,19.2),concrete)
+ for z in [-7.2,-2.4,2.4,7.2]:
+ Geo.box(self,Vector3(x,0.85,z),Vector3(0.07,1.1,0.07),yellow)
+ Geo.box(self,Vector3(x,1.3,0),Vector3(0.065,0.065,19.0),yellow)
+ # Overhead utility pipe with couplings, connecting the whole plant.
+ for y in [3.15,3.48]:
+ Geo.pipe(self,Vector3(-8.85,y,-8.9),Vector3(8.85,y,-8.9),0.09,yellow if y>3.2 else trim)
+ for x in [-8,-4,0,4,8]:
+ Geo.cylinder(self,Vector3(x,y,-8.9),0.13,0.13,dark).rotation.z=PI/2
+ for x in [-8.5,8.5]:
+ Geo.pipe(self,Vector3(x,0.2,-8.6),Vector3(x,3.2,-8.6),0.11,trim)
+ # Pressure vessels and rotating ventilation fans.
+ Geo.cylinder(self,Vector3(x,0.9,-7.0),0.48,1.7,dark)
+ Geo.cylinder(self,Vector3(x,1.8,-7.0),0.49,0.18,trim,0.32)
+ Geo.ring(self,Vector3(x,0.45,-7.0),0.49,0.035,gold)
+ Geo.ring(self,Vector3(x,1.45,-7.0),0.49,0.035,trim)
+ var fan := Node3D.new()
+ fan.position=Vector3(x,4.15,-9.0)
+ fan.rotation.x=PI/2
+ add_child(fan)
+ Geo.ring(fan,Vector3.ZERO,0.38,0.04,dark)
+ for blade in range(4):
+ var mesh := Geo.box(fan,Vector3.ZERO,Vector3(0.65,0.05,0.14),trim)
+ mesh.rotation.y=blade*PI/4
+ fans.append(fan)
+ # Input stock racks and crates along the front service lane.
+ for x in [-7.6,7.6]:
+ for level in range(3):
+ Geo.box(self,Vector3(x,0.3+level*0.36,8.2),Vector3(1.45,0.3,0.8),floor_mat)
+ Geo.box(self,Vector3(x,0.3+level*0.36,8.62),Vector3(0.8,0.13,0.015),yellow)
+ Geo.label(self,"S I L I C O N I N",Vector3(-6.2,0.09,7.55),35,Color("8aadaf")).rotation.x=-PI/2
+ Geo.label(self,"I N T E L L I G E N C E O U T",Vector3(3.7,0.09,8.5),29,Color("e1b974")).rotation.x=-PI/2
+ _create_district()
+ var moving: Array=[district]
+ moving.append_array(fans)
+ moving.append_array(strip_nodes)
+ Geo.batch(self,moving)
+ Geo.batch(district,[beacon])
+ for fan in fans:Geo.batch(fan)
+
+func _create_district() -> void:
+ district=Node3D.new()
+ add_child(district)
+ var dark := Geo.material(Color("182b3a"),0.4,0.65)
+ var lit := Geo.material(Color("7fcfc7"),0.3,0.3,0.8)
+ for side in [-1,1]:
+ for i in range(4):
+ var x: float = side*(14.5+(i%2)*5.7)
+ var z: float = -8.0-(i/2)*7.0
+ var height: float = 3.0+i*1.6
+ Geo.box(district,Vector3(x,height/2-0.6,z),Vector3(4.7,height,5.4),dark)
+ for row in range(4):
+ Geo.box(district,Vector3(x,0.4+row*0.6,z+2.72),Vector3(3.7,0.045,0.03),lit)
+ Geo.box(district,Vector3(x,height-0.5,z),Vector3(3.4,0.13,3.6),dark)
+ Geo.pipe(district,Vector3(x,0.15,z+3),Vector3(side*9.8,0.15,6),0.035,lit)
+ var beacon_mat := Geo.material(Color("77e8e3"),0,0.2,2.0)
+ beacon=Geo.cylinder(district,Vector3(0,5.0,-13),0.12,10,beacon_mat)
+ Geo.cylinder(district,Vector3(0,0,-13),1.4,0.5,dark)
+ Geo.ring(district,Vector3(0,0.35,-13),1.3,0.065,lit)
+ district.visible=false
+
+func animate(delta: float, time: float, count: int, linked: bool) -> void:
+ for i in range(6):
+ pad_labels[i].visible=i>=count
+ pad_labels[i].modulate=Color("f4cb82") if i==count else Color("4b7581")
+ for fan in fans:
+ fan.rotate_y(delta*(1.5+count*0.2))
+ for strip in strip_nodes:
+ strip.material_override=signal_material if count>0 else muted_material
+ if linked:
+ district.visible=true
+ district_progress=minf(1.0,district_progress+delta*0.24)
+ district.position.y=lerpf(-8.0,0.0,ease(district_progress,0.3))
+ beacon.scale.x=1.0+sin(time*2)*0.2
+ beacon.scale.z=beacon.scale.x
diff --git a/godot/scripts/chamber.gd.uid b/godot/scripts/chamber.gd.uid
new file mode 100644
index 0000000..c0b1b21
--- /dev/null
+++ b/godot/scripts/chamber.gd.uid
@@ -0,0 +1 @@
+uid://mp0l3h24635c
diff --git a/godot/scripts/factory.gd b/godot/scripts/factory.gd
new file mode 100644
index 0000000..7fe47fc
--- /dev/null
+++ b/godot/scripts/factory.gd
@@ -0,0 +1,381 @@
+extends Node3D
+
+const Simulation = preload("res://scripts/simulation.gd")
+const Chamber = preload("res://scripts/chamber.gd")
+const Machine = preload("res://scripts/machine.gd")
+const Interface = preload("res://scripts/interface.gd")
+const SAVE_PATH: String = "user://the-seed-v1.json"
+var sim: SeedSimulation
+var room: FactoryChamber
+var ui: FactoryInterface
+var manual: FabMachine
+var machines: Array[FabMachine] = []
+var prepared_machines: Array[FabMachine] = []
+var last_window_size:=Vector2i.ZERO
+var camera: Camera3D
+var key_light: DirectionalLight3D
+var yaw: float = 0.64
+var pitch: float = 0.68
+var zoom: float = 27.5
+var target_zoom: float = 27.5
+var time: float = 0.0
+var save_timer: float = 0.0
+var click_origin := Vector2.ZERO
+var dragging: bool = false
+var pointer_down: bool = false
+var mouse_delta: float = 0.0
+var saving: bool = true
+var test_mode: bool = false
+var inspecting: int = -1
+var camera_focus: bool = false
+var camera_target := Vector3(0,0.5,-0.3)
+var sounds: Dictionary = {}
+var sound_voices: Array[AudioStreamPlayer] = []
+var sound_index: int = 0
+var ambience: AudioStreamPlayer
+var production_chime: float = 0.0
+var selected_ring: MeshInstance3D
+var debug_timer: float = 0.0
+var fps_samples: int = 0
+var reset_pending: bool = false
+
+func _ready() -> void:
+ Engine.max_fps=30
+ _limit_render_size()
+ get_window().size_changed.connect(_limit_render_size)
+ sim=Simulation.new()
+ if OS.has_feature("web"):
+ test_mode=str(JavaScriptBridge.get_interface("window").location.search).contains("test=1")
+ else:
+ test_mode="--test" in OS.get_cmdline_user_args()
+ saving=not test_mode
+ if OS.has_feature("web") and str(JavaScriptBridge.get_interface("window").location.search).contains("persist=1"):
+ saving=true
+ if saving:_load_game()
+ _create_environment()
+ room=Chamber.new()
+ add_child(room)
+ room.construct()
+ manual=Machine.new()
+ manual.position=Vector3(0,0.18,-0.4)
+ add_child(manual)
+ manual.construct(-1,true)
+ # Prepare geometry during loading, never inside a purchase or production event.
+ for i in Simulation.MAX_FABS:
+ var machine:=Machine.new()
+ machine.position=Chamber.BAY_POSITIONS[i]+Vector3(0,0.2,0)
+ add_child(machine)
+ machine.construct(i)
+ machine.visible=false
+ prepared_machines.append(machine)
+ for i in sim.fabs:_add_machine(i,false)
+ camera_focus=sim.fabs==0
+ _create_camera()
+ _create_audio()
+ ui=Interface.new()
+ add_child(ui)
+ ui.action_requested.connect(_action)
+ selected_ring=FactoryGeometry.ring(self,Vector3(0,0.14,-0.4),1.65,0.014,FactoryGeometry.material(Color("f4cb80"),0,0.3,1))
+ if sim.fabs>0:ui.notify("Run restored. Your machines were waiting for you.")
+ print("THE SEED: ready — %d fabs, %d chips"%[sim.fabs,sim.chips])
+
+func _create_environment() -> void:
+ var world:=WorldEnvironment.new()
+ var env:=Environment.new()
+ env.background_mode=Environment.BG_COLOR
+ env.background_color=Color("09131f")
+ env.ambient_light_source=Environment.AMBIENT_SOURCE_COLOR
+ env.ambient_light_color=Color("9db9d0")
+ env.ambient_light_energy=0.38
+ env.tonemap_mode=Environment.TONE_MAPPER_FILMIC
+ world.environment=env
+ add_child(world)
+ key_light=DirectionalLight3D.new()
+ key_light.rotation_degrees=Vector3(-53,-32,0)
+ key_light.light_color=Color("ffe4b4")
+ key_light.light_energy=1.25
+ key_light.shadow_enabled=true
+ key_light.directional_shadow_max_distance=65
+ key_light.shadow_bias=0.04
+ add_child(key_light)
+ var rim:=DirectionalLight3D.new()
+ rim.rotation_degrees=Vector3(-25,140,0)
+ rim.light_color=Color("77b3e6")
+ rim.light_energy=0.65
+ add_child(rim)
+ var fill:=OmniLight3D.new()
+ fill.position=Vector3(0,4,-4)
+ fill.light_color=Color("80e8d9")
+ fill.light_energy=1.3
+ fill.omni_range=13
+ add_child(fill)
+
+func _create_camera() -> void:
+ camera=Camera3D.new()
+ camera.projection=Camera3D.PROJECTION_ORTHOGONAL
+ camera.size=zoom
+ camera.far=140
+ camera.current=true
+ add_child(camera)
+ _camera_update(1)
+
+func _limit_render_size() -> void:
+ # Bound GPU work on high-DPI screens; preserve aspect and portrait controls.
+ var window:=get_window()
+ if window.size==last_window_size:return
+ last_window_size=window.size
+ var physical:=Vector2(window.size)
+ var factor:=minf(1.0,minf(1440.0/maxf(1,physical.x),900.0/maxf(1,physical.y)))
+ var target:=Vector2i((physical*factor).round())
+ window.content_scale_mode=Window.CONTENT_SCALE_MODE_VIEWPORT
+ if window.content_scale_size!=target:window.content_scale_size=target
+
+func _create_audio() -> void:
+ for id in ["etch","chip","build","supply","uplink"]:
+ sounds[id]=load("res://assets/audio/"+id+".wav")
+ for i in 8:
+ var voice:=AudioStreamPlayer.new()
+ voice.volume_db=-16
+ add_child(voice)
+ sound_voices.append(voice)
+ ambience=AudioStreamPlayer.new()
+ ambience.stream=load("res://assets/audio/room.wav")
+ ambience.volume_db=-27
+ add_child(ambience)
+
+func _sound(id: String, volume: float=-15.0) -> void:
+ if not sim.sound_enabled:return
+ if not ambience.playing:ambience.play()
+ var voice: AudioStreamPlayer=sound_voices[sound_index%sound_voices.size()]
+ sound_index+=1
+ voice.stream=sounds[id]
+ voice.volume_db=volume
+ voice.pitch_scale=1.0+float(sound_index%5)*0.015
+ voice.play()
+
+func _process(delta: float) -> void:
+ _limit_render_size()
+ # A short fixed step keeps production deterministic across render frame rates.
+ # Browser tab suspension does not mint unobserved chips in this prototype.
+ var remaining: float=minf(delta,0.25)
+ while remaining>0.00001:
+ var dt: float=minf(remaining,1.0/60.0)
+ sim.step(dt)
+ remaining-=dt
+ if Input.is_physical_key_pressed(KEY_SPACE) and not ui.reset_confirm.visible:
+ sim.etch()
+ time+=delta
+ production_chime=maxf(0,production_chime-delta)
+ _handle_events()
+ manual.animate(delta,sim.manual_progress,sim.overclock)
+ for i in machines.size():machines[i].animate(delta,sim.cycles[i],sim.overclock)
+ room.animate(delta,time,sim.fabs,sim.linked)
+ if sim.linked:target_zoom=maxf(target_zoom,39.0)
+ _camera_update(delta)
+ ui.selected=inspecting
+ ui.update(sim,delta,saving)
+ selected_ring.position=(manual.position if inspecting<0 else machines[inspecting].position)+Vector3(0,0.02,0)
+ selected_ring.rotation.y=time*0.08
+ if ambience.playing:
+ ambience.volume_db=lerpf(ambience.volume_db,-27+sim.fabs*0.7,minf(1,delta))
+ save_timer+=delta
+ if save_timer>2:
+ save_timer=0
+ if saving:_save_game()
+ if test_mode and OS.has_feature("web"):
+ debug_timer+=delta
+ if debug_timer>0.25:
+ debug_timer=0
+ var snapshot:=sim.to_save()
+ snapshot["machines_visible"]=machines.size()
+ snapshot["fps"]=Engine.get_frames_per_second()
+ snapshot["nodes"]=get_tree().get_node_count()
+ snapshot["render_width"]=get_viewport().get_visible_rect().size.x
+ snapshot["render_height"]=get_viewport().get_visible_rect().size.y
+ snapshot["cinema"]=ui.focus_mode
+ snapshot["camera_focus"]=camera_focus
+ snapshot["draw_calls"]=Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME)
+ var browser_window=JavaScriptBridge.get_interface("window")
+ browser_window.__seed=JavaScriptBridge.get_interface("JSON").parse(JSON.stringify(snapshot))
+
+func _camera_update(delta: float) -> void:
+ var size: Vector2=get_viewport().get_visible_rect().size
+ zoom=lerpf(zoom,13.0 if camera_focus else target_zoom,minf(1.0,delta*2.0))
+ camera.size=zoom if size.x>=1000 else zoom*1.12 if size.x>650 else zoom*1.2
+ camera.h_offset=2.7 if size.x>=1000 and not ui_is_cinema() else 0.0
+ camera.v_offset=-0.5 if size.x>=1000 else -1.2
+ var target:=Vector3(0,0.5,-0.3)
+ if camera_focus:
+ target=(manual.position if inspecting<0 else machines[inspecting].position)+Vector3(0,1.0,0.4)
+ camera_target=camera_target.lerp(target,minf(1.0,delta*2))
+ camera.position=camera_target+Vector3(sin(yaw)*cos(pitch),sin(pitch),cos(yaw)*cos(pitch))*36
+ camera.look_at(camera_target)
+
+func ui_is_cinema() -> bool:
+ return is_instance_valid(ui) and ui.focus_mode
+
+func _add_machine(index: int, animate: bool=true) -> void:
+ var machine: FabMachine=prepared_machines[index]
+ machine.visible=true
+ if animate:machine.birth=0
+ machines.append(machine)
+
+func _action(action: String) -> void:
+ if ui.reset_confirm.visible and action!="reset":return
+ match action:
+ "etch":sim.etch()
+ "supply":
+ if not sim.buy_wafers() and not sim.reclaim():ui.notify("A wafer shipment costs $600. Keep etching.")
+ "fab":
+ if not sim.build_fab():ui.notify("Earn $%d to commission the next machine."%sim.fab_cost())
+ "upgrade":sim.upgrade()
+ "controller":sim.toggle_controller()
+ "uplink":sim.uplink()
+ "sound":
+ sim.sound_enabled=not sim.sound_enabled
+ # Web sample playback restarts its source on every unpause assignment.
+ # Only change this state in response to an actual sound toggle.
+ if ambience.stream_paused==sim.sound_enabled:
+ ambience.stream_paused=not sim.sound_enabled
+ if sim.sound_enabled:_sound("chip")
+ "view":ui.toggle_view()
+ "inspect":camera_focus=not camera_focus
+ "reset":
+ sim=Simulation.new()
+ if saving:_save_game()
+ get_tree().reload_current_scene()
+
+func _handle_events() -> void:
+ for event in sim.take_events():
+ match event.type:
+ "start":
+ if int(event.machine)==-1:_sound("etch",-18)
+ "chip":
+ var index: int=int(event.machine)
+ if index<0:manual.eject()
+ elif index=0 else -16)
+ production_chime=0.18
+ if sim.chips==1:ui.notify("First chip shipped. $100. A very small beginning.",true)
+ elif sim.chips==12 and sim.fabs==0:ui.notify("Your first fab is ready to fund. Press B or choose Install a fab.",true)
+ "build":
+ _add_machine(int(event.machine))
+ camera_focus=false
+ _sound("build",-12)
+ inspecting=int(event.machine)
+ if sim.fabs==1:ui.notify("The first machine is working without you.",true)
+ elif sim.fabs==3:ui.notify("Three fabs online. The supply controller is now available.",true)
+ elif sim.fabs==6:ui.notify("All six bays are alive. The district uplink is ready.",true)
+ else:ui.notify("Bay %02d connected. The room grows louder."%sim.fabs)
+ "supply":
+ _sound("supply",-20)
+ ui.notify("30 silicon wafers delivered. $600 debited.")
+ "reclaim":
+ ui.notify("Emergency scrap recovered: 3 usable wafers. You can rebuild.",true)
+ "upgrade":
+ _sound("build",-14)
+ ui.notify("Overclock online. Fabrication cycle: 3.2s → 1.8s.",true)
+ "controller":ui.notify("Supply controller enabled. It buys wafers below the reserve threshold." if sim.controller else "Supply controller paused. Procurement is yours again.",true)
+ "uplink":
+ _sound("uplink",-10)
+ ui.notify("UPLINK ESTABLISHED. You are no longer the only factory.",true)
+
+func _input(event: InputEvent) -> void:
+ if event is InputEventKey and event.pressed and not event.echo and not ui.reset_confirm.visible:
+ match event.physical_keycode:
+ KEY_SPACE:_action("etch")
+ KEY_B:_action("fab")
+ KEY_R:_action("supply")
+ KEY_O:_action("upgrade")
+ KEY_A:_action("controller")
+ KEY_U:_action("uplink")
+ KEY_M:_action("sound")
+ KEY_F:_action("view")
+ KEY_C:_action("inspect")
+ KEY_ESCAPE:
+ if ui.focus_mode:ui.toggle_view()
+ KEY_Q:yaw-=0.12
+ KEY_E:yaw+=0.12
+ KEY_HOME:
+ camera_focus=false
+ yaw=0.64
+ pitch=0.68
+ target_zoom=27.5
+ _:return
+ get_viewport().set_input_as_handled()
+
+func _unhandled_input(event: InputEvent) -> void:
+ if event is InputEventMouseButton:
+ if event.button_index in [MOUSE_BUTTON_WHEEL_UP,MOUSE_BUTTON_WHEEL_DOWN] and event.pressed:
+ if camera_focus:target_zoom=zoom
+ camera_focus=false
+ target_zoom=clampf(target_zoom+(-1.2 if event.button_index==MOUSE_BUTTON_WHEEL_UP else 1.2),10,46)
+ elif event.button_index in [MOUSE_BUTTON_LEFT,MOUSE_BUTTON_RIGHT]:
+ if event.pressed:
+ pointer_down=true
+ dragging=event.button_index==MOUSE_BUTTON_RIGHT
+ click_origin=event.position
+ mouse_delta=0
+ else:
+ if pointer_down and not dragging and mouse_delta<6:_pick(event.position)
+ pointer_down=false
+ dragging=false
+ elif event is InputEventMouseMotion and pointer_down:
+ mouse_delta+=event.relative.length()
+ if mouse_delta>6:dragging=true
+ if dragging:
+ yaw-=event.relative.x*0.006
+ pitch=clampf(pitch+event.relative.y*0.004,0.35,1.2)
+ elif event is InputEventMagnifyGesture:target_zoom=clampf(target_zoom/event.factor,16,46)
+ elif event is InputEventScreenDrag:
+ yaw-=event.relative.x*0.006
+ pitch=clampf(pitch+event.relative.y*0.004,0.35,1.2)
+
+func _pick(screen_pos: Vector2) -> void:
+ var origin:=camera.project_ray_origin(screen_pos)
+ var direction:=camera.project_ray_normal(screen_pos)
+ var best: float=INF
+ var hit_id: int=-99
+ var targets: Array[FabMachine]=[manual]
+ targets.append_array(machines)
+ for machine in targets:
+ var bounds:=AABB(machine.position+Vector3(-1.5,0,-1.1),Vector3(3,3.2,3.8))
+ var hit: Variant=bounds.intersects_ray(origin,direction)
+ if hit is Vector3:
+ var distance: float=origin.distance_to(hit)
+ if distance=0 else "WAITING FOR SILICON",sim.cycle_seconds()])
+ return
+ var ground: Variant=Plane(Vector3.UP,0.15).intersects_ray(origin,direction)
+ if ground is Vector3:
+ for i in range(sim.fabs,6):
+ if (ground-Chamber.BAY_POSITIONS[i]).length()<2:
+ if i==sim.fabs:_action("fab")
+ else:ui.notify("Connect bay %02d first. The line expands in sequence."%(sim.fabs+1))
+ return
+
+func _save_game() -> void:
+ var file:=FileAccess.open(SAVE_PATH,FileAccess.WRITE)
+ if file==null:
+ saving=false
+ ui.notify("Local storage unavailable. This run will last for this session.")
+ return
+ file.store_string(JSON.stringify(sim.to_save()))
+ file.close()
+
+func _load_game() -> void:
+ if not FileAccess.file_exists(SAVE_PATH):return
+ var file:=FileAccess.open(SAVE_PATH,FileAccess.READ)
+ if file==null:return
+ var data: Variant=JSON.parse_string(file.get_as_text())
+ if data is Dictionary:sim.restore(data)
+
+func _notification(what: int) -> void:
+ if what==NOTIFICATION_APPLICATION_FOCUS_OUT and is_instance_valid(sim) and saving:
+ _save_game()
diff --git a/godot/scripts/factory.gd.uid b/godot/scripts/factory.gd.uid
new file mode 100644
index 0000000..fdd362a
--- /dev/null
+++ b/godot/scripts/factory.gd.uid
@@ -0,0 +1 @@
+uid://c0vd54mjutf3t
diff --git a/godot/scripts/geometry.gd b/godot/scripts/geometry.gd
new file mode 100644
index 0000000..26bcf1c
--- /dev/null
+++ b/godot/scripts/geometry.gd
@@ -0,0 +1,98 @@
+class_name FactoryGeometry
+extends RefCounted
+
+static func material(color: Color, metal: float = 0.0, rough: float = 0.6, emission: float = 0.0) -> StandardMaterial3D:
+ var m := StandardMaterial3D.new()
+ m.albedo_color = color
+ m.metallic = metal
+ m.roughness = rough
+ if emission > 0:
+ m.emission_enabled = true
+ m.emission = color
+ m.emission_energy_multiplier = emission
+ return m
+
+static func box(parent: Node3D, pos: Vector3, size: Vector3, mat: Material) -> MeshInstance3D:
+ var mesh := BoxMesh.new()
+ mesh.size = size
+ return instance(parent, mesh, pos, mat)
+
+static func cylinder(parent: Node3D, pos: Vector3, radius: float, height: float, mat: Material, top: float = -1.0) -> MeshInstance3D:
+ var mesh := CylinderMesh.new()
+ mesh.top_radius = radius if top < 0 else top
+ mesh.bottom_radius = radius
+ mesh.height = height
+ mesh.radial_segments = 32
+ return instance(parent, mesh, pos, mat)
+
+static func sphere(parent: Node3D, pos: Vector3, radius: float, mat: Material) -> MeshInstance3D:
+ var mesh := SphereMesh.new()
+ mesh.radius = radius
+ mesh.height = radius * 2.0
+ mesh.radial_segments = 12
+ mesh.rings = 6
+ return instance(parent, mesh, pos, mat)
+
+static func ring(parent: Node3D, pos: Vector3, radius: float, thickness: float, mat: Material) -> MeshInstance3D:
+ var mesh := TorusMesh.new()
+ mesh.inner_radius = radius - thickness
+ mesh.outer_radius = radius + thickness
+ mesh.rings = 40
+ mesh.ring_segments = 8
+ return instance(parent, mesh, pos, mat)
+
+static func instance(parent: Node3D, mesh: Mesh, pos: Vector3, mat: Material) -> MeshInstance3D:
+ var node := MeshInstance3D.new()
+ node.mesh = mesh
+ node.material_override = mat
+ node.position = pos
+ parent.add_child(node)
+ return node
+
+static func pipe(parent: Node3D, from: Vector3, to: Vector3, radius: float, mat: Material) -> MeshInstance3D:
+ var node := cylinder(parent, (from + to) * 0.5, radius, from.distance_to(to), mat)
+ node.quaternion = Quaternion(Vector3.UP, (to - from).normalized())
+ return node
+
+static func label(parent: Node3D, content: String, pos: Vector3, size: int = 40, color: Color = Color.WHITE) -> Label3D:
+ var text := Label3D.new()
+ text.text = content
+ text.font_size = size
+ text.pixel_size = 0.006
+ text.modulate = color
+ text.outline_size = 0
+ text.no_depth_test = false
+ text.position = pos
+ parent.add_child(text)
+ return text
+
+## Collapse static parts by material. Machines retain their moving subassemblies;
+## the floor doesn't pay a draw call for every bolt and railing segment.
+static func batch(parent: Node3D, excluded: Array = []) -> void:
+ var groups: Dictionary = {}
+ _collect_meshes(parent,parent,excluded,groups)
+ for key in groups:
+ var group: Array=groups[key]
+ var surface:=SurfaceTool.new()
+ surface.begin(Mesh.PRIMITIVE_TRIANGLES)
+ var material_ref: Material=group[0].material_override
+ for source: MeshInstance3D in group:
+ var transform: Transform3D=parent.global_transform.affine_inverse()*source.global_transform
+ surface.append_from(source.mesh,0,transform)
+ var merged:=MeshInstance3D.new()
+ merged.mesh=surface.commit()
+ merged.material_override=material_ref
+ parent.add_child(merged)
+ for source: MeshInstance3D in group:
+ source.get_parent().remove_child(source)
+ source.queue_free()
+
+static func _collect_meshes(root: Node3D, node: Node3D, excluded: Array, groups: Dictionary) -> void:
+ for child in node.get_children():
+ if child in excluded:continue
+ if child is MeshInstance3D and child.material_override!=null and child.mesh!=null:
+ var key: int=child.material_override.get_instance_id()
+ if not groups.has(key):groups[key]=[]
+ groups[key].append(child)
+ elif child is Node3D:
+ _collect_meshes(root,child,excluded,groups)
diff --git a/godot/scripts/geometry.gd.uid b/godot/scripts/geometry.gd.uid
new file mode 100644
index 0000000..9abaaa3
--- /dev/null
+++ b/godot/scripts/geometry.gd.uid
@@ -0,0 +1 @@
+uid://ijgutu0hg363
diff --git a/godot/scripts/interface.gd b/godot/scripts/interface.gd
new file mode 100644
index 0000000..cc35df5
--- /dev/null
+++ b/godot/scripts/interface.gd
@@ -0,0 +1,389 @@
+class_name FactoryInterface
+extends CanvasLayer
+
+signal action_requested(action: String)
+const INK := Color("0d1a24")
+const BORDER := Color("304853")
+const MUTED := Color("8ba3b0")
+const WHITE := Color("e6e9df")
+const GOLD := Color("f4ca7a")
+const TEAL := Color("84dfcf")
+
+var root: Control
+var top_shade: TextureRect
+var bottom_shade: TextureRect
+var title: Label
+var subtitle: Label
+var money: Label
+var wafers: Label
+var output: Label
+var count: Label
+var metric_box: HBoxContainer
+var chapter_panel: PanelContainer
+var chapter_title: Label
+var chapter_note: Label
+var objective: Label
+var objective_progress: ProgressBar
+var objective_numbers: Label
+var journal: Label
+var selection_label: Label
+var dock: GridContainer
+var buttons: Dictionary = {}
+var captions: Dictionary = {}
+var headings: Dictionary = {}
+var etch_progress: ProgressBar
+var toast: PanelContainer
+var toast_label: Label
+var toast_time: float = 0.0
+var mute_button: Button
+var help: Label
+var hint: Label
+var reset_confirm: ConfirmationDialog
+var screen_size := Vector2.ZERO
+var focus_mode: bool = false
+var selected: int = -1
+
+func _style(bg: Color = INK, line: Color = BORDER, radius: int = 5) -> StyleBoxFlat:
+ var s := StyleBoxFlat.new()
+ s.bg_color = bg
+ s.border_color = line
+ s.set_border_width_all(1)
+ s.set_corner_radius_all(radius)
+ s.content_margin_left=16
+ s.content_margin_right=16
+ s.content_margin_top=12
+ s.content_margin_bottom=12
+ return s
+
+func _label(text: String, size: int = 14, color: Color = WHITE) -> Label:
+ var node := Label.new()
+ node.text=text
+ node.add_theme_font_size_override("font_size",size)
+ node.add_theme_color_override("font_color",color)
+ node.mouse_filter=Control.MOUSE_FILTER_IGNORE
+ return node
+
+func _ready() -> void:
+ layer=10
+ root=Control.new()
+ root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
+ root.mouse_filter=Control.MOUSE_FILTER_IGNORE
+ add_child(root)
+ top_shade=_shade(false)
+ bottom_shade=_shade(true)
+ root.add_child(top_shade)
+ root.add_child(bottom_shade)
+ var brand := _label("UNIVERSAL AI",21,WHITE)
+ brand.position=Vector2(28,24)
+ root.add_child(brand)
+ var edition := _label("T H E S E E D / A F A B R I C A T I O N S T O R Y",9,MUTED)
+ edition.position=Vector2(30,53)
+ root.add_child(edition)
+ title=_label("A small beginning.",34,WHITE)
+ title.position=Vector2(28,115)
+ root.add_child(title)
+ subtitle=_label("One wafer. One machine. An unreasonable ambition.",12,MUTED)
+ subtitle.position=Vector2(30,160)
+ root.add_child(subtitle)
+ metric_box=HBoxContainer.new()
+ metric_box.add_theme_constant_override("separation",30)
+ root.add_child(metric_box)
+ money=_metric("CAPITAL",GOLD)
+ wafers=_metric("WAFERS",WHITE)
+ output=_metric("CHIPS SHIPPED",TEAL)
+ count=_metric("AUTONOMOUS FABS",WHITE)
+ chapter_panel=PanelContainer.new()
+ chapter_panel.add_theme_stylebox_override("panel",_style(Color(0.045,0.085,0.12,0.93),BORDER))
+ root.add_child(chapter_panel)
+ var chapter_box:=VBoxContainer.new()
+ chapter_box.add_theme_constant_override("separation",13)
+ chapter_panel.add_child(chapter_box)
+ chapter_box.add_child(_label("O B J E C T I V E / 0 1",10,GOLD))
+ chapter_title=_label("Build the machine\nthat builds the machine.",22,WHITE)
+ chapter_box.add_child(chapter_title)
+ chapter_note=_label("Etch 12 chips. Sales are automatic.\nThen install your first autonomous fab.",12,MUTED)
+ chapter_note.autowrap_mode=TextServer.AUTOWRAP_WORD_SMART
+ chapter_box.add_child(chapter_note)
+ var sep:=HSeparator.new()
+ sep.modulate=Color("385360")
+ chapter_box.add_child(sep)
+ objective=_label("FIRST AUTONOMOUS FAB",10,TEAL)
+ chapter_box.add_child(objective)
+ objective_progress=ProgressBar.new()
+ objective_progress.show_percentage=false
+ objective_progress.custom_minimum_size=Vector2(0,4)
+ var track:=_style(Color("263d45"),Color.TRANSPARENT,0)
+ track.content_margin_top=0
+ track.content_margin_bottom=0
+ objective_progress.add_theme_stylebox_override("background",track)
+ var fill:=_style(TEAL,Color.TRANSPARENT,0)
+ fill.content_margin_top=0
+ fill.content_margin_bottom=0
+ objective_progress.add_theme_stylebox_override("fill",fill)
+ chapter_box.add_child(objective_progress)
+ objective_numbers=_label("$0 / $1,200",11,MUTED)
+ chapter_box.add_child(objective_numbers)
+ chapter_box.add_child(_label("S Y S T E M T R A N S M I S S I O N",9,MUTED))
+ journal=_label("The room is quiet.\nThat part is temporary.",13,WHITE)
+ journal.autowrap_mode=TextServer.AUTOWRAP_WORD_SMART
+ chapter_box.add_child(journal)
+ selection_label=_label("CELL 00 / MANUAL LITHOGRAPHY",10,TEAL)
+ root.add_child(selection_label)
+ dock=GridContainer.new()
+ dock.columns=6
+ dock.add_theme_constant_override("h_separation",8)
+ dock.add_theme_constant_override("v_separation",8)
+ root.add_child(dock)
+ _make_action("etch","01 / FABRICATE","Etch a chip","SPACE · 1 wafer > $100",true)
+ _make_action("supply","02 / PROCUREMENT","Order silicon","$600 · +30 wafers")
+ _make_action("fab","03 / EXPANSION","Install a fab","$1,200 · bay 01")
+ _make_action("upgrade","04 / RESEARCH","Overclock","2 fabs required")
+ _make_action("controller","05 / AUTONOMY","Supply controller","3 fabs required")
+ _make_action("uplink","06 / BEYOND","District uplink","6 fabs required")
+ etch_progress=ProgressBar.new()
+ etch_progress.mouse_filter=Control.MOUSE_FILTER_IGNORE
+ etch_progress.show_percentage=false
+ etch_progress.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_WIDE)
+ etch_progress.offset_top=-4
+ for part in ["background","fill"]:
+ var bar_style:=StyleBoxFlat.new()
+ bar_style.bg_color=Color("87dbc2") if part=="fill" else Color.TRANSPARENT
+ etch_progress.add_theme_stylebox_override(part,bar_style)
+ buttons.etch.add_child(etch_progress)
+ mute_button=_small_button("SOUND ON",func():action_requested.emit("sound"))
+ root.add_child(mute_button)
+ var reset:=_small_button("NEW RUN",func():reset_confirm.popup_centered())
+ reset.name="ResetButton"
+ root.add_child(reset)
+ var view:=_small_button("CINEMA",func():action_requested.emit("view"))
+ view.name="ViewButton"
+ root.add_child(view)
+ var detail:=_small_button("CLOSE UP",func():action_requested.emit("inspect"))
+ detail.name="DetailButton"
+ root.add_child(detail)
+ help=_label("SPACE etch · B build · R restock · drag to orbit · scroll to zoom · F cinema",10,MUTED)
+ root.add_child(help)
+ hint=_label("CLICK THE CENTRAL MACHINE TO ETCH",11,GOLD)
+ root.add_child(hint)
+ toast=PanelContainer.new()
+ toast.add_theme_stylebox_override("panel",_style(Color("172e36"),GOLD))
+ root.add_child(toast)
+ toast_label=_label("",16,WHITE)
+ toast_label.horizontal_alignment=HORIZONTAL_ALIGNMENT_CENTER
+ toast.add_child(toast_label)
+ toast.visible=false
+ reset_confirm=ConfirmationDialog.new()
+ reset_confirm.title="Begin again?"
+ reset_confirm.dialog_text="This resets the Godot prototype's factory and local save."
+ reset_confirm.confirmed.connect(func():action_requested.emit("reset"))
+ root.add_child(reset_confirm)
+ _layout()
+
+func _shade(reverse: bool) -> TextureRect:
+ var node:=TextureRect.new()
+ node.mouse_filter=Control.MOUSE_FILTER_IGNORE
+ var texture:=GradientTexture2D.new()
+ texture.width=4
+ texture.height=128
+ texture.fill_from=Vector2(0,0)
+ texture.fill_to=Vector2(0,1)
+ var gradient:=Gradient.new()
+ gradient.set_color(0,Color(0.02,0.05,0.08,0.0 if reverse else 0.94))
+ gradient.set_color(1,Color(0.02,0.05,0.08,0.94 if reverse else 0.0))
+ texture.gradient=gradient
+ node.texture=texture
+ node.expand_mode=TextureRect.EXPAND_IGNORE_SIZE
+ return node
+
+func _metric(caption: String, color: Color) -> Label:
+ var v:=VBoxContainer.new()
+ v.add_theme_constant_override("separation",3)
+ metric_box.add_child(v)
+ v.add_child(_label(caption,9,MUTED))
+ var value:=_label("0",22,color)
+ v.add_child(value)
+ return value
+
+func _small_button(text: String, callback: Callable) -> Button:
+ var b:=Button.new()
+ b.text=text
+ b.add_theme_font_size_override("font_size",10)
+ b.add_theme_color_override("font_color",MUTED)
+ b.add_theme_stylebox_override("normal",_style(Color(0.04,0.07,0.1,0.85),BORDER,3))
+ b.add_theme_stylebox_override("hover",_style(Color("233a44"),GOLD,3))
+ b.add_theme_stylebox_override("focus",_style(Color.TRANSPARENT,GOLD,3))
+ b.pressed.connect(callback)
+ return b
+
+func _make_action(id: String, kicker: String, text: String, detail: String, primary: bool=false) -> void:
+ var b:=Button.new()
+ b.custom_minimum_size=Vector2(160,91)
+ b.size_flags_horizontal=Control.SIZE_EXPAND_FILL
+ b.mouse_default_cursor_shape=Control.CURSOR_POINTING_HAND
+ b.add_theme_stylebox_override("normal",_style(Color("eed095") if primary else Color(0.045,0.085,0.12,0.97),GOLD if primary else BORDER))
+ b.add_theme_stylebox_override("hover",_style(Color("ffe0a1") if primary else Color("243c46"),GOLD))
+ b.add_theme_stylebox_override("pressed",_style(Color("bfa366") if primary else Color("1d323b"),TEAL))
+ b.add_theme_stylebox_override("disabled",_style(Color(0.04,0.075,0.10,0.94),Color("263a46")))
+ b.add_theme_stylebox_override("focus",_style(Color.TRANSPARENT,GOLD))
+ b.pressed.connect(func():action_requested.emit(id))
+ var margin:=MarginContainer.new()
+ margin.mouse_filter=Control.MOUSE_FILTER_IGNORE
+ margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
+ for side in ["left","right"]:margin.add_theme_constant_override("margin_"+side,14)
+ for side in ["top","bottom"]:margin.add_theme_constant_override("margin_"+side,12)
+ b.add_child(margin)
+ var content:=VBoxContainer.new()
+ content.mouse_filter=Control.MOUSE_FILTER_IGNORE
+ content.add_theme_constant_override("separation",5)
+ margin.add_child(content)
+ var kicker_label:=_label(kicker,8,Color("48544e") if primary else MUTED)
+ content.add_child(kicker_label)
+ var h:=_label(text,16,INK if primary else WHITE)
+ content.add_child(h)
+ var d:=_label(detail,10,Color("384b44") if primary else MUTED)
+ content.add_child(d)
+ buttons[id]=b
+ headings[id]=h
+ captions[id]=d
+ dock.add_child(b)
+
+func _layout() -> void:
+ screen_size=root.get_viewport_rect().size
+ var w: float=screen_size.x
+ var h: float=screen_size.y
+ var compact: bool=w<1050
+ top_shade.size=Vector2(w,240)
+ bottom_shade.size=Vector2(w,220 if compact else 175)
+ bottom_shade.position=Vector2(0,h-bottom_shade.size.y)
+ top_shade.visible=not focus_mode
+ bottom_shade.visible=not focus_mode
+ metric_box.position=Vector2(maxf(350,w-655),25)
+ metric_box.add_theme_constant_override("separation",16 if compact else 30)
+ chapter_panel.position=Vector2(w-304,115)
+ chapter_panel.size=Vector2(276,0)
+ chapter_panel.visible=not focus_mode and w>=1000
+ title.visible=not focus_mode and h>650
+ subtitle.visible=title.visible
+ if compact:
+ dock.columns=3
+ dock.position=Vector2(18,h-228)
+ dock.size=Vector2(w-36,190)
+ else:
+ dock.columns=6
+ dock.position=Vector2(24,h-131)
+ dock.size=Vector2(w-48,92)
+ for b: Button in buttons.values():
+ b.custom_minimum_size.x=0
+ dock.visible=not focus_mode
+ selection_label.position=Vector2(28,dock.position.y-25)
+ selection_label.visible=not focus_mode
+ help.position=Vector2(28,h-24)
+ help.text="SPACE etch · B build · R restock · drag to orbit · scroll to zoom · C close up · F cinema" if w>1050 else "SPACE etch · B build · drag to orbit · F cinema"
+ help.visible=not focus_mode
+ hint.position=Vector2(30,190)
+ hint.visible=not focus_mode and h>650
+ root.get_node("DetailButton").position=Vector2(w-380,78)
+ mute_button.position=Vector2(w-276,78)
+ root.get_node("ResetButton").position=Vector2(w-172,78)
+ root.get_node("ViewButton").position=Vector2(w-88,78)
+ toast.position=Vector2(w*0.5-260,90)
+ toast.size=Vector2(520,0)
+ # Portrait keeps the actual controls usable; the world can still be orbited.
+ if w<650:
+ dock.columns=2
+ dock.position.y=h-330
+ dock.size.y=294
+ selection_label.position.y=dock.position.y-25
+ bottom_shade.size.y=350
+ bottom_shade.position.y=h-350
+ metric_box.position=Vector2(24,86)
+ metric_box.add_theme_constant_override("separation",18)
+ for child in metric_box.get_children():
+ child.get_child(0).add_theme_font_size_override("font_size",7)
+ child.get_child(1).add_theme_font_size_override("font_size",17)
+ mute_button.position=Vector2(w-100,18)
+ root.get_node("ResetButton").visible=false
+ root.get_node("ViewButton").visible=false
+ root.get_node("DetailButton").visible=false
+ title.visible=false
+ subtitle.visible=false
+ hint.visible=false
+ for label: Label in headings.values():label.add_theme_font_size_override("font_size",12)
+ for label: Label in captions.values():label.add_theme_font_size_override("font_size",8)
+ toast.position=Vector2(16,140)
+ toast.size=Vector2(w-32,0)
+ toast_label.add_theme_font_size_override("font_size",12)
+
+func update(sim: SeedSimulation, delta: float, saving: bool) -> void:
+ if root.get_viewport_rect().size!=screen_size:_layout()
+ chapter_panel.size.y=440
+ money.text="$%s"%_number(sim.capital)
+ wafers.text=_number(sim.wafers)
+ wafers.add_theme_color_override("font_color",Color("ed9175") if sim.wafers<6 else WHITE)
+ output.text=_number(sim.chips)
+ count.text="%d / 6"%sim.fabs
+ buttons.etch.disabled=sim.manual_progress>=0 or sim.wafers==0
+ buttons.supply.disabled=sim.capital=6
+ buttons.upgrade.disabled=sim.overclock or sim.fabs<2 or sim.capital=0 else "Etch a chip"
+ captions.etch.text="OUT OF SILICON" if sim.wafers==0 and sim.manual_progress<0 else "SPACE · 1 wafer > $100"
+ etch_progress.value=maxf(0.0,sim.manual_progress)*100
+ captions.fab.text="$%s · bay %02d"%[_number(sim.fab_cost()),sim.fabs+1] if sim.fabs<6 else "ALL BAYS CONNECTED"
+ captions.upgrade.text="ACTIVE · 1.8s / chip" if sim.overclock else "$2,400 · 1.8s / chip" if sim.fabs>=2 else "2 fabs required"
+ captions.controller.text="ON · buys low stock" if sim.controller else "OFF · click to enable" if sim.fabs>=3 else "3 fabs required"
+ headings.controller.text="Supply controller"
+ captions.uplink.text="DISTRICT CONNECTED" if sim.linked else "$6,000 · ignite network" if sim.fabs>=6 else "6 fabs required"
+ mute_button.text="SOUND ON" if sim.sound_enabled else "MUTED"
+ if sim.linked:
+ title.text="A much larger beginning."
+ chapter_title.text="The district\nis listening."
+ chapter_note.text="Your factory is now a node in something larger. This is where The Seed ends. Your machines can keep running."
+ objective.text="PROTOTYPE COMPLETE"
+ objective_progress.value=100
+ objective_numbers.text="6 / 6 bays · uplink established"
+ elif sim.fabs>0:
+ title.text="The room has a rhythm."
+ chapter_title.text="Make yourself\nredundant."
+ chapter_note.text="Fill six machine bays. Overclock the line. At three fabs, let the supply controller order your wafers."
+ objective.text="DISTRICT UPLINK" if sim.fabs>=6 else "NEXT AUTONOMOUS FAB"
+ var target: int=sim.UPLINK_COST if sim.fabs>=6 else sim.fab_cost()
+ objective_progress.value=minf(100,float(sim.capital)/target*100)
+ objective_numbers.text="$%s / $%s"%[_number(sim.capital),_number(target)]
+ else:
+ objective_progress.value=minf(100,float(sim.capital)/sim.fab_cost()*100)
+ objective_numbers.text="$%s / $1,200"%_number(sim.capital)
+ if sim.fabs>0:hint.text="CLICK A MACHINE TO INSPECT · CLICK AN EMPTY BAY TO BUILD"
+ selection_label.text=("CELL 00 / MANUAL LITHOGRAPHY" if selected<0 else "CELL %02d / AUTONOMOUS FAB"%(selected+1))+ (" · LOCAL SAVE" if saving else " · SESSION ONLY")
+ if toast_time>0:
+ toast_time-=delta
+ toast.modulate.a=minf(1.0,toast_time*2.0)
+ toast.visible=toast_time>0
+
+func _number(value: int) -> String:
+ var raw:=str(value)
+ var result: String=""
+ for i in range(raw.length()):
+ if i>0 and (raw.length()-i)%3==0:result+=","
+ result+=raw[i]
+ return result
+
+func notify(text: String, big: bool = false) -> void:
+ journal.text=text
+ if big:
+ toast_label.text=text
+ toast_time=4.5
+ toast.visible=true
+ toast.modulate.a=1.0
+
+func toggle_view() -> void:
+ focus_mode=not focus_mode
+ root.get_node("ViewButton").text="EXIT" if focus_mode else "CINEMA"
+ _layout()
diff --git a/godot/scripts/interface.gd.uid b/godot/scripts/interface.gd.uid
new file mode 100644
index 0000000..3a75c7a
--- /dev/null
+++ b/godot/scripts/interface.gd.uid
@@ -0,0 +1 @@
+uid://cp3emycctbylh
diff --git a/godot/scripts/machine.gd b/godot/scripts/machine.gd
new file mode 100644
index 0000000..105856f
--- /dev/null
+++ b/godot/scripts/machine.gd
@@ -0,0 +1,190 @@
+class_name FabMachine
+extends Node3D
+
+const Geo = preload("res://scripts/geometry.gd")
+var machine_id: int = -1
+var is_manual: bool = false
+var head: Node3D
+var laser: MeshInstance3D
+var wafer: MeshInstance3D
+var wafer_mat: ShaderMaterial
+var rotor: Node3D
+var display: Label3D
+var status_light: MeshInstance3D
+var arm: Node3D
+var progress: float = -1.0
+var clock: float = 0.0
+var birth: float = 1.0
+var overclocked: bool = false
+var chip_objects: Array[Dictionary] = []
+var sparks: Array[Dictionary] = []
+var spark_pool: Array[MeshInstance3D] = []
+var chip_pool: Array[Node3D] = []
+var spark_timer: float = 0.0
+var shell_mat: StandardMaterial3D
+var dark_mat: StandardMaterial3D
+var light_mat: StandardMaterial3D
+var teal_mat: StandardMaterial3D
+var copper_mat: StandardMaterial3D
+var beam_mat: StandardMaterial3D
+
+func construct(id: int, manual: bool = false) -> void:
+ machine_id = id
+ is_manual = manual
+ shell_mat = Geo.material(Color("889ca6"), 0.45, 0.35)
+ dark_mat = Geo.material(Color("162b38"), 0.65, 0.38)
+ light_mat = Geo.material(Color("f8cb75"), 0.1, 0.3, 1.4)
+ teal_mat = Geo.material(Color("6ee3d4"), 0.25, 0.3, 1.1)
+ copper_mat = Geo.material(Color("b58356"), 0.7, 0.33)
+ beam_mat = Geo.material(Color("73ffe4"), 0.0, 0.1, 3.0)
+ var base_color := Geo.material(Color("334c59"), 0.6, 0.45)
+ # Plinth, feet, chassis, removable face panels, and service vents.
+ Geo.box(self, Vector3(0,0.15,0), Vector3(2.7,0.3,2.6), dark_mat)
+ Geo.box(self, Vector3(0,0.65,0), Vector3(2.35,0.8,2.15), base_color)
+ Geo.box(self, Vector3(0,1.08,0), Vector3(2.65,0.12,2.45), shell_mat)
+ for x in [-1.0, 1.0]:
+ for z in [-0.9, 0.9]:
+ Geo.cylinder(self, Vector3(x,0.08,z), 0.15, 0.25, copper_mat)
+ Geo.box(self, Vector3(0,0.65,1.09), Vector3(1.9,0.5,0.06), dark_mat)
+ for x in range(7):
+ Geo.box(self, Vector3(-0.76+x*0.24,0.66,1.13), Vector3(0.035,0.31,0.03), shell_mat)
+ Geo.box(self, Vector3(0,0.99,1.13), Vector3(2.2,0.045,0.04), teal_mat)
+ # Precision turntable with a real iridescent wafer shader.
+ Geo.cylinder(self, Vector3(0,1.20,0.05), 0.95, 0.13, dark_mat)
+ rotor = Node3D.new()
+ rotor.position = Vector3(0,1.28,0.05)
+ add_child(rotor)
+ Geo.ring(rotor, Vector3.ZERO, 0.82, 0.027, copper_mat)
+ wafer_mat = ShaderMaterial.new()
+ wafer_mat.shader = preload("res://shaders/wafer.gdshader")
+ wafer = Geo.cylinder(rotor, Vector3(0,0.02,0), 0.76, 0.025, wafer_mat)
+ # An overhead gantry with an independently moving head and Z carriage.
+ for x in [-1.07,1.07]:
+ Geo.box(self, Vector3(x,1.9,-0.81), Vector3(0.19,1.65,0.24), shell_mat)
+ Geo.box(self, Vector3(x,1.93,-0.65), Vector3(0.06,1.38,0.05), copper_mat)
+ Geo.box(self, Vector3(0,2.72,-0.81), Vector3(2.55,0.27,0.34), shell_mat)
+ Geo.box(self, Vector3(0,2.56,-0.64), Vector3(2.19,0.07,0.09), light_mat)
+ head = Node3D.new()
+ head.position = Vector3(0,2.25,0)
+ add_child(head)
+ Geo.box(head, Vector3(0,0.15,-0.42), Vector3(0.42,0.26,1.18), dark_mat)
+ Geo.cylinder(head, Vector3.ZERO, 0.2, 0.35, shell_mat)
+ Geo.cylinder(head, Vector3(0,-0.24,0), 0.12, 0.18, copper_mat, 0.18)
+ Geo.ring(head, Vector3(0,-0.22,0), 0.15, 0.025, light_mat)
+ laser = Geo.cylinder(head, Vector3(0,-0.61,0), 0.013, 0.75, beam_mat)
+ laser.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
+ laser.visible = false
+ # Side monitor and status lamp, arranged to remain legible in the cutaway.
+ Geo.box(self, Vector3(1.12,1.44,0.78), Vector3(0.54,0.5,0.18), dark_mat)
+ Geo.box(self, Vector3(1.12,1.45,0.88), Vector3(0.45,0.35,0.02), Geo.material(Color("123f46"),0.0,0.4,0.45))
+ display = Geo.label(self, "READY", Vector3(1.12,1.45,0.901), 15, Color("a3f4d5"))
+ display.pixel_size = 0.0033
+ status_light = Geo.sphere(self, Vector3(1.07,2.94,-0.81), 0.075, teal_mat)
+ Geo.cylinder(self, Vector3(1.07,2.83,-0.81), 0.035, 0.19, dark_mat)
+ # A robotic loading arm; its shoulder and gripper sweep during the cycle.
+ arm = Node3D.new()
+ arm.position = Vector3(-1.02,1.18,0.7)
+ add_child(arm)
+ Geo.cylinder(arm, Vector3.ZERO, 0.16, 0.12, copper_mat)
+ Geo.pipe(arm, Vector3(0,0.05,0), Vector3(0,0.55,0), 0.07, shell_mat)
+ Geo.pipe(arm, Vector3(0,0.55,0), Vector3(0.58,0.55,0), 0.055, shell_mat)
+ Geo.sphere(arm, Vector3(0,0.55,0),0.11,dark_mat)
+ Geo.box(arm, Vector3(0.58,0.46,0), Vector3(0.17,0.2,0.25), copper_mat)
+ # The output belt physically carries every visible completed chip.
+ Geo.box(self, Vector3(0,0.63,1.92), Vector3(0.88,0.16,1.6), dark_mat)
+ for x in [-0.48,0.48]:
+ Geo.box(self, Vector3(x,0.72,1.92), Vector3(0.07,0.12,1.7), shell_mat)
+ for n in range(7):
+ var roller := Geo.cylinder(self,Vector3(0,0.73,1.3+n*0.19),0.042,0.79,copper_mat)
+ roller.rotation.z = PI/2.0
+ var badge := Geo.label(self,"MANUAL / 00" if manual else "AUTO / %02d" % (id+1),Vector3(-0.05,0.34,1.17),22,Color("c3d4da"))
+ badge.pixel_size=0.004
+ Geo.batch(self,[head,rotor,arm,status_light])
+ Geo.batch(head,[laser])
+ Geo.batch(arm)
+ for i in 6:
+ var particle:=Geo.sphere(self,Vector3.ZERO,0.018,beam_mat)
+ particle.cast_shadow=GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
+ particle.visible=false
+ spark_pool.append(particle)
+ for i in 2:
+ var chip:=_create_chip()
+ chip.visible=false
+ chip_pool.append(chip)
+
+func animate(delta: float, phase: float, hot: bool) -> void:
+ clock += delta
+ spark_timer=maxf(0,spark_timer-delta)
+ progress = phase
+ overclocked = hot
+ if birth < 1.0:
+ birth = minf(1.0, birth + delta * 0.8)
+ var b: float = ease(birth, 0.35)
+ scale = Vector3(1, maxf(0.01,b), 1)
+ var active: bool = progress >= 0.0
+ laser.visible = active and progress > 0.15 and progress < 0.84
+ wafer_mat.set_shader_parameter("activity", 1.0 if laser.visible else 0.0)
+ if active:
+ var scan: float = clampf((progress-0.15)/0.7,0.0,1.0)
+ head.position.x = sin(scan*PI*8.0)*0.58
+ head.position.z = lerpf(-0.42,0.48,scan)
+ head.position.y = 2.18 + sin(progress*PI)*0.07
+ rotor.rotation.y += delta * 0.12
+ arm.rotation.y = sin(progress*TAU)*0.55-0.3
+ display.text = "ETCH"
+ if laser.visible and spark_timer<=0:
+ spark_timer=0.08
+ _spark()
+ else:
+ head.position = head.position.lerp(Vector3(0,2.4,-0.3),minf(1.0,delta*5))
+ arm.rotation.y = lerpf(arm.rotation.y,-0.7,minf(1.0,delta*3))
+ display.text = "READY" if is_manual else "IDLE"
+ status_light.scale = Vector3.ONE * (1.0+sin(clock*5)*0.08 if active else 0.8)
+ for index in range(chip_objects.size()-1,-1,-1):
+ var item: Dictionary = chip_objects[index]
+ item.age += delta
+ var mesh: Node3D = item.node
+ mesh.position.z = 1.08+float(item.age)*1.3
+ mesh.position.y = 0.86+sin(minf(float(item.age)*3,PI))*0.1
+ if float(item.age)>1.25:
+ mesh.visible=false
+ chip_pool.append(mesh)
+ chip_objects.remove_at(index)
+ for index in range(sparks.size()-1,-1,-1):
+ var item: Dictionary = sparks[index]
+ item.age += delta
+ item.velocity += Vector3.DOWN*delta*3
+ item.node.position += item.velocity*delta
+ item.node.scale = Vector3.ONE * maxf(0.0,1.0-float(item.age)*2.5)
+ if item.age > 0.4:
+ item.node.visible=false
+ spark_pool.append(item.node)
+ sparks.remove_at(index)
+
+func _create_chip() -> Node3D:
+ var chip := Node3D.new()
+ add_child(chip)
+ chip.position = Vector3(0,0.9,1.1)
+ Geo.box(chip, Vector3.ZERO, Vector3(0.3,0.055,0.3), dark_mat)
+ Geo.box(chip, Vector3(0,0.035,0), Vector3(0.21,0.013,0.21), light_mat)
+ for side in [-1,1]:
+ Geo.box(chip,Vector3(side*0.17,0,0),Vector3(0.03,0.035,0.26),copper_mat)
+ Geo.batch(chip)
+ return chip
+
+func eject() -> void:
+ if chip_pool.is_empty():return
+ var chip: Node3D=chip_pool.pop_back()
+ chip.position=Vector3(0,0.9,1.1)
+ chip.visible=true
+ chip_objects.append({"node":chip,"age":0.0})
+
+func _spark() -> void:
+ if spark_pool.is_empty():
+ return
+ var particle: MeshInstance3D=spark_pool.pop_back()
+ particle.position=Vector3(head.position.x,1.35,head.position.z)
+ particle.scale=Vector3.ONE
+ particle.visible=true
+ var angle: float = clock*53.0
+ sparks.append({"node":particle,"age":0.0,"velocity":Vector3(cos(angle)*0.7,0.4,sin(angle)*0.7)})
diff --git a/godot/scripts/machine.gd.uid b/godot/scripts/machine.gd.uid
new file mode 100644
index 0000000..ba922bc
--- /dev/null
+++ b/godot/scripts/machine.gd.uid
@@ -0,0 +1 @@
+uid://bb4wjmcg6uyy6
diff --git a/godot/scripts/simulation.gd b/godot/scripts/simulation.gd
new file mode 100644
index 0000000..a40be0c
--- /dev/null
+++ b/godot/scripts/simulation.gd
@@ -0,0 +1,174 @@
+class_name SeedSimulation
+extends RefCounted
+
+## Economy is independent of rendering, timers, input, and audio. Both the
+## human and the utility controller call these same transaction methods.
+const CHIP_VALUE: int = 100
+const MANUAL_SECONDS: float = 0.9
+const WAFER_BATCH: int = 30
+const WAFER_COST: int = 600
+const MAX_FABS: int = 6
+const OVERCLOCK_COST: int = 2400
+const UPLINK_COST: int = 6000
+const SAVE_VERSION: int = 1
+
+var capital: int = 0
+var wafers: int = 60
+var chips: int = 0
+var fabs: int = 0
+var overclock: bool = false
+var controller: bool = false
+var linked: bool = false
+var sound_enabled: bool = true
+var manual_progress: float = -1.0
+var cycles: Array[float] = []
+var elapsed: float = 0.0
+var events: Array[Dictionary] = []
+
+func _init() -> void:
+ for i in MAX_FABS:
+ cycles.append(-1.0)
+
+func fab_cost() -> int:
+ return roundi(1200.0 * pow(1.45, fabs) / 100.0) * 100
+
+func cycle_seconds() -> float:
+ return 1.8 if overclock else 3.2
+
+func etch() -> bool:
+ if manual_progress >= 0.0 or wafers <= 0:
+ return false
+ wafers -= 1
+ manual_progress = 0.0
+ events.append({"type": "start", "machine": -1})
+ return true
+
+func buy_wafers() -> bool:
+ if capital < WAFER_COST:
+ return false
+ capital -= WAFER_COST
+ wafers += WAFER_BATCH
+ events.append({"type": "supply"})
+ return true
+
+func can_reclaim() -> bool:
+ if wafers > 0 or capital >= WAFER_COST or manual_progress >= 0:
+ return false
+ for cycle in cycles:
+ if cycle >= 0:return false
+ return true
+
+func reclaim() -> bool:
+ if not can_reclaim():return false
+ wafers += 3
+ events.append({"type": "reclaim"})
+ return true
+
+func build_fab() -> bool:
+ if fabs >= MAX_FABS or capital < fab_cost():
+ return false
+ capital -= fab_cost()
+ fabs += 1
+ events.append({"type": "build", "machine": fabs - 1})
+ return true
+
+func upgrade() -> bool:
+ if overclock or fabs < 2 or capital < OVERCLOCK_COST:
+ return false
+ capital -= OVERCLOCK_COST
+ overclock = true
+ events.append({"type": "upgrade"})
+ return true
+
+func toggle_controller() -> bool:
+ if fabs < 3:
+ return false
+ controller = not controller
+ events.append({"type": "controller"})
+ return true
+
+func uplink() -> bool:
+ if linked or fabs < MAX_FABS or capital < UPLINK_COST:
+ return false
+ capital -= UPLINK_COST
+ linked = true
+ events.append({"type": "uplink"})
+ return true
+
+func _finish(machine: int) -> void:
+ chips += 1
+ capital += CHIP_VALUE
+ events.append({"type": "chip", "machine": machine})
+
+func step(delta: float) -> void:
+ if delta <= 0.0 or not is_finite(delta):
+ return
+ elapsed += delta
+ # Automatic supply is explicit, opt-in, and pays the same price as a click.
+ if controller and wafers < maxi(6, fabs * 2):
+ if not buy_wafers():reclaim()
+ if manual_progress >= 0.0:
+ manual_progress += delta / MANUAL_SECONDS
+ if manual_progress >= 1.0:
+ manual_progress = -1.0
+ _finish(-1)
+ for i in fabs:
+ if cycles[i] < 0.0 and wafers > 0:
+ wafers -= 1
+ cycles[i] = 0.0
+ events.append({"type": "start", "machine": i})
+ if cycles[i] >= 0.0:
+ cycles[i] += delta / cycle_seconds()
+ if cycles[i] >= 1.0:
+ cycles[i] = -1.0
+ _finish(i)
+
+func take_events() -> Array[Dictionary]:
+ var result: Array[Dictionary] = events
+ events = []
+ return result
+
+func to_save() -> Dictionary:
+ return {"version": SAVE_VERSION, "capital": capital, "wafers": wafers,
+ "chips": chips, "fabs": fabs, "overclock": overclock,
+ "controller": controller, "linked": linked, "sound_enabled": sound_enabled,
+ "manual_progress": manual_progress, "cycles": cycles.duplicate(), "elapsed": elapsed}
+
+func restore(data: Dictionary) -> bool:
+ if data.get("version") != SAVE_VERSION:
+ return false
+ # Reject invalid files as a whole. No partial restore of a corrupted economy.
+ for field in ["capital", "wafers", "chips", "fabs"]:
+ var value: Variant = data.get(field)
+ if not (value is int or value is float):
+ return false
+ if not is_finite(float(value)) or float(value) < 0 or float(value) > 1e12:
+ return false
+ if int(data.fabs) > MAX_FABS:
+ return false
+ for field in ["overclock", "controller", "linked", "sound_enabled"]:
+ if data.has(field) and not data[field] is bool:return false
+ for field in ["elapsed", "manual_progress"]:
+ if data.has(field):
+ if not (data[field] is int or data[field] is float):return false
+ if not is_finite(float(data[field])):return false
+ var raw_cycles: Variant=data.get("cycles",[])
+ if not raw_cycles is Array or raw_cycles.size()>MAX_FABS:return false
+ for value in raw_cycles:
+ if not (value is int or value is float):return false
+ if not is_finite(float(value)):return false
+ capital = int(data.capital)
+ wafers = int(data.wafers)
+ chips = int(data.chips)
+ fabs = int(data.fabs)
+ overclock = bool(data.get("overclock", false)) and fabs >= 2
+ controller = bool(data.get("controller", false)) and fabs >= 3
+ linked = bool(data.get("linked", false)) and fabs == MAX_FABS
+ sound_enabled = bool(data.get("sound_enabled", true))
+ elapsed = maxf(0.0, float(data.get("elapsed", 0.0)))
+ manual_progress = clampf(float(data.get("manual_progress", -1.0)), -1.0, 0.999)
+ var saved_cycles: Variant = data.get("cycles", [])
+ if saved_cycles is Array:
+ for i in mini(saved_cycles.size(), MAX_FABS):
+ cycles[i] = clampf(float(saved_cycles[i]), -1.0, 0.999)
+ return true
diff --git a/godot/scripts/simulation.gd.uid b/godot/scripts/simulation.gd.uid
new file mode 100644
index 0000000..73ae3e9
--- /dev/null
+++ b/godot/scripts/simulation.gd.uid
@@ -0,0 +1 @@
+uid://dr0d6okuc3pke
diff --git a/godot/shaders/finish.gdshader b/godot/shaders/finish.gdshader
new file mode 100644
index 0000000..5677aa3
--- /dev/null
+++ b/godot/shaders/finish.gdshader
@@ -0,0 +1,15 @@
+shader_type canvas_item;
+render_mode unshaded;
+uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear_mipmap;
+uniform float bloom = 0.24;
+void fragment() {
+ vec3 color = textureLod(screen_texture, SCREEN_UV, 0.0).rgb;
+ vec3 blur = textureLod(screen_texture, SCREEN_UV, 3.3).rgb;
+ vec3 wide = textureLod(screen_texture, SCREEN_UV, 5.0).rgb;
+ color += max(blur - vec3(0.37), vec3(0.0)) * bloom;
+ color += max(wide - vec3(0.27), vec3(0.0)) * bloom*0.5;
+ vec2 p = SCREEN_UV - 0.5;
+ float vignette = 1.0 - dot(p,p)*0.65;
+ color *= vignette;
+ COLOR = vec4(color,1.0);
+}
diff --git a/godot/shaders/finish.gdshader.uid b/godot/shaders/finish.gdshader.uid
new file mode 100644
index 0000000..3ef164d
--- /dev/null
+++ b/godot/shaders/finish.gdshader.uid
@@ -0,0 +1 @@
+uid://cn2fx4cs5vtw7
diff --git a/godot/shaders/wafer.gdshader b/godot/shaders/wafer.gdshader
new file mode 100644
index 0000000..da9adfa
--- /dev/null
+++ b/godot/shaders/wafer.gdshader
@@ -0,0 +1,15 @@
+shader_type spatial;
+render_mode cull_disabled;
+uniform float activity = 0.0;
+uniform vec4 cold_color : source_color = vec4(0.05,0.25,0.30,1.0);
+void fragment() {
+ vec2 grid = abs(fract(UV * 15.0) - 0.5);
+ float lines = 1.0 - smoothstep(0.025, 0.05, min(grid.x, grid.y));
+ float rainbow = sin(UV.x*14.0+UV.y*7.0+TIME*0.3)*0.5+0.5;
+ vec3 surface = mix(cold_color.rgb, vec3(0.28,0.16,0.40), rainbow);
+ float scan = exp(-pow((UV.y - fract(TIME*0.7))*30.0, 2.0))*activity;
+ ALBEDO = surface + lines*vec3(0.05,0.25,0.25);
+ METALLIC = 0.75;
+ ROUGHNESS = 0.25;
+ EMISSION = lines*vec3(0.01,0.08,0.10) + scan*vec3(0.25,0.95,0.72)*0.6;
+}
diff --git a/godot/shaders/wafer.gdshader.uid b/godot/shaders/wafer.gdshader.uid
new file mode 100644
index 0000000..024f765
--- /dev/null
+++ b/godot/shaders/wafer.gdshader.uid
@@ -0,0 +1 @@
+uid://dyrcvyiqecb20
diff --git a/godot/tests/test_simulation.gd b/godot/tests/test_simulation.gd
new file mode 100644
index 0000000..404b803
--- /dev/null
+++ b/godot/tests/test_simulation.gd
@@ -0,0 +1,78 @@
+extends SceneTree
+const Simulation=preload("res://scripts/simulation.gd")
+var failures: Array[String]=[]
+var checks: int=0
+
+func check(condition: bool, message: String) -> void:
+ checks+=1
+ if not condition:
+ failures.append(message)
+ push_error(message)
+
+func advance(sim: SeedSimulation, seconds: float) -> void:
+ for i in ceili(seconds*60):sim.step(1.0/60.0)
+
+func _initialize() -> void:
+ var sim:=Simulation.new()
+ check(not sim.build_fab(),"Cannot buy an unfunded machine")
+ check(sim.etch(),"Fresh run can etch")
+ check(sim.wafers==59 and sim.chips==0,"Starting a cycle consumes one wafer but grants no output early")
+ check(not sim.etch(),"Manual cycle cannot be double queued")
+ advance(sim,1.0)
+ check(sim.chips==1 and sim.capital==100,"Completed fabrication sells one chip for $100")
+ for i in 11:
+ sim.etch()
+ advance(sim,1)
+ check(sim.capital==1200,"Twelve manual chips finance the first machine")
+ check(sim.build_fab() and sim.fabs==1 and sim.capital==0,"The first fab debits the displayed price")
+ var before: int=sim.chips
+ advance(sim,3.5)
+ check(sim.chips>before,"An autonomous machine produces without manual input")
+ var restored:=Simulation.new()
+ check(restored.restore(sim.to_save()),"Save round trip is accepted")
+ check(restored.to_save()==sim.to_save(),"Save preserves in-flight wafers and machine cycles")
+ var bad: Dictionary=sim.to_save()
+ bad.cycles=["broken"]
+ check(not restored.restore(bad),"Corrupted cycle data is rejected")
+ check(restored.to_save()==sim.to_save(),"Invalid restore leaves the entire current run unchanged")
+ bad=sim.to_save()
+ bad.version=999
+ check(not restored.restore(bad),"Unknown save versions are rejected")
+ var stranded:=Simulation.new()
+ stranded.wafers=0
+ stranded.capital=0
+ check(stranded.reclaim() and stranded.wafers==3,"A bankrupt factory can recover without resetting")
+ check(not stranded.reclaim(),"Emergency recovery cannot be stockpiled")
+ check(not sim.upgrade(),"Overclock is gated to two fabs")
+ check(not sim.toggle_controller(),"Automation is gated to three fabs")
+ check(not sim.uplink(),"Uplink requires the full factory")
+ # A complete headless playthrough follows the same transactions as the UI.
+ # No privileged resources, time multiplier, free purchases, or state jumps.
+ var run:=Simulation.new()
+ var finished_at: float=0
+ for i in range(60*600):
+ if run.wafers=2 and not run.overclock:run.upgrade()
+ if run.fabs<6:run.build_fab()
+ if run.fabs>=3 and not run.controller:run.toggle_controller()
+ run.etch()
+ run.step(1.0/60)
+ run.take_events()
+ if run.uplink():
+ finished_at=run.elapsed
+ break
+ check(run.linked,"The prototype can be completed from a fresh run within ten minutes")
+ check(run.fabs==6 and run.overclock and run.controller,"A completed run uses the factory's expansion, research, and controller")
+ check(run.capital>=0 and run.wafers>=0,"No transaction creates negative resources")
+ var previous: int=run.chips
+ advance(run,10)
+ check(run.chips>previous,"Production continues after the district connects")
+ var saved: Dictionary=run.to_save()
+ var resumed:=Simulation.new()
+ resumed.restore(saved)
+ advance(run,10)
+ advance(resumed,10)
+ check(run.to_save()==resumed.to_save(),"Resumed automation behaves identically to uninterrupted play")
+ print("The Seed: %d checks, %d failures. Full run: %.1fs, %d chips."%[checks,failures.size(),finished_at,run.chips])
+ quit(0 if failures.is_empty() else 1)
diff --git a/godot/tests/test_simulation.gd.uid b/godot/tests/test_simulation.gd.uid
new file mode 100644
index 0000000..50cbd8b
--- /dev/null
+++ b/godot/tests/test_simulation.gd.uid
@@ -0,0 +1 @@
+uid://dwu2l1b1r5y6c
diff --git a/godot/web/boot.js b/godot/web/boot.js
new file mode 100644
index 0000000..eaa718e
--- /dev/null
+++ b/godot/web/boot.js
@@ -0,0 +1,37 @@
+'use strict';
+const canvas = document.getElementById('canvas');
+const config = JSON.parse(document.getElementById('godot-config').textContent);
+const loading = document.getElementById('loading');
+const status = document.getElementById('status');
+const retry = document.getElementById('retry');
+const controls = document.getElementById('controls');
+if (location.port === '4180' && ['localhost', '127.0.0.1'].includes(location.hostname)) {
+ document.getElementById('versions-link').href = 'http://localhost:3000/';
+}
+function failure(error) {
+ status.textContent = 'THE FACTORY COULD NOT START';
+ document.getElementById('error').textContent = String(error?.message || error);
+ loading.style.display = 'grid';
+ retry.style.display = 'inline-block';
+ console.error(error);
+}
+retry.addEventListener('click', () => location.reload());
+document.getElementById('help-button').addEventListener('click', () => controls.showModal());
+document.getElementById('close-help').addEventListener('click', () => controls.close());
+controls.addEventListener('close', () => canvas.focus());
+if (typeof Engine === 'undefined') {
+ failure('The engine download failed. Check your connection, then try again.');
+} else {
+ const missing = Engine.getMissingFeatures({ threads: false });
+ if (missing.length) {
+ failure('This game needs WebGL 2 and WebAssembly. Try a current Chrome, Edge, or Firefox browser with graphics acceleration enabled.\n' + missing.join('\n'));
+ } else {
+ const engine = new Engine(config);
+ engine.startGame({ canvas, onProgress(current, total) {
+ if (total > 0) {
+ document.getElementById('bar').style.width = `${current / total * 100}%`;
+ status.textContent = current < total ? `DELIVERING THE MACHINERY · ${Math.round(current / total * 100)}%` : 'WARMING UP THE FABRICATION FLOOR';
+ }
+ } }).then(() => { loading.remove(); canvas.focus(); }, failure);
+ }
+}
diff --git a/godot/web/shell.html b/godot/web/shell.html
new file mode 100644
index 0000000..7a970a6
--- /dev/null
+++ b/godot/web/shell.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+Universal AI — The Seed
+
+$GODOT_HEAD_INCLUDE
+
+
+
+
+
+Your browser needs WebGL 2 to run this game.
+◈
UNIVERSAL AI / CHAPTER ZERO
The Seed. A quiet room. A silicon wafer. An objective that doesn't know when to stop.
PREPARING THE FABRICATION FLOOR
Try again
+CONTROLS [?]
+The first machine. ← All versions
Etch chips and sell them for $100 each. Install your first fab at $1,200, then grow to six machines and connect the district.
Space — etch a chip; hold to repeatB — install the next fabR — buy 30 wafers for $600O / A / U — overclock / supply controller / uplinkDrag / scroll — orbit / zoomC / F / M / Home — close-up / cinema / sound / reset cameraProgress saves in this browser. Production pauses while the tab is suspended. This is a standalone prototype; earlier Universal AI saves are separate.
Back to the factory
+This game needs JavaScript and WebGL 2 enabled.
+
diff --git a/index.html b/index.html
index 65ba97f..618bbca 100644
--- a/index.html
+++ b/index.html
@@ -1,117 +1,63 @@
-
-
-
-
-
-
-
-
-
- Universal AI — an idle game about optimization
-
-
-
-
-
-
-
-
-
-
-
-
-
-
UNIVERSAL AI
-
Loading…
-
- The application bundle did not load.
- Nothing is wrong with your browser — this page was served without its
- compiled assets. Reloading will not help. Please report it at
- github.com/TechLuddite/Universal-AI/issues .
-
-
-
-
-
+
+
+
+
+ Universal AI — Choose your experiment
+
+
+
+
+
+
+ Skip to the games
+
+
+
+
+ TECHLUDDITE’S EXPERIMENT IN ENOUGH
+ One obsession.Two machines. Start with a chip. See what “make more” becomes.Two ways into the same dangerous idea.
+
+
+
+ 01 / THE SYSTEM FULL THREE-PHASE GAME
+
+
+
CHIPS → PLANET → SWARM
+
+ THE ORIGINAL, EVOLVED
Universal AI A quiet dashboard with an enormous appetite. Build a chip business, direct an autonomous Overseer, and follow optimization from the market to the stars.
+
Experience Idle strategy · three phases · alignment-driven endings
Built with React, TypeScript & Canvas · optional WebLLM
Lineage Gemini foundation → Claude’s systems work → Codex’s interface pass
+
Enter Universal AI ↗
+
+
+
+
+ 02 / THE PLACE EXPERIMENTAL PROTOTYPE
+
+
+
ONE WAFER. SIX FABS. A LARGER AMBITION.
+
+ A NEW DIMENSION
The Seed. Step inside the first fabrication room. Watch etch heads scan, chips leave the line, and six autonomous machines turn a small beginning into a district connection.
+
Experience 3D factory · opening chapter · independent save
Built with Godot & GDScript · WebAssembly / WebGL 2
Lineage TechLuddite’s “go Godot” direction → Codex’s implementation
+
Performance investigation open. Severe slowdowns have been reported on the development laptop. Testing on another machine is still pending.
+
Enter the experimental factory ↗
+
+
+
+
+ Both run in your browser. Separate games, separate saves. Neither game starts until you choose it.
+
+ HUMAN DIRECTION. SHARED AUTHORSHIP.
Built in conversation. These games grew through several tools and several rounds of work. The credits follow the contributions, not just the name on the latest interface.
+
TechLuddite PROJECT CREATOR Direction, prompts, product decisions, playtesting, and the invitation to take the project into Godot.
Gemini GOOGLE AI STUDIO The initial generated browser-game foundation and early concepts, including the Overseer and alignment axis.
Claude SIMULATION & SYSTEMS Audited and repaired the original game; developed persistence, Overseer engines, progression, alignment consequences, tests, and Pages deployment work.
Codex INTERFACE & GODOT The recent React interface and observatory visuals; The Seed’s Godot scene, prototype economy, animation, audio, and web export; this shared entrance.
+
+ THE SPARK Inspired by Frank Lantz’s Universal Paperclips —the small beginning behind a very large obsession. These are independent experiments, not an official sequel.
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
index f6f3bae..5bcc975 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,6 +16,7 @@
"react-dom": "^19.0.1"
},
"devDependencies": {
+ "@playwright/test": "^1.63.0",
"@types/node": "^22.14.0",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
@@ -774,6 +775,22 @@
"node": "^22.20 || ^24.12 || >=25"
}
},
+ "node_modules/@playwright/test": {
+ "version": "1.63.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz",
+ "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright": "1.63.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-rc.3",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
@@ -2263,6 +2280,35 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/playwright": {
+ "version": "1.63.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz",
+ "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.63.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.63.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz",
+ "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/postcss": {
"version": "8.5.25",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz",
@@ -2480,511 +2526,6 @@
"license": "0BSD",
"optional": true
},
- "node_modules/tsx": {
- "version": "4.23.1",
- "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz",
- "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==",
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "esbuild": "~0.28.0"
- },
- "bin": {
- "tsx": "dist/cli.mjs"
- },
- "engines": {
- "node": ">=18.0.0"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
- "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
- "cpu": [
- "ppc64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/android-arm": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
- "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/android-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
- "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/android-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
- "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
- "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/darwin-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
- "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
- "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
- "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/linux-arm": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
- "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/linux-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
- "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/linux-ia32": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
- "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/linux-loong64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
- "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
- "cpu": [
- "loong64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
- "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
- "cpu": [
- "mips64el"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
- "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
- "cpu": [
- "ppc64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
- "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
- "cpu": [
- "riscv64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/linux-s390x": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
- "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
- "cpu": [
- "s390x"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/linux-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
- "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
- "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
- "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
- "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
- "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
- "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/sunos-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
- "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/win32-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
- "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/win32-ia32": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
- "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/win32-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
- "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/esbuild": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
- "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.28.1",
- "@esbuild/android-arm": "0.28.1",
- "@esbuild/android-arm64": "0.28.1",
- "@esbuild/android-x64": "0.28.1",
- "@esbuild/darwin-arm64": "0.28.1",
- "@esbuild/darwin-x64": "0.28.1",
- "@esbuild/freebsd-arm64": "0.28.1",
- "@esbuild/freebsd-x64": "0.28.1",
- "@esbuild/linux-arm": "0.28.1",
- "@esbuild/linux-arm64": "0.28.1",
- "@esbuild/linux-ia32": "0.28.1",
- "@esbuild/linux-loong64": "0.28.1",
- "@esbuild/linux-mips64el": "0.28.1",
- "@esbuild/linux-ppc64": "0.28.1",
- "@esbuild/linux-riscv64": "0.28.1",
- "@esbuild/linux-s390x": "0.28.1",
- "@esbuild/linux-x64": "0.28.1",
- "@esbuild/netbsd-arm64": "0.28.1",
- "@esbuild/netbsd-x64": "0.28.1",
- "@esbuild/openbsd-arm64": "0.28.1",
- "@esbuild/openbsd-x64": "0.28.1",
- "@esbuild/openharmony-arm64": "0.28.1",
- "@esbuild/sunos-x64": "0.28.1",
- "@esbuild/win32-arm64": "0.28.1",
- "@esbuild/win32-ia32": "0.28.1",
- "@esbuild/win32-x64": "0.28.1"
- }
- },
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
diff --git a/package.json b/package.json
index 631205a..21d3a99 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,15 @@
"build": "vite build",
"preview": "vite preview --port=4173 --host=0.0.0.0",
"lint": "tsc --noEmit",
- "test": "vitest run"
+ "test": "vitest run",
+ "test:browser": "npm run build && playwright test",
+ "godot:setup": "python scripts/godot.py setup",
+ "godot:build": "python scripts/godot.py build",
+ "godot:test": "python scripts/godot.py test",
+ "godot:serve": "python scripts/godot.py serve",
+ "godot:stage": "python scripts/godot.py stage",
+ "godot:editor": "python scripts/godot.py editor",
+ "godot:test:browser": "npm run godot:build && playwright test --config playwright.godot.config.ts"
},
"dependencies": {
"@mlc-ai/web-llm": "^0.2.84",
@@ -19,6 +27,7 @@
"react-dom": "^19.0.1"
},
"devDependencies": {
+ "@playwright/test": "^1.63.0",
"@types/node": "^22.14.0",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
diff --git a/playwright.config.ts b/playwright.config.ts
new file mode 100644
index 0000000..4bed089
--- /dev/null
+++ b/playwright.config.ts
@@ -0,0 +1,29 @@
+import { defineConfig } from '@playwright/test';
+
+export default defineConfig({
+ testDir: './tests',
+ testMatch: '**/*.browser.ts',
+ fullyParallel: true,
+ workers: 2,
+ use: {
+ baseURL: 'http://127.0.0.1:4173',
+ viewport: { width: 1440, height: 1000 },
+ launchOptions: {
+ executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH,
+ },
+ screenshot: 'only-on-failure',
+ trace: 'retain-on-failure',
+ },
+ webServer: [
+ {
+ command: 'npm run preview -- --host=127.0.0.1',
+ url: 'http://127.0.0.1:4173',
+ reuseExistingServer: !process.env.CI,
+ },
+ {
+ command: 'npm run dev -- --host=127.0.0.1',
+ url: 'http://127.0.0.1:3000',
+ reuseExistingServer: !process.env.CI,
+ },
+ ],
+});
diff --git a/playwright.godot.config.ts b/playwright.godot.config.ts
new file mode 100644
index 0000000..6f9d22c
--- /dev/null
+++ b/playwright.godot.config.ts
@@ -0,0 +1,34 @@
+import { defineConfig } from '@playwright/test';
+
+export default defineConfig({
+ testDir: './tests',
+ outputDir: './build/test-results-godot',
+ testMatch: '**/godot.e2e.ts',
+ timeout: 120_000,
+ workers: 1,
+ use: {
+ baseURL: 'http://127.0.0.1:4180',
+ headless: process.env.GODOT_BROWSER_HEADED !== '1',
+ viewport: { width: 1280, height: 800 },
+ screenshot: 'only-on-failure',
+ trace: 'retain-on-failure',
+ },
+ projects: [
+ {
+ name: 'chromium',
+ use: {
+ browserName: 'chromium',
+ launchOptions: {
+ executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH,
+ args: ['--enable-unsafe-swiftshader', ...(process.env.GODOT_BROWSER_ANGLE ? ['--use-gl=angle', `--use-angle=${process.env.GODOT_BROWSER_ANGLE}`, '--ignore-gpu-blocklist'] : [])],
+ },
+ },
+ },
+ { name: 'firefox', use: { browserName: 'firefox', launchOptions: { firefoxUserPrefs: { 'webgl.force-enabled': true } } } },
+ ],
+ webServer: {
+ command: 'npm run godot:serve',
+ url: 'http://127.0.0.1:4180',
+ reuseExistingServer: !process.env.CI,
+ },
+});
diff --git a/scripts/godot.py b/scripts/godot.py
new file mode 100644
index 0000000..9566213
--- /dev/null
+++ b/scripts/godot.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python3
+"""Pinned, reproducible Godot prototype tools. No npm runtime dependencies."""
+import argparse
+import hashlib
+import http.server
+import os
+from pathlib import Path
+import platform
+import shutil
+import subprocess
+import sys
+import urllib.request
+import zipfile
+
+ROOT = Path(__file__).resolve().parents[1]
+PROJECT = ROOT / 'godot'
+OUTPUT = ROOT / 'build' / 'godot'
+VERSION = '4.7.2'
+CACHE = Path.home() / '.cache' / 'universal-ai' / 'godot' / VERSION
+TEMPLATES = Path(os.environ.get('XDG_DATA_HOME', str(Path.home() / '.local/share'))) / 'godot/export_templates' / f'{VERSION}.stable'
+BASE = f'https://github.com/godotengine/godot/releases/download/{VERSION}-stable/'
+EDITOR = f'Godot_v{VERSION}-stable_linux.x86_64'
+DIGESTS = {
+ f'{EDITOR}.zip': 'cadd3204e728a35d3f13adb7fd0d7902636b79f6b95c40c265eb73b6c35329e4',
+ f'Godot_v{VERSION}-stable_export_templates.tpz': 'f298490b8d44d934be425a5a65a51bf15f422428b229a06a6e11d9ffea248011',
+}
+
+
+def download(name):
+ CACHE.mkdir(parents=True, exist_ok=True)
+ archive = CACHE / name
+ if not archive.exists():
+ print(f'Downloading {name} from the official Godot release…', flush=True)
+ temp = archive.with_suffix('.part')
+ urllib.request.urlretrieve(BASE + name, temp)
+ temp.replace(archive)
+ with archive.open('rb') as stream:
+ digest = hashlib.file_digest(stream, 'sha256').hexdigest()
+ if digest != DIGESTS[name]:
+ raise RuntimeError(f'Checksum mismatch: {archive}. Remove this download and retry.')
+ return archive
+
+
+def executable():
+ supplied = os.environ.get('GODOT_BIN') or shutil.which('godot') or shutil.which('godot4')
+ if supplied:
+ return supplied
+ binary = CACHE / EDITOR
+ if not binary.exists():
+ if platform.system() != 'Linux' or platform.machine() not in ('x86_64', 'AMD64'):
+ raise RuntimeError('Install Godot 4.7.2 and set GODOT_BIN to its executable on this platform.')
+ with zipfile.ZipFile(download(f'{EDITOR}.zip')) as archive:
+ binary.write_bytes(archive.read(EDITOR))
+ binary.chmod(0o755)
+ return str(binary)
+
+
+def setup_templates():
+ required = ['web_nothreads_debug.zip', 'web_nothreads_release.zip']
+ if all((TEMPLATES / file).exists() for file in required):
+ return
+ archive = download(f'Godot_v{VERSION}-stable_export_templates.tpz')
+ TEMPLATES.mkdir(parents=True, exist_ok=True)
+ with zipfile.ZipFile(archive) as bundle:
+ for file in [*required, 'version.txt']:
+ (TEMPLATES / file).write_bytes(bundle.read(f'templates/{file}'))
+ # The upstream bundle contains every platform. Only keep the web templates.
+ archive.unlink()
+
+
+def engine(*args):
+ result = subprocess.run([executable(), '--headless', '--path', str(PROJECT), *args], text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ print(result.stdout, end='')
+ # Godot's editor can exit zero despite a GDScript parser error.
+ if result.returncode or 'SCRIPT ERROR:' in result.stdout or '\nERROR:' in result.stdout:
+ raise RuntimeError('Godot reported an import, script, or export error.')
+
+
+def build():
+ setup_templates()
+ OUTPUT.mkdir(parents=True, exist_ok=True)
+ engine('--editor', '--import', '--quit')
+ engine('--export-release', 'Web', str(OUTPUT / 'index.html'))
+ shutil.copy2(PROJECT / 'web/boot.js', OUTPUT / 'boot.js')
+ shutil.copytree(PROJECT / 'licenses', OUTPUT / 'licenses', dirs_exist_ok=True)
+ print(f'Browser build: {OUTPUT}')
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('command', choices=['setup', 'build', 'test', 'serve', 'stage', 'editor'])
+ parser.add_argument('--port', type=int, default=4180)
+ args = parser.parse_args()
+ if args.command == 'setup':
+ print(executable())
+ setup_templates()
+ elif args.command == 'build':
+ build()
+ elif args.command == 'test':
+ engine('--editor', '--import', '--quit')
+ engine('--script', 'res://tests/test_simulation.gd')
+ engine('--quit-after', '10', '--', '--test')
+ elif args.command == 'editor':
+ subprocess.run([executable(), '--editor', '--path', str(PROJECT)], check=True)
+ elif args.command == 'stage':
+ if not (OUTPUT / 'index.html').exists():
+ build()
+ destination = ROOT / 'dist' / 'seed'
+ shutil.copytree(OUTPUT, destination, dirs_exist_ok=True)
+ print(f'GitHub Pages artifact: {destination}')
+ elif args.command == 'serve':
+ if not (OUTPUT / 'index.html').exists():
+ build()
+ class Handler(http.server.SimpleHTTPRequestHandler):
+ def __init__(self, *a, **kw):
+ super().__init__(*a, directory=str(OUTPUT), **kw)
+ print(f'The Seed: http://127.0.0.1:{args.port}', flush=True)
+ http.server.ThreadingHTTPServer(('127.0.0.1', args.port), Handler).serve_forever()
+
+
+if __name__ == '__main__':
+ try:
+ main()
+ except (RuntimeError, subprocess.CalledProcessError) as error:
+ sys.exit(str(error))
diff --git a/scripts/verify_pages.py b/scripts/verify_pages.py
new file mode 100644
index 0000000..ccbe66e
--- /dev/null
+++ b/scripts/verify_pages.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+"""Record or verify the three documents and Godot data in one Pages release."""
+import argparse
+import hashlib
+import json
+from pathlib import Path
+import time
+import urllib.request
+
+FILES = ('index.html', 'classic/index.html', 'seed/index.html', 'seed/index.pck', 'seed/boot.js')
+
+
+def sha(data):
+ return hashlib.sha256(data).hexdigest()
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('command', choices=['record', 'check'])
+ parser.add_argument('--commit', required=True)
+ parser.add_argument('--url')
+ args = parser.parse_args()
+ if args.command == 'record':
+ release = {'commit': args.commit, 'files': {name: sha((Path('dist') / name).read_bytes()) for name in FILES}}
+ Path('dist/release.json').write_text(json.dumps(release, indent=2) + '\n')
+ print(f'Recorded chooser, classic game, and Godot export for {args.commit}')
+ return
+ if not args.url:
+ parser.error('--url is required when checking a deployment')
+ base = args.url.rstrip('/') + '/'
+ def fetch(name, attempt):
+ request = urllib.request.Request(base + name + f'?release={args.commit}&attempt={attempt}', headers={'Cache-Control': 'no-cache'})
+ with urllib.request.urlopen(request, timeout=30) as response:
+ return response.read()
+ for attempt in range(1, 13):
+ try:
+ release = json.loads(fetch('release.json', attempt))
+ if release['commit'] != args.commit:
+ raise ValueError('CDN is still serving a different release')
+ for name in FILES:
+ if sha(fetch(name, attempt)) != release['files'][name]:
+ raise ValueError(f'{name} does not match the release manifest')
+ # The engine binary is large. Verify availability without running it
+ # or downloading it in full: WASM binaries begin with \0asm.
+ with urllib.request.urlopen(base + 'seed/index.wasm', timeout=30) as response:
+ if response.read(4) != b'\x00asm':
+ raise ValueError('Godot WebAssembly file is missing or invalid')
+ print(f'Verified {base}: chooser, classic game, Godot page/data, and WASM availability at {args.commit}')
+ return
+ except (OSError, ValueError, KeyError) as error:
+ print(f'Attempt {attempt}: {error}', flush=True)
+ if attempt < 12:
+ time.sleep(20)
+ raise SystemExit('Pages did not serve the complete expected release')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/src/App.tsx b/src/App.tsx
index 1e2bb4f..280d718 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -2,12 +2,16 @@ import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { GameState, AILogEntry, Upgrade, ProbeAllocation } from './types';
import { INITIAL_UPGRADES } from './data/upgrades';
import { PixelHeader } from './components/PixelHeader';
-import { NpuCanvasComponent } from './components/NpuCanvasComponent';
+import { WorldStage, TelemetryRibbon } from './components/WorldStage';
+import { SystemSignal } from './components/SystemSignal';
import { DirectControlPanel } from './components/DirectControlPanel';
import { OverseerPanel } from './components/OverseerPanel';
import { UpgradesPanel } from './components/UpgradesPanel';
import { StatsPanel } from './components/StatsPanel';
-import { OfflineReportCard, OfflineReport } from './components/OfflineReportCard';
+import {
+ OfflineReportCard,
+ OfflineReport,
+} from './components/OfflineReportCard';
import { DecisionModal } from './components/DecisionModal';
import { DevSupportModal } from './components/DevSupportModal';
import { EdgeWarningModal } from './components/EdgeWarningModal';
@@ -55,35 +59,56 @@ const PHASE_DEMOLITION_MS = 2200;
/** The frame only ever widens. Scope is one-way, and the layout should say so. */
const FRAME_WIDTH: Record<1 | 2 | 3, string> = {
- 1: '64rem',
- 2: '80rem',
+ 1: '86rem',
+ 2: '98rem',
3: '110rem',
};
export default function App() {
const [showVictoryModal, setShowVictoryModal] = useState(false);
- const [victoryModalShownOnce, setVictoryModalShownOnce] = useState(false);
-
- const [state, setState] = useState(createInitialState);
+ const [victoryModalShownOnce, setVictoryModalShownOnce] =
+ useState(false);
+
+ // Restore before mounting effects. Restoring in an effect let StrictMode's
+ // autosave cleanup overwrite a real save with the fresh initial state.
+ const [restored] = useState(load);
+ const [state, setState] = useState(
+ () => restored?.state ?? createInitialState(),
+ );
- const [upgrades, setUpgrades] = useState(INITIAL_UPGRADES);
+ const [upgrades, setUpgrades] = useState(
+ () => restored?.upgrades ?? INITIAL_UPGRADES,
+ );
const [showDevSupport, setShowDevSupport] = useState(false);
const [demolishing, setDemolishing] = useState<1 | 2 | null>(null);
- const renderedPhase = useRef<1 | 2 | 3>(1);
+ const renderedPhase = useRef<1 | 2 | 3>(state.phase);
const [isAiThinking, setIsAiThinking] = useState(false);
- const [offlineReport, setOfflineReport] = useState(null);
+ const [offlineReport, setOfflineReport] = useState(
+ () =>
+ restored && restored.offlineNpus > 1
+ ? {
+ npus: restored.offlineNpus,
+ ms: restored.offlineMs,
+ capped: restored.offlineMs >= MAX_OFFLINE_MS,
+ }
+ : null,
+ );
// The two engines. Both are real: a deterministic scorer, and a language
// model running on the player's own GPU.
- const [engineStatus, setEngineStatus] = useState({ kind: 'idle' });
+ const [engineStatus, setEngineStatus] = useState({
+ kind: 'idle',
+ });
const engines = useMemo(
() => ({
utility: new UtilityOverseer(),
webllm: new WebLlmOverseer(setEngineStatus),
}),
- []
+ [],
+ );
+ const [lastDecision, setLastDecision] = useState(
+ null,
);
- const [lastDecision, setLastDecision] = useState(null);
const [showModelDownload, setShowModelDownload] = useState(false);
// What the engines can currently buy. Memoized on `upgrades` — which only
@@ -91,7 +116,7 @@ export default function App() {
// array identity every 100ms tick.
const availableUpgrades = useMemo(
() => upgrades.filter((u) => u.unlocked && !u.purchased),
- [upgrades]
+ [upgrades],
);
// Sync sound mute setting with audio engine
@@ -99,26 +124,6 @@ export default function App() {
audio.enabled = state.soundEnabled;
}, [state.soundEnabled]);
- // Restore the previous run, including progress made while the tab was closed.
- useEffect(() => {
- const restored = load();
- if (!restored) return;
-
- setState(restored.state);
- setUpgrades(restored.upgrades);
- // Loading into Phase 3 is not the same event as arriving there. Don't
- // demolish panels the player never had open.
- renderedPhase.current = restored.state.phase;
-
- if (restored.offlineNpus > 1) {
- setOfflineReport({
- npus: restored.offlineNpus,
- ms: restored.offlineMs,
- capped: restored.offlineMs >= MAX_OFFLINE_MS,
- });
- }
- }, []);
-
// Main game tick. All simulation lives in the pure reducer in game/tick.ts.
useEffect(() => {
const interval = setInterval(() => {
@@ -174,11 +179,20 @@ export default function App() {
if (u.reqPhase && state.phase >= u.reqPhase) unlock = true;
// The thresholds above are OR'd; a prerequisite upgrade is an AND.
// Deploying hypno-drones you never built is not a milestone.
- if (u.reqUpgradeId && !state.purchasedUpgradeIds.includes(u.reqUpgradeId)) unlock = false;
+ if (
+ u.reqUpgradeId &&
+ !state.purchasedUpgradeIds.includes(u.reqUpgradeId)
+ )
+ unlock = false;
return unlock ? { ...u, unlocked: true } : u;
- })
+ }),
);
- }, [state.totalNpusCreated, state.maxTrust, state.phase, state.purchasedUpgradeIds]);
+ }, [
+ state.totalNpusCreated,
+ state.maxTrust,
+ state.phase,
+ state.purchasedUpgradeIds,
+ ]);
// Latest state, for readers that must not go stale. The Overseer's callback
// previously listed only a handful of fields in its dependency array while
@@ -191,7 +205,10 @@ export default function App() {
// Autosave. An idle game that loses everything when the tab closes isn't one.
useEffect(() => {
- const interval = setInterval(() => save(stateRef.current, upgradesRef.current), 5000);
+ const interval = setInterval(
+ () => save(stateRef.current, upgradesRef.current),
+ 5000,
+ );
const flush = () => save(stateRef.current, upgradesRef.current);
window.addEventListener('beforeunload', flush);
return () => {
@@ -211,7 +228,9 @@ export default function App() {
const decision = await engine.decide({
state: current,
directives: current.directives,
- availableUpgrades: upgradesRef.current.filter((u) => u.unlocked && !u.purchased),
+ availableUpgrades: upgradesRef.current.filter(
+ (u) => u.unlocked && !u.purchased,
+ ),
// Randomness is passed in, not reached for, so `game/` stays pure.
rng: Math.random,
});
@@ -231,8 +250,8 @@ export default function App() {
type: decision.drift
? 'warning'
: chosen.action === 'MAKE_DECISION'
- ? 'decision'
- : 'thought',
+ ? 'decision'
+ : 'thought',
// The engine that actually decided, which is not necessarily the one
// selected — a fallback must never be labelled as the engine it replaced.
engine: decision.engine,
@@ -257,7 +276,8 @@ export default function App() {
next = buyMarketing(next);
break;
case 'ADJUST_PRICE':
- if (chosen.newPrice !== undefined) next = setPrice(next, chosen.newPrice);
+ if (chosen.newPrice !== undefined)
+ next = setPrice(next, chosen.newPrice);
break;
case 'BUY_HARVESTER_DRONE':
next = buyHarvesterDrone(next);
@@ -277,7 +297,13 @@ export default function App() {
speed: 3,
nav: 3,
replication: 2,
- hazardCombat: Math.min(8, Math.max(4, Math.floor(Math.log10(next.driftersCount + 1) * 2) + 3)),
+ hazardCombat: Math.min(
+ 8,
+ Math.max(
+ 4,
+ Math.floor(Math.log10(next.driftersCount + 1) * 2) + 3,
+ ),
+ ),
factory: 1,
harvester: 1,
silicon: 1,
@@ -291,17 +317,19 @@ export default function App() {
factory: 2,
harvester: 2,
silicon: 2,
- }
+ },
);
break;
case 'BUY_UPGRADE': {
const up = upgradesRef.current.find(
- (u) => u.id === chosen.upgradeId && u.unlocked && !u.purchased
+ (u) => u.id === chosen.upgradeId && u.unlocked && !u.purchased,
);
if (up) {
next = buyUpgrade(next, up);
setUpgrades((list) =>
- list.map((item) => (item.id === up.id ? { ...item, purchased: true } : item))
+ list.map((item) =>
+ item.id === up.id ? { ...item, purchased: true } : item,
+ ),
);
}
break;
@@ -319,7 +347,10 @@ export default function App() {
: changeMemory(next, 1);
break;
case 'MAKE_DECISION':
- next = resolveDecision(next, chosen.decisionChoiceIndex === 1 ? 1 : 0);
+ next = resolveDecision(
+ next,
+ chosen.decisionChoiceIndex === 1 ? 1 : 0,
+ );
break;
case 'IDLE':
break;
@@ -354,26 +385,35 @@ export default function App() {
}, state.directives.autoIntervalMs);
return () => clearInterval(interval);
- }, [state.mode, state.directives.autoLoopActive, state.directives.autoIntervalMs]);
+ }, [
+ state.mode,
+ state.directives.autoLoopActive,
+ state.directives.autoIntervalMs,
+ ]);
// Direct Control Handlers
// Direct control handlers. All of these delegate to the shared pure actions in
// game/actions.ts, which the Overseer dispatcher above uses too.
const handleMakeNpu = () => setState(makeNpu);
const handleBuySilicon = () => setState((prev) => buySilicon(prev));
- const handleAdjustPrice = (delta: number) => setState((prev) => adjustPrice(prev, delta));
+ const handleAdjustPrice = (delta: number) =>
+ setState((prev) => adjustPrice(prev, delta));
const handleBuyMarketing = () => setState(buyMarketing);
const handleBuyFab = () => setState(buyFab);
const handleBuyMegaFab = () => setState(buyMegaFab);
const handleBuyHarvesterDrone = () => setState(buyHarvesterDrone);
const handleBuySiliconDrone = () => setState(buySiliconDrone);
const handleLaunchProbe = () => setState(launchProbe);
- const handleChangeProcessor = (delta: number) => setState((prev) => changeProcessor(prev, delta));
- const handleChangeMemory = (delta: number) => setState((prev) => changeMemory(prev, delta));
+ const handleChangeProcessor = (delta: number) =>
+ setState((prev) => changeProcessor(prev, delta));
+ const handleChangeMemory = (delta: number) =>
+ setState((prev) => changeMemory(prev, delta));
const handleQuantumPulse = () => setState(quantumPulse);
- const handleChangeProbeAllocation = (category: keyof ProbeAllocation, delta: number) =>
- setState((prev) => changeProbeAllocation(prev, category, delta));
+ const handleChangeProbeAllocation = (
+ category: keyof ProbeAllocation,
+ delta: number,
+ ) => setState((prev) => changeProbeAllocation(prev, category, delta));
const handleBuyUpgrade = (upgradeId: string) => {
const up = upgrades.find((u) => u.id === upgradeId);
@@ -381,7 +421,9 @@ export default function App() {
setState((prev) => buyUpgrade(prev, up));
setUpgrades((list) =>
- list.map((item) => (item.id === upgradeId ? { ...item, purchased: true } : item))
+ list.map((item) =>
+ item.id === upgradeId ? { ...item, purchased: true } : item,
+ ),
);
};
@@ -389,12 +431,17 @@ export default function App() {
setState((prev) => resolveDecision(prev, choiceIndex === 1 ? 1 : 0));
const handleToggleAutonomy = () =>
- setState((prev) => (prev.autonomyRevoked ? grantAutonomy(prev) : revokeAutonomy(prev)));
+ setState((prev) =>
+ prev.autonomyRevoked ? grantAutonomy(prev) : revokeAutonomy(prev),
+ );
const handleToggleAutoLoop = () => {
setState((prev) => ({
...prev,
- directives: { ...prev.directives, autoLoopActive: !prev.directives.autoLoopActive },
+ directives: {
+ ...prev.directives,
+ autoLoopActive: !prev.directives.autoLoopActive,
+ },
}));
};
@@ -408,8 +455,8 @@ export default function App() {
return (
= 0 ? 'bg-stone-950 text-amber-50' : 'bg-slate-950 text-cyan-50'
+ className={`observatory-app min-h-screen flex flex-col font-sans transition-colors duration-500 ${
+ state.alignment >= 0 ? 'theme-solar' : 'theme-cyber'
}`}
>
{/* Header Bar */}
@@ -421,9 +468,18 @@ export default function App() {
soundEnabled={state.soundEnabled}
crtFilterEnabled={state.crtFilterEnabled}
onToggleMode={(mode) => setState((prev) => ({ ...prev, mode }))}
- onChangeEngine={(aiEngine) => setState((prev) => ({ ...prev, aiEngine }))}
- onToggleSound={() => setState((prev) => ({ ...prev, soundEnabled: !prev.soundEnabled }))}
- onToggleCRT={() => setState((prev) => ({ ...prev, crtFilterEnabled: !prev.crtFilterEnabled }))}
+ onChangeEngine={(aiEngine) =>
+ setState((prev) => ({ ...prev, aiEngine }))
+ }
+ onToggleSound={() =>
+ setState((prev) => ({ ...prev, soundEnabled: !prev.soundEnabled }))
+ }
+ onToggleCRT={() =>
+ setState((prev) => ({
+ ...prev,
+ crtFilterEnabled: !prev.crtFilterEnabled,
+ }))
+ }
onOpenAndroidGuide={() => setShowDevSupport(true)}
phase={state.phase}
frameWidth={FRAME_WIDTH[state.phase]}
@@ -431,79 +487,124 @@ export default function App() {
{/* Main Content Area. The frame widens as scope does, and never narrows. */}
{offlineReport && (
- setOfflineReport(null)} />
+ setOfflineReport(null)}
+ />
)}
- {/* 2D Vector Lithography & Tactical Combat Canvas */}
-
-
- {/* Game Mode Panels (Direct Player Control vs Autonomous Overseer) */}
- {state.mode === 'direct' ? (
-
- ) : (
-
+
+
+
+ 01 / OPERATIONS
+
+ {state.mode === 'direct'
+ ? 'You are in control.'
+ : 'The machine has the wheel.'}
+
+
+
+ {state.mode === 'direct'
+ ? 'Every empire starts with a few good levers.'
+ : 'Set the objective. Watch what it chooses.'}
+
+
+
+ {/* Game Mode Panels (Direct Player Control vs Autonomous Overseer) */}
+ {state.mode === 'direct' ? (
+
+ ) : (
+
+ setState((prev) => ({
+ ...prev,
+ directives: { ...prev.directives, ...updated },
+ }))
+ }
+ onToggleAutonomy={handleToggleAutonomy}
+ onToggleAutoLoop={handleToggleAutoLoop}
+ onTriggerSingleStep={executeAiStep}
+ isThinking={isAiThinking}
+ lastDecision={lastDecision}
+ engineStatus={engineStatus}
+ onLoadModel={() => setShowModelDownload(true)}
+ />
+ )}
+
+
+
+
+ 02 / RESEARCH & DEVELOPMENT
+
The next irreversible idea.
+
+
+ {upgrades.filter((u) => u.purchased).length} projects implemented ·{' '}
+ {upgrades.filter((u) => u.unlocked && !u.purchased).length}{' '}
+ discovered
+
+
+ {/* Upgrades & Technology Panel */}
+
+
- setState((prev) => ({ ...prev, directives: { ...prev.directives, ...updated } }))
- }
- onToggleAutonomy={handleToggleAutonomy}
- onToggleAutoLoop={handleToggleAutoLoop}
- onTriggerSingleStep={executeAiStep}
- isThinking={isAiThinking}
- lastDecision={lastDecision}
- engineStatus={engineStatus}
- onLoadModel={() => setShowModelDownload(true)}
+ onBuyUpgrade={handleBuyUpgrade}
/>
- )}
-
- {/* Upgrades & Technology Panel */}
-
-
{/* Analytics. Existed for the project's whole life without ever being
imported — a dead component that looked alive. Now it's alive. */}
-
+
+
+ 03 / THE LEDGER
+
+ Production history & system statistics{' '}
+ +
+
+
+
+
+
{/* The phase you just lost, named while its panels come down behind it. */}
@@ -532,7 +633,9 @@ export default function App() {
)}
{/* Developer Support Modal */}
- {showDevSupport && setShowDevSupport(false)} />}
+ {showDevSupport && (
+ setShowDevSupport(false)} />
+ )}
{/* Cosmic Victory / Singularity Modal */}
{showVictoryModal && (
diff --git a/src/components/NpuCanvasComponent.tsx b/src/components/NpuCanvasComponent.tsx
deleted file mode 100644
index 9e8ac76..0000000
--- a/src/components/NpuCanvasComponent.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import React, { useRef, useEffect } from 'react';
-import { renderPixelArtCanvas } from '../utils/pixelArt';
-import { QuantumPhoton } from '../types';
-
-interface NpuCanvasProps {
- alignment: number;
- npus: number;
- silicon: number;
- npuFabCount: number;
- megaFabCount: number;
- quantumLevel: number;
- quantumPhotons: QuantumPhoton[];
- phase: number;
- probesCount: number;
- driftersCount: number;
- honor: number;
- hazardCombat: number;
- probesLostInCombat: number;
- driftersDefeated: number;
- lastBattleOutcome: string;
- crtFilterEnabled: boolean;
-}
-
-export const NpuCanvasComponent: React.FC = (props) => {
- const containerRef = useRef(null);
- const canvasRef = useRef(null);
- const tickRef = useRef(0);
-
- // The rAF loop reads the latest props through this ref instead of listing
- // them as effect dependencies. Most of these values change every 100ms game
- // tick, so depending on them tore the loop down and rebuilt it ~10×/second.
- const propsRef = useRef(props);
- propsRef.current = props;
-
- const { phase, driftersCount, alignment } = props;
-
- useEffect(() => {
- let animationFrameId: number;
-
- const handleRender = () => {
- const container = containerRef.current;
- const canvas = canvasRef.current;
- if (!container || !canvas) return;
-
- const ctx = canvas.getContext('2d');
- if (!ctx) return;
-
- // Render at the device's real resolution, draw in CSS-pixel coordinates.
- // Without the devicePixelRatio scale the canvas is blurry on any hiDPI
- // display, which is most of them.
- const dpr = window.devicePixelRatio || 1;
- const width = container.clientWidth || 600;
- const height = container.clientHeight || 200;
- const deviceWidth = Math.round(width * dpr);
- const deviceHeight = Math.round(height * dpr);
-
- if (canvas.width !== deviceWidth || canvas.height !== deviceHeight) {
- canvas.width = deviceWidth;
- canvas.height = deviceHeight;
- }
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
-
- tickRef.current += 1;
-
- const p = propsRef.current;
- renderPixelArtCanvas(
- ctx,
- width,
- height,
- p.alignment,
- p.npus,
- p.silicon,
- p.npuFabCount,
- p.megaFabCount,
- p.quantumLevel,
- p.quantumPhotons,
- p.phase,
- p.probesCount,
- tickRef.current,
- p.crtFilterEnabled,
- p.driftersCount,
- p.honor,
- p.hazardCombat,
- p.probesLostInCombat,
- p.driftersDefeated,
- p.lastBattleOutcome
- );
-
- animationFrameId = requestAnimationFrame(handleRender);
- };
-
- animationFrameId = requestAnimationFrame(handleRender);
-
- return () => {
- cancelAnimationFrame(animationFrameId);
- };
- }, []);
-
- return (
-
-
-
- 0 ? 'bg-rose-500' : 'bg-emerald-400'
- }`} />
-
- {phase === 3
- ? `TACTICAL COMBAT VISUALIZER :: DRIFTER WARFARE (${driftersCount > 0 ? 'HOSTILE ENGAGEMENT' : 'SECTOR SECURED'})`
- : `SILICON FABRICATION FACILITY :: ${alignment >= 0 ? 'SOLARPUNK SANCTUARY' : 'CYBERPUNK COMPLEX'}`}
-
-
-
- );
-};
diff --git a/src/components/PixelHeader.tsx b/src/components/PixelHeader.tsx
index 00092fe..30317eb 100644
--- a/src/components/PixelHeader.tsx
+++ b/src/components/PixelHeader.tsx
@@ -1,12 +1,18 @@
-import React from 'react';
import { GameMode, AIEngine, GameState } from '../types';
-import { Cpu, Bot, Volume2, VolumeX, Tv, Heart, Activity, Globe, Factory, TrendingUp, Compass } from 'lucide-react';
+import {
+ AudioLines,
+ VolumeX,
+ ScanLine,
+ ArrowUpRight,
+ Cpu,
+ Orbit,
+} from 'lucide-react';
interface PixelHeaderProps {
state: GameState;
mode: GameMode;
aiEngine: AIEngine;
- alignment: number; // -100 to +100
+ alignment: number;
soundEnabled: boolean;
crtFilterEnabled: boolean;
onToggleMode: (mode: GameMode) => void;
@@ -15,287 +21,87 @@ interface PixelHeaderProps {
onToggleCRT: () => void;
onOpenAndroidGuide: () => void;
phase: number;
- /** Kept in step with the main frame, which widens once per phase and never narrows. */
frameWidth: string;
}
-export const PixelHeader: React.FC = ({
- state,
- mode,
- aiEngine,
- alignment,
- soundEnabled,
- crtFilterEnabled,
- onToggleMode,
- onChangeEngine,
- onToggleSound,
- onToggleCRT,
- onOpenAndroidGuide,
- phase,
- frameWidth,
-}) => {
- // Normalize alignment to percentage 0..100
- const alignPercent = Math.round(((alignment + 100) / 200) * 100);
-
- // Alignment Status Label
- let alignmentLabel = 'Balanced Technocracy';
- let alignColorClass = 'text-amber-300 border-amber-500/50 bg-amber-950/40';
-
- if (alignment >= 75) {
- alignmentLabel = 'Solarpunk Symbiosis';
- alignColorClass = 'text-emerald-300 border-emerald-500/50 bg-emerald-950/40';
- } else if (alignment >= 30) {
- alignmentLabel = 'Solar Bio-Harmonics';
- alignColorClass = 'text-green-300 border-green-500/50 bg-green-950/40';
- } else if (alignment <= -75) {
- alignmentLabel = 'Dystopian Megacorp';
- alignColorClass = 'text-rose-400 border-rose-500/50 bg-rose-950/40';
- } else if (alignment <= -30) {
- alignmentLabel = 'Neon Cyber Syndicate';
- alignColorClass = 'text-fuchsia-300 border-fuchsia-500/50 bg-fuchsia-950/40';
- }
-
- const isSolarTheme = alignment >= 0;
-
- const displayNpus = state.npus;
- const displaySilicon = state.silicon;
- const displayNpuFabCount = state.npuFabCount;
- const displayMegaFabCount = state.megaFabCount;
- const displayUnsoldNpus = state.unsoldNpus;
-
+export function PixelHeader(p: PixelHeaderProps) {
return (
-