# Discovery: search engines and AI engines as first-class audiences

- Status: requirement, recorded 2026-08-02 (owner flagged it as the audience
  that nearly got lost in the Phase 8 cleanup)
- Applies to: Inception Space and every Lucas Academy site
- Related: the Lucas Academy knowledge platform's locked principle —
  GEO / AI-readability first, EN + ZH only, no top menu

## Why this needs writing down

Everything the museum shows lives inside a WebGL canvas. To a crawler or an
LLM, `index.html` is an empty page with a script tag: no room names, no
artist statements, no artwork titles. A product whose entire content is
invisible to search and to AI assistants cannot be found, cited, or
recommended — and for a school project, "a parent asks an assistant about
kids' art programs" is a real discovery path.

Three audiences, not one:

1. **People** — the 3D experience.
2. **Search engines** — need crawlable HTML, titles, descriptions, sitemaps,
   structured data.
3. **AI engines** (assistants, answer engines) — need clean text they can
   read and quote, ideally without executing JavaScript.

## Requirements

### R1. Every space has a text twin

Each room, artwork, and exhibition gets a real URL serving server-rendered
HTML: title, description, the artist statement, the medium, the class or
program it belongs to, and a link into the 3D view at that spot. The canvas
becomes the *enhanced* view of a page that already works as text.

### R2. Structured data

`schema.org` JSON-LD on those pages: `VisualArtwork` for pieces,
`ExhibitionEvent` / `Collection` for shows, `Organization` +
`EducationalOrganization` for Lucas Academy, `Person`-free by policy (student
identity stays out — use pseudonymous attribution).

### R3. Machine-readable mirrors

- `sitemap.xml` and `robots.txt` that actually list the text twins.
- An `llms.txt` at the site root plus Markdown mirrors of the key pages, so
  an assistant can read the content without a headless browser.
- Stable, human-readable URLs (`/rooms/van-gogh-house`, not `?slot=3`).

### R4. Social/answer previews

Per-page `<title>`, meta description, OpenGraph and Twitter cards with a
real rendered image (a curated screenshot of the room, not a logo). These
double as the thumbnail an answer engine shows.

### R5. Language policy

EN + ZH only, site-wide, both indexable, `hreflang` pairs. This matches the
knowledge platform's locked decision; do not add more locales here.

### R6. Privacy is non-negotiable and comes first

Indexable text is a *published* surface: it may contain only what the
`public_snapshot` layer allows (execution plan §2.3 / §5). No real names, no
emails, no class membership, no draft history, no AI dialogue. When in
doubt, a page is not indexed at all — `noindex` is the default for anything
that is not a curated public snapshot. The current `index.html` carries
`<meta name="robots" content="noindex">` and keeps it until the public
surface exists.

**The worked example is `/class/`** (owner 2026-09-12). A lesson record names
the children who were in the room, quotes what they said about being loved, and
shows their faces. R6 does not forbid the page — it decides where the page
lives: not indexed at all. The section is absent from `sitemap.xml` and
`llms.txt`, sits behind `Disallow: /class/`, and every record and its index carry
`noindex, nofollow, noarchive, noimageindex`; `tests/class-section.test.js`
holds all four, because any one of them is easy to forget. What R6 cannot do is
make an unlisted URL private — a link is a secret, not a door — so the rule for
what belongs there is the same as for a printed handout: nothing that would harm
a child if a stranger read it.

Reusable lesson plans are public teaching material (owner clarification,
2026-09-12). The six-lesson plan is published at
`/docs/van-gogh-love-lessons.html`, included in the public docs navigation,
sitemap and llms.txt. All lesson records and their index stay unlisted.
Links may lead from a record to the public plan; public plans and their
navigation must never reveal record URLs, student photographs or the private
record index. Previously shared plan URLs use the public doc's navigation and
canonical URL, so they do not retain a route into the records.

## What this changes about hosting

Static Vite output cannot server-render the text twins. Two workable paths,
to decide when the public surface is built:

- **Prerender at build time** — generate one static HTML file per public
  room/artwork from the same manifest the 3D world reads. Simplest, works on
  Render's static hosting, no runtime cost. Recommended default.
- **Server-render on request** — needed only if public content changes
  faster than deploys (it will not, for curated snapshots).

## What is built (2026-08-03)

`npm run build` runs `scripts/prerender-discovery.mjs` after Vite, which
generates the readable surface from the SAME manifest and room documents the
3D world reads — so there is no second description to drift out of date:

- **R1** `dist/rooms/<slot>.html` per PUBLIC room: title, a description
  derived from the room's own shape, face count and prop count, and a link
  into the museum. A private room is not written at all.
- **R2** `schema.org` JSON-LD — `EducationalOrganization` + `WebSite` +
  `Collection` on the root, `Collection` per room. No `Person`, ever.
- **R3** `sitemap.xml`, `robots.txt` (allow the root, the room pages and
  `llms.txt`; nothing else), and `llms.txt` — a Markdown brief an assistant
  can read without running JavaScript.
- **R4** `<title>`, description and OpenGraph on the root and every room
  page — **and, since 2026-08-31, a preview image on all of them**. The root
  had declared `twitter:card = summary_large_image` from the start, which
  promises a big-picture card, with no `og:image` to fill it; the card
  therefore rendered as a plain text box, and the room pages carried no
  picture and no `twitter:` tag at all. One shared card now answers every
  page: `public/og-cover-v1.jpg`, a 1200 x 630 crop of the museum's own
  constellation portal, owned by `src/inception/discovery/socialCard.js` so
  the landing page and both generated templates cannot drift, with the build
  refusing if the file is missing from `dist/`.
  **This is the shared card, not what R4 asks for.** R4 wants a curated
  screenshot *per room* — "a room, not a logo" — because the same image is
  what an answer engine shows beside a specific room. That is still unbuilt
  (owner 2026-08-31: one image for everything first). When it is built, it
  replaces this card by giving the same module something better to return
  per page; the shared card stays as the fallback for pages that are not a
  room.
- **R6** Two tests enforce it: the published files may not contain any owner
  email or the word "password", and a room appears in the sitemap **iff**
  its document says `visibility: public`.
- **The `/docs/` documentation site (2026-08-03)**: the repository's own
  Markdown docs — vision, direction, the bilingual execution plan, the
  `.lpr` format, performance notes and the ADRs — rendered to static HTML
  with a raw `.md` mirror per page (`src/inception/discovery/docsCatalog.js`
  is the published list). Email addresses are redacted at render time, and
  a test scans everything under `/docs/` for address shapes. `llms.txt`
  links the `.md` mirrors so an assistant with no repository context can
  read the same source of truth people do. The ops runbook and the
  agent-instruction files are deliberately not published.

The landing page itself is deliberately almost empty — the door and the
controls, with everything else behind `?debug` (owner 2026-08-03). Discovery
therefore rides on the head metadata and the prerendered pages listed in the
sitemap, not on visible text in the app shell. That is a real constraint:
if a crawler ignores the sitemap it sees very little.

### Still open

- **R4's PER-ROOM preview image.** A curated screenshot of each room,
  rendered headlessly at build time. Every page has a picture now, but it is
  the same picture; a room page's card does not yet show that room.
- **R5's `hreflang` pair.** The pages are English only; the ZH twin needs
  the room documents to carry a translated title first.
- Per-artwork pages. Rooms exist as pages; individual paintings do not,
  because the public-snapshot layer that decides what an artwork may say
  (execution plan §2.3) is not built.
