Skip to content

Open Perpetuum wiki (static site, Zola) - #1

Merged
clouths merged 82 commits into
mainfrom
feature/implementation
Oct 4, 2026
Merged

clouths merged 82 commits into
mainfrom
feature/implementation

Conversation

@clouths

@clouths clouths commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Draft — preview / work in progress. This is the full Open Perpetuum wiki as a static site.
It is a preview of the finished product; structure and prose are still being reviewed before it goes final.

What this is

A complete player-facing + developer-facing wiki for the Open Perpetuum server, built with Zola (~5,400 pages):

  • Feature guides (/features/): 20+ hand-written pages (combat, production, PBS, transport, research, …) with mermaid diagrams, written from and verified against the server source
  • Generated data catalogs (/content/): items (one page per item), ores (one page per ore), plants, deployables, robots, extensions + interactive extension tree, missions, shop (per-category pages), recipes, tech tree (658 node pages), stat reference
  • World & zones (/zones/): one page per zone (82) with ore configuration and per-zone teleport maps (SVG), the interactive world map, zone index, protection levels
  • Site chrome: sci-fi dark/light theme with accent palettes, colorblind modes, client-side search, i18n-ready UI strings
  • License: Apache-2.0; all prose is original, verified against the backend

Generated markdown is committed to git, so the site build (and CI) needs no database — only a Zola binary.

CI

wiki.yml runs the Zola build on every PR (build-only) and deploys to GitHub Pages on main. GitHub Pages needs to be enabled on the repo (Settings → Pages → Source: GitHub Actions) before the deploy job can do anything.

How to run locally

Prerequisites

  • Docker (the build uses the pinned official Zola container; no local Zola install needed)
  • For regenerating data pages only: .NET 8 SDK and a reachable game database (see below)
git clone --recurse-submodules https://github.com/OpenPerpetuum/Wiki.git
cd Wiki

Build the site (no DB needed)

make build     # builds the static site into public/
make serve     # serves it with live reload on http://localhost:8085 (WIKI_PORT=NNNN to change)

Open http://localhost:8085 — that is the whole site. make clean removes the build output.

Regenerate the data pages (DB needed)

The generated pages (items, ores, zones, tech tree, …) are committed; regenerate them only when the database content changes:

# once: the plant rule files come from the server submodule
git submodule update --init server

make generate WIKI_DB="Server=<host>,<port>;Database=perpetuumsa;User Id=sa;Password=<...>;TrustServerCertificate=True;Encrypt=False"

The generator issues SELECTs only (read-only). WIKI_PLANTRULES defaults to the submodule's
server/src/Perpetuum.ServerService2/data/plantrules and can be pointed at any GameRoot plantrules/ directory instead.
Rebuild afterwards with make build. See generator/README.md for the page ↔ table mapping.

What the PR check does

The CI check is exactly zola build with the pinned v0.23.6 release binary — equivalent to make build without Docker. A green check means the site compiles and all templates render.

- Features: 17 gameplay system pages + client UI guide
- Content: items catalog (4,514 item pages), ores, plants, deployables,
  robots, extensions, tech tree, missions, recipes, shop (per-vendor +
  per-item 'Where to buy')
- Zones: generation mechanics, zone index, 3 worked examples, zone map
  (all 82 zones on the x/y grid with 171 TP connections)
- Formats: zone file + field reference pages
- Generator output committed (regenerated by Perpetuum.WikiGenerate)
- Templates (Zola), sci-fi dark/light theme, search index, mermaid
- Move the content generator (Perpetuum.WikiGenerate) into generator/
- Makefile: build/serve run Zola in the pinned Docker container
  (ghcr.io/getzola/zola:v0.23.6); generate runs the C# generator
  (WIKI_DB / WIKI_PLANTRULES / WIKI_GBF, gbf default ../Perpetuum.gbf)
- README: build + regeneration instructions
- GitHub Pages workflow for this repo (triggers on main)
- gitignore: build output, generator bin/obj

Verified: make build and make generate from this layout reproduce the
committed pages byte-identically.
search.js loads from <head>, so its init ran before the body existed and
silently bailed (no input listener ever attached). Wait for DOMContentLoaded
when the document is still loading. Enter now navigates to the top result
(the input is not in a form, so previously it was a no-op).

Verified with jsdom on a deep page and the home page: typing shows the
result dropdown, Enter navigates, Escape clears.
Sources: perpetuum.miraheze.org (fetched 2026-09-25; all 149 wikitext
pages kept as provenance in official-backup/, noted in the developer
comments of every adapted page).

New feature pages:
- faq (tutorial, faction choice), abbreviations (EP/NIC/CT/TAP/...),
  npcs (ranks + faction names, damage weaknesses, spawn types, TAPs,
  tactics, open-PvP hunting perks), sparks, exploration (artifacts/relics),
  outposts (stability, the four SAP types, decay, relics/guardians/invaders),
  lore (Nia, the Nians, the Syndicate), client-setup (Linux, multi-boxing
  rules, UI scaling), modules (families + fitting rules of thumb)

Enriched existing pages:
- combat (target lock, optimal/falloff, surface size, damage types,
  detection/interference/speed/demobilization)
- getting-started (tutorial, survival rules), groups (corp creation,
  CEO takeover, logo editor), production (item lifecycle: decoders ->
  reverse engineering -> mill -> prototype), missions (relations ->
  facility ratio, mission flavors, private transport collateral),
  gathering (solid vs liquid ore behavior), robots (community
  class-name crosswalk: light robots=runners, assault robots=crawlers,
  heavy gliders=walkers, destroyers=starter & special)

Terminology: rewritten to this wiki's terms (CPRG, protected/open-PvP
zones, current client zone names). Skipped: the source's Cargo stat
tables (our generator is current by construction), per-robot bios,
WIP/unverified formulas, retail-client-only guides.

Home page and features index updated with the new pages.
(Home-page video cards land in the follow-up UI commit with their CSS.)
- Branding: OPP project logo (from the community wiki) as
  the favicon (32px) and the brand icon left of the title (150px)
- Accent palettes: swatch button next to the theme toggle offering
  cyan (default) / violet / phosphor / amber; each palette tints the
  whole site in both dark and light modes (neon accents, body glows,
  grid overlay, background families, table-header bands); persisted
  in localStorage, applied pre-paint
- Search keyboard: Ctrl+F / Cmd+F focuses the wiki search bar, Esc
  unfocuses + clears; placeholder hints at the shortcut
- Home page: click-to-play video grid (official OPP YouTube videos)
  above 'New here' - auto-fit grid, several per row on wide windows,
  iframes only loaded on demand
- Footer: GitHub + X @OpenPerpetuum icon links, and attribution for the
  logo and adapted community-wiki content
- 34 robot model icons from the OPP community wiki in static/img/robots/
  (Scarab's icon is missing on their CDN; all others resolved)
- RobotsPage emits the icon per model (display-name -> icon map) so it
  survives regeneration
- Footer attribution: logo, robot icons and adapted content credited to
  the Open Perpetuum community wiki
- Content background is now a 2-stop top->bottom gradient with per-palette
  --bg-top/--bg-bottom (dark + light)
- Colorblind modes in the settings menu (default/deuteranopia/protanopia/
  tritanopia/grayscale): Okabe-Ito-based retint of the zone-map family
  colors and the accent; map colors moved from inline hex to CSS classes
  (ZonesMapPage emits mapfam-* classes, style.css owns the colors);
  persisted as wiki-cb, applied pre-paint
- Screen-reader text: video cards get role=button + aria-label, the zone
  map SVG gains a <title>, decorative spans aria-hidden; audited: 0 images
  without alt text site-wide
- Footer aligned to the content column (margin-left 240px, max-width
  1050px, panel border) so it no longer slides under the sticky sidenav
- 18 community-wiki images into static/img/: the four SAP
  icons + intrusion + relic (outposts), reactor plasma (npcs), spark
  (sparks), planet + three megacorp logos (lore), fitting/coreactor/evasive/
  drainer/neutralizer/interference-emitter (modules)
- Scarab model icon extracted from the client archive (GBF entry icon_scarab,
  per-chunk XOR) — its wiki 404s; the only robot icon not under a licensed source (noted in RobotsPage.cs)
- Items catalog now lists tier 0-5 ascending within every sub-category
  (untyped items last), name as the secondary key
- Layout: sidenav + (main + footer) in a flex row; the sticky sidenav now
  releases at the content column's bottom, so at full scroll its bottom
  aligns with the footer's
- Home menu: outline-only SVG group icons (transparent fill, accent stroke,
  shapes echoing the community wiki's category icons: radar disc, robotic
  arm, calibration panel, compass star, twin robots, crystal)
- Footer: YouTube channel link
- i18n seam: default_language in config, ALL chrome strings moved to
  [extra.ui] (templates read config.extra.ui; JS receives them via a
  #wiki-ui-strings data-attribute element -> window.WIKI_UI, since Zola 0.23
  has no JSON filter); language button (globe) left of the palette with the
  current 'English' entry
- search.js reads its copy from WIKI_UI
- Makefile: document the root-owned public/ case; clean falls back to a
  container rm (root cause was a leftover make serve container rebuilding
  public/ as root)
- Shop: shop.md is now an index (description, vendors, category reference
  table); each catalog category is a dedicated generated page
  (ShopPage.BuildCategoryPage -> shop/<slug>.md, 11 pages); the left menu
  Item shop entry expands (native <details>) to list the categories
- Sidenav: full-height flex panel (no sticky) — the top scrolls with the
  content and the bottom meets the footer's in every viewport, incl.
  fullscreen; group headings carry the outline icons (SVG sprite, 36px),
  home menu icons doubled to 36px via the same sprite
- FAQ and Client & PC setup moved under the New here group
- Current-page selection: bold + neon outline (with contrast) in the left
  menu; the bottom menu additionally wraps the selected entry in
  parentheses, e.g. (Features); Formats (dev) removed from the bottom menu
- Fix: a stray </script> after the WIKI_UI collector split the body script
  block, rendering the JS as a visible wall of text at the page bottom
…ages

- Sidenav: sticky below the top bar with its bottom at the screen edge while
  scrolling; on short pages it stretches to the content height instead of
  overflowing (align-items: stretch + max-height: calc(100vh - 70px))
- Bottom menu selection keeps the bold + neon outline but no longer wraps the
  entry in parentheses
- Sprite symbols: add fill="none" stroke="currentColor" + line caps — the
  2x icons rendered as solid black fills instead of outlines
- Language button: the menu now actually opens (it had no toggle handler) and
  lists a single entry, 'EN - English'; selection persists (wiki-lang)
- Main-menu categories: all 8 sidenav group headings are now links to new
  category overview pages (/menu/<slug>/, content/menu/*.md) that describe
  the category and link to each sub-page; group headings highlight when
  their overview page is open
- Sub-menus: <details> open state is remembered per browser
  (localStorage wiki-nav-open:<key>) and force-opens while the current page
  is inside the sub-menu's section
- All images sourced from the Open Perpetuum community wiki are out of the
  repo: 18 feature-page images, 35 robot model icons (incl. the GBF-extracted
  Scarab), the project logo and favicon. The official-backup snapshot had
  already been stripped from the git history and now lives outside the repo.
- static/img/placeholder.svg: outline robot glyph (stroke=currentColor) used
  everywhere an icon is needed but not defined — robot family pages (all 50
  families for now, via the RobotsPage placeholder fallback), the top-bar
  brand and the favicon. RobotIcons stays as the (empty) hook for licensed
  art.
- Feature pages: dropped the inline community-wiki images; dev notes no
  longer point at the old official-backup/ path.
- Footer attribution: logo/icon wording removed.
… unlimited vendor qty

Numbers (Md.Num):
- All generated tables now use engineering notation: 1000 -> 1k, 1001 ->
  1.001k (max 3 decimals, trailing zeros trimmed, k/M/G). Applied to item
  stats, shop prices, mission rewards/fees, tech tree point prices and ore
  amounts; values below 1000 are unchanged.

Mermaid transport diagrams:
- The finished product is now always the right-hand node (blue), the capsule
  the left one (green): capsule pages read 'capsule --yields/carries--> item',
  item pages read 'capsule --> item'. The right node links to the item page.

Tech tree (was one flat table, now a page tree):
- /content/techtree/ index with the 7 research categories
- one page per category (techtree/groups/<name>/) with a clickable mermaid
  tree of its chains (658 nodes total) and a node table
- one detail page per node (techtree/nodes/<item-slug>/): category, what it
  unlocks, parent, what it unlocks next, enabler extension, point prices
- robot nodes link to the robot catalog; node slugs match item slugs

Item shop:
- Vendor qty now shows the stock situation: '∞' when the vendor has no
  global limit (itemshop.globallimit NULL, per ItemShopEntry.CheckGlobalLimit),
  '240 · stock 600' when limited; explained on the shop index and category
  pages. All current stock is unlimited, so every row shows ∞ for now.

Icon visibility:
- SVG icons loaded via <img> cannot inherit page CSS colors, so the
  placeholder renders as a CSS alpha-mask (currentColor) in the top bar,
  robot pages and (explicit cyan) the favicon. The language button now has an
  explicit neon color in both themes.

Navigation:
- All 8 left-menu groups are collapsible (<details>) with full-width summary
  rows for a larger toggle target; open state persists per group
  (wiki-nav-open:<group>) and a group force-opens when the current page is
  one of its entries (longest-link match, so /features/research/ opens
  Industry & progress, not Data reference via the /features/ index link).
- Zone Map is a top-level entry, second in the menu (and in Data reference).
What an item stat value means and how big it is:
- Units: each documented stat field gets a unit (RP, CU, m, m/s, s, hp, %)
  and a one-line player-facing meaning. Only fields whose meaning is certain
  are documented; modifier and internal fields stay unitless (Formats lists
  every field).
- Scale: per stat, item count, smallest/median/largest value in the whole
  catalog, and a real item to open at each end.
- Powergrid/CPU context: the robot chassis capacity table (powergrid_max /
  core_max / cpu_max per frame, test/NPC/police frames excluded) so a module
  usage value can be read against the robot that carries it, with a worked
  percentage example (usage 214 on a 100 RP Ikarus vs a 6.6k RP Onyx).
- Capsule stats (e.g. landmine damage) link to the payload item, not the
  capsule entry.
- Linked from the item catalog index.
Client names:
- ClientNames.cs: static snapshot of the official client's English string
  dictionary, reduced to the 4,266 definition/zone keys whose display name
  differs from the derived name (of 4,338 resolved keys). Official client
  development is finished, so the strings are final.
- Verified byte-identical output: a full regeneration against the committed
  (archive-generated) content produced 0 differing files.
- Removed: Gbf.cs (900 MB archive parser), --gbf argument, Makefile WIKI_GBF,
  README/Makefile layout assumptions about ../Perpetuum.gbf.
- The generator now only needs the database and the plant rule files.

Stale page cleanup (pre-existing bug, found while diffing):
- The generator overwrote pages in place but never deleted ones that fell
  out of the catalog: 1,329 ghost item pages from the first implementation
  (fluxore, gammaterial, ... inventory pages) had been sitting in git and in
  the search index. make generate now removes its owned artifacts (items/,
  shop/, techtree/, the top-level generated .md files) before writing.
- Search index drops from 6,580 to 5,251 entries.

Link integrity:
- Stat reference and tech tree node pages now apply the catalog exclusion
  rules (NPC unit fits, ores, deployable structures) before linking, so
  deployable PBS foundation capsules render as plain text instead of dead
  links. 0 missing links / 0 bad anchors across 5,252 pages.
The wiki backlog entries (IMPROVEMENT-034/035/036) used to live in the
PerpetuumServer2 repository's docs/backlog/ as uncommitted working-tree
changes; per the standalone-repo rule the wiki's own backlog now lives here.
Reconstructed from the original entries and the work log; all three are
complete.
The 8 menu groups are back to flat, always-visible headings with their
entries listed beneath — no <details>, no persisted open state, no
force-open script. The item-shop sub-categories are an always-open
indented list again. The Zone Map top-level entry stays where it is.
docs/category-tree.md defines the full target menu/section tree with every
node marked existing vs planned. Key decisions:
- New 'How it works' (mechanics) group; 'Knowledge' becomes 'World & lore'.
- Robots become a per-robot generated catalog; modules get per-family pages.
- Stat reference joins the Data reference menu.
- No pages move until planned content exists (no URL churn).
- No ingestion from the community wiki backup: planned pages are written
  originally so the wiki stays eligible for a permissive license.
- IMPROVEMENT-037 tracks the work.
- The current-page highlight was exact-match only, so generated sub-pages
  (item pages, tech tree nodes, shop categories) highlighted nothing. It now
  takes the longest matching menu entry — the page itself or the deepest
  ancestor entry — so an item page highlights 'Content', a shop category
  highlights its own row, etc.
- The sidenav scroll position is kept per tab (sessionStorage) and restored
  on load, and after restore the highlighted entry is scrolled into view if
  it is not visible, so navigation can never hide the current
  category/sub-category.
The Zone map is already a top-level entry (second in the menu); the repeat
inside the Data reference group at the bottom of the menu is removed.
Styled as a nav-group (icon + uppercase neon label) in the same position —
second in the menu — instead of a plain entry.
All formerly adapted feature pages are now original or backend-verified
prose, so the copyleft attribution footer is replaced by an Apache-2.0
statement. Adds LICENSE/, updates the footer in config.toml, cleans stale
community-wiki CSS rules and updates the reuse-policy docs.
…extension tree; misc fixes

- ZonePages.cs: generate a dedicated page for every zone (82 total incl. the
  three hand-written worked examples), with facts, ore config, steady-state
  stock pie, TP connections, exit gates and back-links
- ZoneMapSvg.cs: per-zone teleport map (static/zonemaps/*.svg) plotting TP
  columns at real tile coordinates, landing spots from other zones and
  stronghold/arena exit gates; disabled columns drawn dimmed
- Zones map renamed to World (menu label + page title + back-links)
- Extensions tree: interactive SVG (static/extensions-tree.svg) of all active
  extensions by rank/category with prerequisite edges, using the shared
  zoom/pan wrapper (map.js now handles .zonemap-wrap and .map-zoom-wrap,
  plus touch pinch); new Extension tree menu group
- map.js: wheel zoom was already keyless; added pinch zoom, MutationObserver
  re-scan for late-rendered wrappers
- base.html: section hash only jumps on a fresh menu click (back button now
  restores scroll position); history.scrollRestoration=auto; mermaid render
  waits for the deferred script instead of dropping diagrams
- Steam: replaced the retired iframeguesturl embed with a storefront card
  (Steam CDN header image + store link)
- protection.md, sidenav, style: world map references and tree icon
Ore type rows on the ores index used to be in-page anchors (#titan, ...).
Each of the 19 ore types now has its own page under /content/ores/<slug>/:

- extraction facts (yield, solid/liquid, enabler effect, geo-scan document)
- link to the material's item page when it exists in the item catalog
  (ores with the CfOre category flag are excluded from the item catalog,
  so the link is emitted only when the catalog page actually exists)
- Where it spawns: every zone that regenerates the ore with its node
  configuration, each zone linked to its dedicated page; ores without a
  zone config (plants) say so explicitly
- Made from it: catalog items that take the ore as a recipe component,
  with amount per production and research level (non-catalog definitions
  such as transport capsules and robot fits appear by name only)

OresPage.cs reworked into Load/IndexBuild/OrePagesBuild; the ores index
keeps its per-zone generation tables but all ore references (jump line,
table, per-zone rows) now point at the dedicated pages. Zone pages and
the hand-written worked examples link to /content/ores/<slug>/ instead of
row anchors. Generator cleans ores/ before writing, like items/shop/techtree.
The wiki repo no longer needs to sit inside the server repo. A submodule
'server/' pins a PerpetuumServer2 commit and provides:

- the plant rule files: WIKI_PLANTRULES now defaults to
  server/src/Perpetuum.ServerService2/data/plantrules, so
  'git submodule update --init server' + 'make generate WIKI_DB=...'
  is all that is needed (WIKI_PLANTRULES still overridable for a
  live GameRoot)
- the server source and docs/db_structure for verifying generated
  claims without a second checkout

The site build itself (make build, CI) already needed no database or
parent repo; only 'make generate' used WIKI_PLANTRULES from outside
the repo. Client display names remain a static snapshot (ClientNames.cs).
Content was stale relative to the database:

- named{1,2,3}_sentry_turret / _gun / _unit_cprg and
  standard_engineering_remote_controller(_cprg) definitions are now
  disabled in entitydefaults; their item and tech-tree pages are
  removed, and sentry_turret(_unit)(_cprg) + the
  named/standard engineering remote controller pages are added
  (definitions renamed upstream)
- extensions.md, items index, zone map SVG and search index updated
  accordingly (5,356 search entries)

Generated via 'make generate' with WIKI_PLANTRULES from the new
server/ submodule (identical output to the previous GameRoot path).
The workflow only triggered on push to main, so PRs (including the
feature/implementation merge) had no automated check. It now also runs
on pull_request; the Pages deploy job is guarded to main pushes only,
so PR runs are build-only and never touch the Pages environment.
The GitHub runner is non-root, so 'tar xzC /usr/local' failed with
permission denied. Extract the release tarball into
$GITHUB_WORKSPACE/tools and prepend it to $GITHUB_PATH instead.
$GITHUB_PATH only takes effect from the next step, so the
'zola --version' sanity check in the same step still saw
command-not-found. Export PATH in-step; keep the GITHUB_PATH line
so the Build step also finds zola.
clouths added 25 commits October 2, 2026 01:03
The teleport maps now show each zone's actual terrain instead of a
procedural stand-in. tools/gen_zone_teleport_maps.py reads the zone's
altitude layer (2 bytes/tile little-endian; the game's own layer files,
custom-layers/*.bin with the .gbf archives as fallback — decoded with
the server's extract_gbf.py) and renders two 128x128 modes per zone:

  height  hillshaded monochrome heightmap (real island shapes)
  color   altitude color ramp + the accurate island coastline (the
          Island flag of the blocks layer)

The SVG carries both as <image> layers; zone-map.js adds a Height/Color/
Plain switcher and remembers the choice (localStorage) so it applies to
every other zone map too. Zones without layer data fall back to the old
embedded procedural heightmap. make zonemaps mounts the sibling
PerpetuumServer2 checkout read-only; all 80 maps currently have real
terrain (5.6 MB of PNGs, the SVGs themselves shrank to 0.18 MB).
map.js now attaches the same wheel-zoom/drag-pan/pinch controls to
mermaid diagrams as to the maps: per-wrap state survives theme-toggle
re-renders (the svg is swapped in place), a reset button is created for
each diagram box, and the 25% area-budget clamp applies. Pie charts are
excluded — small, dense, and their wheel-zoom was just in the way, so
the wheel keeps scrolling the page over them.
The <800px rule gave main an extra 1rem of side margin on top of the
.layout inset, so the panel sat 16px right of the footer below it.
Drop the extra margin — .layout and the footer already share the same
1.2rem inset, so the panel edges now line up top to bottom.
  * roads: the zone's Highway tiles (control layer, ushort/tile, the
    Highway flag from the server's TerrainControlFlags) are now drawn in
    both display modes. The earlier 'roads in the water' was a data
    misread — every road tile is on land; the coastline is now derived
    from the altitude band instead of the blocks Island flag, which
    proved too sparse to use as a land mask.
  * resolution: 128 → 512 per mode (tiny 256×256 zones keep native
    size), so terrain, roads and the island edge read crisply.
  * points of interest: teleport columns / landing spots / exit gates
    get a 1.5× radius and a 1.25× stroke; labels grow 1.3×, lift to
    match, and get a dark halo (paint-order stroke) so they stay
    readable on any terrain. A data-zm-v2 marker keeps the script
    idempotent now that the sizing is multiplicative.
  * modes: Plain is gone — Height and Color only (the stored preference
    still normalises old 'plain' values to 'height').
  * the generator now runs one zone per process (multiprocessing; the
    per-tile loops are Python bytecode, so threads would not help):
    80 zones in ~7s on 32 cores instead of ~100s.
The 160 generated PNGs (~51 MB) leave git; the repo keeps only the
small SVGs plus derived PNGs for the 20 zones whose layer data has no
public source yet (it ships only in the game client's Perpetuum.gbf,
which needs a Steam account).

tools/fetch_zone_layers.sh pulls what is publicly available — the same
sources the PerpetuumServer2 CI uses: the original zones' .bin layers
from the Dedicated Server installer (Steam app 693060, anonymous) and
the gamma/custom zones from the public Drive archive. Both parts are
verified working.

CI (test + build jobs) caches .assets and runs the fetch + generator
before zola build. Locally, make zonemaps prefers a PerpetuumServer2
checkout and skips the download when one exists; the Docker wrapper is
gone (pure python3).

The generator keeps the committed fallback PNGs for zones without
layer data instead of downgrading to the procedural placeholder.
The old check looked for a 'pie' class on the rendered svg — this
mermaid build leaves the svg root unclassed, so the exclusion never
matched. Detect pies from the pie-specific inner elements (or from the
diagram source before the render), and strip the zoom affordances if a
pie is only recognized after the handlers attached (mermaid inserts the
svg shell before its content).

Also fixes the reset button vanishing after a mermaid re-render (theme
toggle): the re-render wipes the button, so it is re-created in the
re-render path. Covers a regression: an injected pie chart must keep
the wheel scrolling the page, ignore drags, and show no reset button.
…, extension categories

- Character: new /features/character/ page (character window: extensions tab
  and sparks); Extensions moves under it in the nav (as in the game) and the
  loose 'Extension tree' entry leaves the Reference group
- Recipes: the flat component table becomes category sections of
  reward-style cards, each linked to the item's own page (tools/
  gen_recipes_cards.py, mirrored in RecipesPage.cs)
- Item pages: 'Production' mermaid section showing what can be built with
  the item (capped at 8 products, rest to the recipes page; tools/
  gen_production_pages.py, mirrored in ItemsPage.cs)
- Extensions page: 'Main categories' section with a new overview SVG —
  the 15 skill categories as boxes (count, entry points, rank range) with
  an arrow for every cross-category prerequisite (tools/
  gen_extension_categories.py + ExtensionsCategories.cs)
- Zone maps: teleport links were missing the trailing slash (regex group
  stopped before the table link's '/'); regenerated all 80 SVGs
- Mobile drawer: the backdrop-click listener closed the drawer the instant
  the hamburger opened it (the button sits outside #sidenav); the test now
  taps the backdrop with a raw mouse click (it is a body::before)
- tools/regen_search_index.py moved out of /tmp; search index refreshed
- README: document the no-database content tools
…s, spark tree, nav rework

- Extensions page: per-category card sections (rank, price, bonus,
  prerequisites) replace the 251-row table; Research link removed;
  sparks referenced from the intro.
- Item pages: reverse production tree (components -> item, with
  amounts and research level) plus forward 'Used in production'
  sample; CT-capsule and ore pages untouched; disabled/hidden
  definitions no longer produce 404 links.
- Recipes page stays the index (card layout); each recipe's data now
  also lives on the produced item's page.
- New spark connection tree (static/sparks-tree.svg, 47 sparks -> 21
  bundles) embedded on the sparks page; categories overview now uses
  rank bands (left = starting at rank 1) and leaves sparks out.
- Nav: Sparks under Character; Research becomes a sub-group holding
  the Tech tree; Lore moves to the top of the World section. Menu
  pages follow.
- Generator now writes tools/recipes_data.json (components +
  itemresearchlevels + display names) so the Python one-shot tools
  stay byte-identical with the C# output; plants/robots mermaid
  fences fixed to column 0.
…ap, in-zone TP hover lines, clickable category boxes

- zone pages: teleport map moved to right under the title (generated +
  3 hand-written pages); caption explains the hover lines when present
- zone maps: hidden dashed lines per local (in-zone) teleport pair, lit
  while the cursor is near one of the pair's columns (hysteresis, screen
  px so it tracks zoom); endpoint circles carry the data-ltp tokens
- world map: every zone drawn with its real terrain (world.png, 128px
  color-mode downscale of the zone map) over the family-color outline;
  in-zone teleports carry endpoint dots + hover lines; sub-sections are
  zone cards (coastline thumb.png, name, type/size)
- gen_zone_teleport_maps.py: emits world.png + thumb.png per zone (real
  data, fallback downscale, or fBm) — same altitude grid, so the shape
  matches the full maps
- extensions: the detail tree section is gone; the category overview is
  embedded inline and its boxes link to the per-category sections below;
  card values (rank, credits, bonus) are bolded and colored
- gen_extension_categories.py rewritten byte-identical to the C#
  generator (verified C#-then-Python produces no diff)
- WriteRecipesData carries over the items/ores sections maintained by
  gen_production_pages.py (previously blanked)
- tests: ltp hover, world terrain + cards, extension box links +
  emphasis (64 passed / 54 skipped)
… TP lines, spark family graph, always-visible in-zone TPs, internal link checker

- Rename the 'Starter islands' map section to 'Alpha' (group id, nav
  label/key, protection table, zone-page back-links).
- World map: drop the teleport-connections and exit-gates tables (the
  map itself carries the data); islands now use the index-card design
  (coastline thumb.png over the dark family-outlined plate); inter-zone
  TPs are dashed lines gradient-colored source-family → destination-
  family (per-line userSpaceOnUse gradients); no more in-zone TP
  hover lines on the world map.
- Zone maps: in-zone teleport dashed lines are always visible (hover
  only brightens them); captions updated.
- Anchor jumps (category/family boxes) now land below the two-row
  sticky topbar (scroll-margin-top).
- Sparks page is now fully generated (SparksPage.cs + SparksFamilySvg.cs):
  hand-written prose + a compact family overview graph (6 clickable boxes
  linking to the per-family sections) + 47 per-spark cards (unlock cost /
  standing, switch cost, bundled extension levels) + the connection tree
  further down. tools/gen_spark_families.py is the byte-identical offline
  mirror (C# generator remains the source of truth).
- OresPage.ItemLink: only catalog items get item-page links (robot fits
  and PBS capsules have no item page — 97 broken links on the plants
  page, caught by the new link checker).
- gen_zone_teleport_maps.py: emit world.png/thumb.png for world-map zones
  without a teleport SVG (the strongholds).
- tests/links.spec.js: exhaustive internal link + anchor sweep over all
  5,300+ built pages (Node-side; a browser crawl is too slow and gets
  paused by Playwright's watchdog). tests/sparks.spec.js: family boxes,
  card counts, emphasis, click-to-scroll.
- 70 passed / 54 skipped / 0 failed.
The family cards (unlock cost, switch cost, bundled extension levels)
replace it — the 47-row bipartite SVG was hard to scan and duplicated
what the cards now state per spark.

- SparksPage.cs: drop the '## Spark connection tree' section and the
  #tree references (family intro sentence, card prereq link)
- Program.cs + SparksTree.cs + static/sparks-tree.svg deleted
- gen_spark_families.py: generated block now ends at
  <a id='switching-sparks'>; intro line updated (byte-identical to C#,
  re-verified)
- sparks.spec.js: asserts the tree is gone
…line

The raw tierlevel number ('Tier | 3') meant nothing to someone who has
never seen the tier system. Now:

- Md.Tier renders the tier as T1–T5 (the client's convention —
  def_elitet4_* is tierlevel 4, def_elitet2_* is 2, so display = level:
  standard = T1, named1 = T2, named2 = T3, named3 = T4) with series
  markers: 'T3 (prototype)' for prototype variants, 'T4 (special)' for
  elite/artifact/faction lines.
- Item pages of tiered items gain a 'Tier line' row: the whole
  standard → named1 → named2 → named3 chain as links with tier badges,
  current item bolded (1,306 pages affected, incl. the _pr prototype
  variants of each tier).
- The items index legend now explains T1–T5/prototype/special (it used
  to describe the internal tiertype codes, which confused things).
- production.md: new 'Tier progression' section stating the rules
  explicitly — each tier is its own tech-tree node (tiers must be
  unlocked in order, no making a higher tier without research), each
  higher tier consumes one specimen of the previous tier (a T1 can
  never be turned in for a T3; the specimen is consumed, not
  converted) — with the small miner module line as the worked example.
- tests/tiers.spec.js: T-row format, tier-line chain/links/bold on the
  T1 and T3 pages, and the rules section on the production page.
The committed (batch 3) copy was the Python json.dump(indent=1) form;
make generate rewrites recipes/names in the generator's compact layout
and carries over the items/ores sections. Content is byte-identical
modulo formatting — all four sections verified equal by key. The
Python tool's json.dump rewrite keeps producing the same data (it only
re-reads what the pages carry), so this is purely the file's resting
format between regenerations.

Also re-ran make zonemaps (the previous commit had captured the raw
pre-zonemaps SVGs — the last make generate overwrote them and the
commit ran before the zonemaps step). Zone-map tests re-verified green.
…cards, deployables pages

B5 batch, all 14 items:
- zone map local TPs already family-colored (verified)
- probes mermaid: drop the junk-node parenthetical
- tech tree: per-root-line mermaid graphs behind a chip index
  (like the missions page); chips are raw <a> HTML because Zola does
  not parse markdown inside HTML blocks
- spark family SVG: 3 columns x 2 rows (was 6 across)
- sparks page: Switching sparks now above the family catalog
- abbreviations: EP/CT/Proto links fixed, robots terminology link fixed
- nav: Recipes under Production, Missions data page under Play/Missions
- Named1/2/3/4 tokens in derived names -> T2/T3/T4/T5 (matches the
  tierlevel the client shows; 314 display names changed, slugs untouched)
- item pages: 'Produced from' mermaid tree -> component cards
  (icon + name left, required amount in body); 'Used in production'
  keeps its mermaid; C# and the Python tool stay byte-identical
- ores index: per-zone node generation section removed (data stays on
  each ore page's Zone parameters table)
- items catalog index removed (Zola render:false keeps it a 404);
  '[All items]' footers and stale links cleaned up
- deployables: card index by category + one page per deployable
  (220 pages) with capsule<->object twin links
- zone pages: 'Connections' section removed (zone map + world map
  already show the TPs)

Also: gen_production_pages.py rewrites recipes_data.json in the C#
line-per-entry shape (was a 90k-line indent=1 dump), and the
production section now appends at EOF (the [All items] anchor is gone).
… pages

- gen_zone_teleport_maps.py: ATTR regex allowed digits (x1/y1/x2/y2 were
  dropped, ltp lines degenerated to points) — regression test added
- ZoneMapSvg.cs: ltp endpoint labels exclude the zone's own name
- ZonesMapPage.cs: TP lines drawn OVER the islands with endpoints at the
  real column/landing positions (AVG(x/y) within each zone); gate links
  anchored at the gate position; FamilyPages() generates
  content/zones/{alpha,beta,gamma}.md (gamma with T0-T4 sections); the
  map page keeps only the map + legend + links to the family pages
- Md.Header(): extra front-matter param (nested under extra:, as Zola
  0.23.6 requires — top-level custom keys never reach page.extra)
- ZonePages.cs + hand-written zone pages: extra.family (alpha/beta/gamma)
- base.html: World group reordered (Lore, Training, Protection, Alpha,
  Beta, Gamma with T0-T4 sub-list); body[data-zone-family] + nav
  highlight for the matching family-listing entry
- map.js: attachTpLines() — inter-zone TP lines are nearly invisible at
  rest on desktop pointers and fade in near the cursor (point-to-segment
  distance in viewBox coords, works through zoom/pan; touch layouts keep
  the plain 0.65 opacity)
- tests: nav/map/zone-map updated for the new structure; new tests for
  family highlighting, family-listing cards, TP-line proximity, TP line
  anchoring/on-top order. 82 passed / 0 failed / 60 skipped
… cards

- ZonesMapPage.cs: the world map flipped the y axis, but the game grid
  grows downward (zone teleport maps plot DB coords directly on the
  terrain PNGs and align) — gamma is at the TOP of the map, the starter
  islands at the bottom, like in the client. Zones without any inter-zone
  teleport connection (strongholds, PvP arena, dead-end gate islands) are
  no longer plotted; gate links follow their source islands.
- World map TP lines: rest opacity 0.07 -> 0.25 on desktop (faded but
  visible); hover-reveal unchanged.
- Home page: the "Get the game" and "Watch first" sections moved from
  getting-started.md (first content after the intro paragraph); new
  home-card grid of high-level topics (Movement, Gathering, Missions,
  Combat, PBS, Intrusion, Production, Research, Market, Robots, Sparks)
  with high-contrast names; the World Map card takes a full-width line
  with a low-res render of the real map (static/world-map-thumb.png,
  shot from the generated map SVG) and the label centered over it.
- Mobile: the hamburger now floats bottom-left (fixed) instead of taking
  a topbar slot; dimmed while the drawer is open.
- tools/shot.js: screenshot helper (url, out, --width/--height/--scale,
  --full, --clip, --wait, --idle, --mobile, --eval).
- tests: map.spec.js updated (59 plotted nodes, rest opacity 0.25, hover
  test scrolls the line into view — the map is taller than the viewport);
  zone-map failures were stale SVGs (pipeline order: generate -> zonemaps).
  Full suite: 82 passed / 0 failed / 60 skipped.
…olumn

- Desktop: the topbar is a single row — brand left, then the chrome
  icons (language/palette/theme) and the search bar at the right end.
  Mobile keeps the two rows (row 2 = search).
- Wide screens: the content column (main + footer, shared 1050px width)
  is the centered element; the sidenav sits to its left and is not part
  of the centered block (.content-col centers a 1050px grid column).
- Sidenav sticky offsets and anchor scroll-margins follow the shorter
  one-row topbar (mobile keeps the old two-row values).
- Mobile: the three chrome buttons are hidden while the sidenav drawer
  is open (the drawer covers that corner of the topbar); the hamburger
  goes back to its topbar slot (the floating bottom-left variant will
  come back with the work needed to make it right).
- tests/sparks.spec.js: the anchor-scroll assertion measured the topbar
  height instead of hardcoding the old two-row height.
…rawer

- Progressive width test (2560 -> 320px, 22 breakpoints): the search bar
  must be visible and non-overlapping at every size. Found and fixed:
  - the desktop-only .topbar-search display rule hid the search entirely
    on mobile (width 0) — it is a row-2 element on mobile again
  - 801-900px: the search (basis 560px, shrink 1) overlapped the chrome
    icons; now .topbar-main flexes (shrink 1) and the search shrinks
    first (shrink 3, min-width 140px), so only the brand ellipsizes,
    never an overlap
- Mobile drawer: instead of hiding the three chrome buttons (language /
  palette / theme) while the drawer is open, they reposition inside the
  drawer just below the Home quick-link (fixed at the measured 4.2rem /
  z 46 above the drawer).
- Full suite: 82 passed / 0 failed / 60 skipped.
- Topbar brand renamed to "OP Wiki" (config.extra.ui.brand; the full
  site name stays the page <title>).
- World map opens pre-zoomed (initContentZoom): the bounding box of the
  plotted islands is scaled to ~88% of the view and centered. Verified
  browser mapping: screen = (rc - t) + s*P with rc the element-box
  center — the box center lands within 0.0px of the wrap center. The
  wheel caps at s=3 until the user pans (INITIAL_MAX), so the whole
  canvas stays reachable; reset returns to the pre-zoomed resting state.
- Zone labels: moved ABOVE each island (centered), display names instead
  of definition names (zone_gamma_tc_z106 -> "Gamma TC Z106"; the DB
  has no client display names, so they are derived), and the font is
  scaled per zone so a label never overhangs the island's own width —
  the map reads clean at rest, zoom in for the small islands.
- Home page world-map card thumbnail: re-rendered at the card's own
  aspect (tools/gen_world_thumb.js), zoomed onto the alpha starter
  zones centered with the beta edge on top, no zone names.
- "Get the game" and "Watch first" (incl. the click-to-play script)
  moved from the home page to Getting started, ahead of the character
  steps.
- Wheel zoom now truly anchors on the mouse: the old zoomAt computed
  the fixed point as (C - C0)/s instead of (C - C0 - t)/s, so once a
  pan had moved the map (t != 0) the wheel zoom drifted toward the
  center. Re-derived the transform math (screen = C0 + t + s*(p - C0),
  C0 = rect center - t) and added a regression test that verifies the
  content point under the cursor stays under it across successive
  zooms. Applies to the world map, zone maps and mermaid diagrams.
- Zone maps: exit teleports are now drawn as dashed lines from the
  column to the map edge, pointing the way the destination zone lies
  on the WORLD MAP (world-grid delta, y-down like the zone map), in
  the destination's family color — so a glance tells you the TP leaves
  the zone, which family it is, and roughly where.
- Color-mode contrast: local-TP dashed lines and the landing-spot
  circles get a dark casing underneath, so the dashes no longer blend
  into the bright terrain in color display mode.
- Nav: the Ores and Plants content pages move under Gathering (Play),
  out of the Reference Content list.
- Remove the Reference group: abbreviations merged into FAQ (page deleted),
  zone generation moved to World > How it works, zone index to World as
  All zones, deployables under Play > Bases & sites, server/formats into
  the new Dev group; /menu/reference/ deleted
- New sidenav tree: Start(4), World(6), Play(6 sub-groups), Systems(6
  sub-groups); shop categories become a 3rd-level sub-list under Market
- Dev mode: Play/Dev knob in the top bar (persisted in localStorage,
  no-flash early script) toggles html[data-mode=dev]
- Dev mode = matrix theme (near-black, glowing bright green text/borders)
  + the Dev sidenav group (hidden in play mode): Developer home,
  Server & reference, Formats
- New /features/architecture/ dev home: high-level architecture overview
  (client/server/DB/zone files) from a dev/DevOps point of view
- Update config.toml nav strings, docs/category-tree.md, search index;
  nav tests rewritten for the new tree + dev-mode coverage

Tests: 87 passed / 0 failed / 65 skipped
Multi-destination teleport endpoints (zone-map columns, world-map
islands) no longer guess a destination on click:

- C#: circles carry data-dests (external dests) + data-eid; exit lines
  carry data-tps (source eid) + data-tpd (dest). World islands carry
  data-dests + data-zn; TP lines data-tps + data-tpd.
- Python: multi-dest columns are left UNLINKED (no <a>); single-dest
  keeps its plain link; LTP-only endpoints carry no data-dests at all.
- map.js: attachTpSelector() opens a destination popover on hover
  (desktop) / tap (touch). While open, the item's dashed lines are
  emphasised; hovering one entry emphasises only that line (scoped by
  source, so shared destinations of other columns stay dim); clicking
  an entry navigates to the destination's zone page.
- Zone pages (generated + 3 hand-written): a small horizontal world-map
  card above the zone map linking to /zones/map/.
- Nav: How it works keeps only Zone generation (protection levels is
  the head link); PBS sub-list gains the Power base stations category;
  Deployables sub-list lists its 10 main categories as anchor links;
  Groups renamed to Alliance / Corporation.

Tests: +5 (zone selector, single-dest links, LTP no-action, world
selector, nav structure); 92 passed / 0 failed / 70 skipped.
… color-mode line contrast

- Zone index: family card grid (the gamma family-page design) instead of
  the stat table, listing only the 59 zones with at least one teleport
  point to another zone (the world map's connection set); strongholds,
  the arena and dead-end gate islands are dropped.
- Zone meta for the map tool: the index no longer carries the table
  tools/gen_zone_teleport_maps.py parsed, so the generator now writes
  static/zonemaps/meta.tsv (name, display name, slug, id, size) and the
  Python tool reads it (table parsing kept as fallback).
- Gamma family page: Training no longer listed — the tier loop matched
  every id starting with "t", which includes "training".
- TP selector: entries now carry the destination DISPLAY names (the same
  strings the column labels show; data-dests is ;-separated
  display|zone pairs); the selector also opens when hovering the column
  label. The world map keeps its plain island links — no selector there.
- Deployables index: the Power base stations category is no longer
  listed (the PBS page documents those; per-deployable PBS pages are
  still generated); nav: PBS is a plain entry again, Missions renamed
  to Mission list.
- Nav: a gamma zone page highlights its tier entry (T1-T4, computed from
  the zone number — kept in sync with ZonesMapPage.GammaTier), not the
  Gamma head.
- Color render mode: the TP lines (ltp + exit) are darkened (CSS filter
  on the wrap's zm-color class) and drawn 1.7x thicker (zone-map.js
  restores the original widths on exit) for contrast on bright terrain.

Tests: zone-index/cards, selector display names + label hover, tier
highlight, deployables nav, sub-count 16; world-map selector test
removed. 93 passed / 0 failed.
…orld-map ones

The zone cards (family pages) reference /zonemaps/<slug>/thumb.png for
every zone of the family, but gen_zone_teleport_maps.py only created
world.png/thumb.png for the slugs referenced in content/zones/map.md
(the world map). Since TP-less zones (strongholds 71/72, dead-end gate
islands, the arena) no longer appear on the world map, their thumbnails
were never generated on a clean checkout -> 404 in CI's link test.
Locally the stale files from an earlier run masked the break.

The fallback set now comes from static/zonemaps/meta.tsv (the full zone
list the generator writes), minus the zones that have their own
teleport SVG; the map.md scan is kept only as a legacy fallback.

Verified with a full clean rebuild (no public/, no generated zonemap
assets): generate -> zonemaps -> build -> 93 passed / 0 failed.

Also re-encodes static/search_index.json as written by make generate
(C# SearchIndex) instead of the Python re-emitter — byte encoding of
embedded quotes differs, parsed content is identical.
In dev mode the Home quick link and all play-mode groups (Start, World,
Play, Systems) are hidden — the sidenav contains just the Dev group.
Play mode is unchanged.

Also fixes a latent template bug this exposed: the Systems group's
</ul></li> was missing, so the Dev group's <li> was actually nested
inside the Systems item list (the browser auto-closed both at </nav>).
In dev mode the Dev group used to render at the bottom of the Systems
group; it is now a proper top-level sibling.
@clouths
clouths marked this pull request as ready for review October 4, 2026 22:33
@clouths clouths changed the title WIP: Open Perpetuum wiki (static site, Zola) Open Perpetuum wiki (static site, Zola) Oct 4, 2026
@clouths
clouths merged commit c5157a7 into main Oct 4, 2026
7 checks passed
@clouths
clouths deleted the feature/implementation branch October 5, 2026 22:46
clouths added a commit that referenced this pull request Oct 5, 2026
CodeQL alert #1: DOM text from data-dests flows into window.location.href
via hrefOf() without sanitization. The fix strips any character not in
[a-z0-9-] so a tampered value can never produce a non-zone path or a
scheme switch (e.g. javascript:).

Adds a Playwright test that opens a multi-dest TP selector, clicks a
destination, and asserts the navigated URL matches /zones/<safe-slug>/.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants