d464459838
Ten agents wrote this in parallel against CONTRACT.md, which exists because the five design agents before them collided on fifteen blocking points — four files specified twice with incompatible contents, three separate backends for one box, and `Environment` exported twice meaning different things. What landed: a Stage owning only the renderer and the loop, with the city and an office as two scenes over it. They cannot share one — San Francisco is ~94 m per scene unit with 3.6x vertical exaggeration and an office is 1 unit = 1 m — and the city is paused rather than disposed on the way in, because rebuilding its 336,864-point heightfield costs about a second on the way back out. Offices are data. `src/offices/lumbridge-hq.ts` is fifteen rooms and seventy-six seats, and it is the file a self-hoster copies. Walls are a segment list with 1-D openings, so doors and windows are holes punched in a wall rather than placed objects, and the pass that splits a wall around its openings hands the walk-mode collider its segments for free. The sun is real. `solar.ts` is a NOAA/Meeus implementation with no imports at all — not even three.js — so time of day keeps working on a laptop in a field. Verified against known values: 75.45 degrees at the June solstice in SF, 28.79 at December, sunset at 03:15Z. The first screenshot after wiring it was a black rectangle, which turned out to be correct: it was midnight in San Francisco. Presence binds to a seat id and never to a coordinate. The pack knows where `eng-04` is; who is sitting in it is private data behind an API. Same shape as the marker rule, one level in. Two corrections to ARCHITECTURE.md are in here. Containment does not discharge ODbL — publishing OSM-derived coordinates is Public Use of a Derivative Database wherever the rows live, so the rule is about the geocoder (US Census, public domain) and not the storage. And a person at a desk is not a Marker; markers are geographic. One contract gap surfaced only in a screenshot: two agents read `height` on a viewpoint differently, so the establishing shot aimed at empty air fourteen metres above the roof. It now means what the same field means for a city. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
173 lines
8.6 KiB
Markdown
173 lines
8.6 KiB
Markdown
# Contributing to Tera
|
|
|
|
Thanks for wanting to. Two sections matter before any others — the inbound
|
|
grant, because it is what keeps this repo's licensing claims true, and the four
|
|
hard rules, because a change that breaks one of them cannot be merged no matter
|
|
how good it is.
|
|
|
|
---
|
|
|
|
## The inbound grant
|
|
|
|
**By submitting a contribution to this repository — a pull request, a patch, a
|
|
commit, or a suggested change in any form — you agree to the following, and the
|
|
act of submitting it is the agreement. There is nothing extra to sign.**
|
|
|
|
1. You license your contribution under the **Apache License, Version 2.0**, on
|
|
the terms in [LICENSE](LICENSE).
|
|
|
|
2. **For any change under `src/assets/`**, you *additionally* dedicate the
|
|
**artistic output** of that contribution — the meshes, geometry, textures,
|
|
materials, palettes and images it produces — to the public domain under
|
|
**CC0 1.0**, on the terms in [`src/assets/LICENSE-ART`](src/assets/LICENSE-ART).
|
|
Where a dedication is not possible under the law that applies to you, you
|
|
grant the equivalent unconditional licence described in that file.
|
|
|
|
3. You certify that the contribution is your own work, or that you have the
|
|
right to submit it under these terms; that you are not knowingly including
|
|
anyone else's copyrighted material, trademark, or data carrying obligations
|
|
this repository cannot meet (see the hard rules below); and that you
|
|
understand the contribution and this record of it are public and permanent.
|
|
|
|
If your employer has rights in your work, get their sign-off before you submit.
|
|
If you cannot make all three certifications for some part of a change, say so in
|
|
the pull request and it will be sorted out before merge rather than after.
|
|
|
|
### Why this is standing rather than per-PR
|
|
|
|
Apache 2.0 §5 supplies a default inbound=outbound grant, so contributions arrive
|
|
Apache-2.0 whether or not anyone says so. **There is no equivalent default for
|
|
CC0.** A single merged pull request under `src/assets/` whose author never said
|
|
the words would leave that contribution Apache-only, which would make
|
|
`LICENSE-ART`'s dedication false for part of the library — and copyright cannot
|
|
be taken back afterwards, so it would also be unfixable. Stating the grant here
|
|
and treating submission as acceptance is what stops that from being possible.
|
|
|
|
The reasoning is recorded in [CONTRACT.md](CONTRACT.md) §3.1, and the carve-out
|
|
is in [NOTICE](NOTICE), which is what a downstream consumer actually reads to
|
|
learn this repo is not uniformly Apache-2.0.
|
|
|
|
---
|
|
|
|
## The four hard rules
|
|
|
|
These are licensing and privacy constraints, not preferences. Each has its full
|
|
reasoning in [ARCHITECTURE.md](ARCHITECTURE.md) §3 or [CONTRACT.md](CONTRACT.md).
|
|
|
|
**1. No binary art in `src/**`, ever.** No textures, no glTF, no logos, no
|
|
fonts, no images. Every mesh is a function composing cached unit primitives and
|
|
every texture is drawn on a 2D canvas from seeded noise. This is what gives the
|
|
repo zero asset-licensing exposure, and it is worth more than the quality
|
|
ceiling it costs.
|
|
|
|
CI enforces this by enumerating tracked files with `git ls-files`, never by
|
|
walking the working tree — so your own legally-clean `.glb` sitting in
|
|
`public/props/` is invisible to the check and cannot fail your build. Those
|
|
directories (`public/props/`, `public/kits/`, `public/offices/`, `docs/`) are
|
|
self-hoster space and are git-ignored on purpose.
|
|
|
|
**2. No OpenStreetMap-derived coordinates.** OSM and Nominatim output is ODbL,
|
|
whose share-alike terms cannot be reconciled with Apache 2.0. Note that keeping
|
|
such rows out of the repo does *not* discharge the obligation — serving a
|
|
snapshot of them publicly is Public Use of a Derivative Database either way. The
|
|
sanctioned geocoder is the US Census Geocoder (`geocoding.geo.census.gov`), a US
|
|
Government work in the public domain. Google, Mapbox and HERE do not solve this
|
|
either; their terms restrict storing and redistributing what they return.
|
|
Geography in `src/cities/` is traced by hand.
|
|
|
|
**3. No trademarks.** Organisation logos are fetched at runtime by the
|
|
consuming application and are never committed here.
|
|
|
|
**4. The engine takes no position on what data means.** `markers.ts` renders
|
|
`Marker[]` with an opaque `colorKey`; `src/assets/` renders a `SurfaceRole` and
|
|
an opaque colour key one level in. Neither will ever learn that a marker is a
|
|
company, that a status maps to a colour, or that a seat has a person in it.
|
|
That is what lets one engine serve a private map and a public one without either
|
|
being a fork — and, for interiors, what lets the office geometry be open-source
|
|
while who is sitting in `eng-04` stays private data behind an API.
|
|
|
|
Everything must work with **no account, no Supabase project, no API key and no
|
|
network**. That is the acceptance test, and CI runs it: `docker compose up`
|
|
under `env -i`, asserting `GET /api/v1/health` returns 200.
|
|
|
|
---
|
|
|
|
## Contributing an asset
|
|
|
|
Assets live in `src/assets/` and are procedural TypeScript. The pieces:
|
|
|
|
| file | what it holds |
|
|
| --- | --- |
|
|
| `kit.ts` | `AssetId`, `AssetDef`, the registry, `createAssetContext` |
|
|
| `materials.ts` | `MaterialRegistry`, keyed on the closed `SurfaceRole` union |
|
|
| `palette.ts` | the interior palette, derived from the city's by declared HSL shifts |
|
|
| `parts.ts` | the shared cached unit-primitive bin, and `MeshBin` |
|
|
| `textures.ts` | every texture, drawn with Canvas2D and seeded noise |
|
|
|
|
Conventions, in order of how often they are got wrong:
|
|
|
|
- **Metres. 1 unit = 1 m.** The city is not on this scale and cannot be; that is
|
|
why an office gets its own `THREE.Scene`.
|
|
- **Build out of `parts`, not out of fresh geometry.** Every unit part is 1 m in
|
|
each dimension with its base on `y = 0`, and assets place them with scaled
|
|
local matrices. This is what keeps a 1,200-object office at about thirty draw
|
|
calls, and it is also what makes independently-written assets look like one
|
|
library rather than eighteen dialects.
|
|
- **Ask for a `SurfaceRole`, never a colour.** Roles are named after the object
|
|
(`deskSurface`, `partitionFabric`), never after the finish. If you need a role
|
|
that does not exist, add it in three places — the union in `materials.ts`, its
|
|
spec below it, and its shift in `ROLE_SHIFTS` — and the compiler will not let
|
|
you forget the third.
|
|
- **Namespace built-ins `tera:`.** Your own assets get your own namespace, and
|
|
`overrides: "tera:desk.workstation"` reskins the reference office without
|
|
forking it. That mechanism is the point; use it rather than editing built-ins.
|
|
- **`footprint()` must not build anything.** Layout asks how big things are far
|
|
more often than it asks for their geometry.
|
|
- **Determinism.** Any randomness comes from `ctx.rand`, seeded per instance. A
|
|
world that reshuffles itself between visits is a lava lamp, not a place.
|
|
- **Nothing throws.** An unregistered id draws a placeholder box; an unknown
|
|
surface id falls back to a role. A pack with one typo in it should still open.
|
|
|
|
Doors and windows are `Opening` records punched out of a wall, not assets. There
|
|
is deliberately no `tera:shell.door` — see `interiors/types.ts`.
|
|
|
|
---
|
|
|
|
## Contributing a city
|
|
|
|
Write `src/cities/<id>.ts` exporting a `City`: coastline and parks traced by
|
|
hand, hills as radial peaks, a street bearing per district. A city pack is pure
|
|
data, which is what makes it reviewable. Rule 2 above applies with no exceptions.
|
|
|
|
---
|
|
|
|
## House style
|
|
|
|
- **Comments explain *why*, not *what*.** The line below a comment already says
|
|
what it does. What it cannot say is that an earlier version rejection-sampled
|
|
buildings uniformly and looked like rubble, or that four comparisons took the
|
|
terrain build from 2.3 s to 1.0 s. Write those down; they are the expensive
|
|
part. Read two or three existing files before you write your first one.
|
|
- Prose comments, full sentences. Section headers are `// ---- Name ----`, and
|
|
that is the only decoration in the repo.
|
|
- Strict TypeScript with `noUncheckedIndexedAccess`, `noUnusedLocals`,
|
|
`noUnusedParameters` and `verbatimModuleSyntax`. Index access needs a
|
|
null-check or a `?? fallback`, type-only imports say `import type`, and
|
|
relative imports carry the `.ts` extension.
|
|
- No new runtime dependency without a reason in the pull request. The dependency
|
|
check is an allowlist that names why each one is permitted, not a count.
|
|
|
|
## Running it
|
|
|
|
```bash
|
|
npm install
|
|
npm run dev # the demo app
|
|
npm run typecheck # tsc --noEmit
|
|
npm run build # typecheck, then vite build
|
|
```
|
|
|
|
The two CI jobs that gate the repo are `git clone && npm ci && npm run build`,
|
|
and the zero-config `docker compose up` health check described above. If a
|
|
change makes either of those need a key, an account or a network call, it is the
|
|
change that is wrong.
|