# Heaven City Assets

The independent source and CDN package for every Heaven City asset. The web app requests this service's manifest, then downloads model definitions when they appear in a preview or on the map. Geometry is cached by immutable content hash; colour variants share one definition. No JavaScript is downloaded from the asset service.

## Local development

Requires Node.js 22+.

```sh
npm ci
npm run dev
```

The local server is `http://127.0.0.1:5180` and the entry point is `/manifest.json`. The Vite app proxies `/city-assets/` to this service by default. Run this server alongside the frontend (5173) and API (3000).

After editing source JSON, run `npm run build`. Refresh the web app to reload the manifest. The server reads the generated files directly. `dist/` is generated and ignored by Git.

```text
src/world.json          Shared projection, tile, and headroom rules
src/assets/*.json       One editable source file per asset
schemas/                Machine-readable asset and manifest contracts
scripts/build.mjs       Validate, hash, and package the catalog
server.mjs              Local HTTP server with CORS, ETags and cache headers
dist/manifest.json      Generated public index
 dist/assets/*.json     Generated immutable model definitions
```

## Asset developer workflow

1. Copy a source JSON in `src/assets/`; choose a stable snake_case `id` and matching filename.
2. Define its geometry using boxes/pyramids, named hex colours, and reusable variants. Read [AUTHORING.md](AUTHORING.md) and validate against [asset.schema.json](schemas/asset.schema.json).
3. Default to a **1×1** logical footprint, a **3×3** animal roaming area, and **one plot of vertical headroom**. Static assets may explicitly declare larger footprints up to 8×8; the included Plaza (2×2) and Warehouse (3×4) demonstrate this. Art dimensions never determine occupied plots. The renderer owns the 2:1 isometric projection and 90° rotation.
4. Run `npm run build`. Invalid definitions, duplicate IDs, unknown palette references, and oversized models fail the build.
5. Check all variants and four rotations in the web app. New geometry must include all four sides.
6. Commit the source and push. Build/deploy the CDN. Give the app developer **one manifest URL**.

The manifest contains metadata for library cards plus a versioned relative URL and SHA-256 checksum per asset. The browser loads definitions on demand. Changed geometry gets a new filename; unchanged geometry stays cached. Do not reuse IDs for different objects or remove variants used by saved map instances.

The manifest controls appearance and capabilities. PostgreSQL owns placed instances: asset ID, position, rotation, chosen variant, and roaming toggle. New IDs/capability changes also require the API's `npm run assets:sync -- <manifest-url>` followed by an API rebuild/restart. Asset definitions and categories never need database rows. Palette/geometry-only updates do not require moving any placed objects.

## Host on Cloudflare Pages

Cloudflare is a good fit: this is a static JSON catalog and needs no hosted Node.js process. `server.mjs` is only for local development. Hosting configuration follows the official [Direct Upload](https://developers.cloudflare.com/pages/get-started/direct-upload/) and [custom headers](https://developers.cloudflare.com/pages/configuration/headers/) documentation.

### Git deployment (recommended)

Connect the `heaven-city-assets` GitHub repository in Cloudflare → Workers & Pages → Pages. Set:

| Setting | Value |
| --- | --- |
| Framework preset | None |
| Build command | `npm run build` |
| Build output directory | `dist` |
| Root directory | Repository root |
| Node version | 24 |

Pushes to `main` build and publish the asset pack. Add an asset subdomain such as `assets.YOUR-DOMAIN.com` in Pages → Custom domains. The URL you give to the web app is `https://assets.YOUR-DOMAIN.com/manifest.json` (or the provided `pages.dev` URL).

### Manual CLI deployment

```sh
npm ci
npm run build
npx wrangler login
npx wrangler pages project create heaven-city-assets --production-branch main
npm run deploy
```

The script deploys **only `dist/`**. Authenticate with your own Cloudflare account; no API token is committed here. The first project-create command is needed once. If using Git deployment, deploy by pushing instead.

### Connect the web app

In the frontend's environment file, set this public URL, then restart/rebuild Vite:

```dotenv
VITE_ASSET_MANIFEST_URL=https://assets.YOUR-DOMAIN.com/manifest.json
```

Then update server capabilities when necessary:

```sh
# Run in the API repository
npm run assets:sync -- https://assets.YOUR-DOMAIN.com/manifest.json
npm run build
```

Default local URL remains `/city-assets/manifest.json`. Production builds must use the CDN URL unless your web host explicitly proxies that local path.

### Caching and releases

The build generates a Pages `_headers` file: public CORS for these non-sensitive assets, `no-cache` on the manifest, and one-year immutable browser caching on hashed model files. A real `404.html` prevents missing JSON from falling back to an HTML application shell. Existing hashes in `dist/` are retained during local builds; preserve historical immutable files if you need already-open clients to load old releases across a fresh CI checkout. For a very large catalog or long-term version retention, move the same generated files to Cloudflare R2 with a public custom domain and equivalent CORS/cache headers. The manifest format does not change.

No CDN deployment is performed by starting the local server.
