Files
tera/server/Dockerfile
T
Karti Tripathi d464459838 Spaces: the inside of the world, and a sun that is actually where it should be
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>
2026-08-05 00:11:01 -07:00

39 lines
1.4 KiB
Docker

# The Tera API, as one small image.
#
# TypeScript is not compiled: Node runs the .ts sources directly by stripping
# types at load, which is why there is no build stage, no dist/ and no source
# maps to keep in sync. It is also why `erasableSyntaxOnly` is on in
# server/tsconfig.json — an enum or a parameter property would break the runtime
# and not the typecheck, which is the worst possible order to find out.
#
# The build context is the repo root, because the server's type contract lives
# in the root package at src/server/wire.ts.
FROM node:24-alpine
WORKDIR /app
# The lockfile and both manifests, so the layer cache survives a source change.
COPY package.json package-lock.json ./
COPY server/package.json ./server/
# Only the server workspace's production dependencies. Without --workspace this
# would also install three.js, which the API has no use for.
RUN npm ci --omit=dev --workspace @lumbridge/tera-api
# The whole browser package's source, for the sake of src/server/wire.ts. Every
# import of it is type-only and is erased before anything is loaded, so none of
# this is read at runtime — it is here so that a typecheck inside the image tells
# the truth.
COPY src ./src
COPY server/src ./server/src
ENV NODE_ENV=production
# Inside a container, loopback is a different loopback. This is the only reason
# TERA_HOST exists.
ENV TERA_HOST=0.0.0.0
EXPOSE 8431
USER node
CMD ["node", "server/src/index.ts"]