Anchor Website 10
Opus 4.8
Summary
Moved the in-engine Anchor 2 renderer of a327ex.com onto the web. By session end it boots in a browser, renders pixel-identical to the desktop app, and lazily loads the full media set. Also did a domain-transfer research pass and a homepage visual-fidelity pass against the live site.
Domain transfer (research):
- Goal: get a327ex.com onto Cloudflare (proxy + R2 zero-egress video). Domain was bought on Netlify; Netlify Domains is white-labeled Name.com, so the documented exit is an account-to-account "push" into the user's own Name.com account (not an EPP transfer), gated on Netlify support (backlogged — a June 12 2026 forum thread is the same case, 5+ days unanswered).
- Key reframe: web delivery only needs nameserver delegation to Cloudflare, not a registrar transfer (the transfer is optional, at-cost renewals only). But you must control the domain to delegate NS, so the Name.com push is the one unavoidable step.
- Drafted the Netlify support-ticket text (account code 3275770-0c7699f) + gotchas (60-day locks x2, DNSSEC, clientTransferProhibited) and the Cloudflare transfer steps (unlock, disable WHOIS privacy, EPP code, add to Cloudflare, change NS, ~5 days, +1yr renewal).
- By end of session the owner confirmed the Netlify->Name.com push completed. NEXT = Name.com->Cloudflare, then R2.
Engine -> WASM (shared Anchor2/engine/src/anchor.c):
- Engine no longer compiled to WASM (libav linked unconditionally; the May-3 build-web-engine predated the video subsystem). Guarded the entire libav Video subsystem under
#ifndef __EMSCRIPTEN__with a#elseweb-stub set (the 15l_video_*+ a no-opvideo_pump_all);video_loadreturns nil so the renderer keeps the poster. Compiles to WASM again; nothing else needed guarding.
Renderer web packaging + first boot:
renderer/tools/build-web.sh(file_packager preloading only anchor/, ricochet/, assets/, data/, root .lua; excludes the 5 FFmpeg DLLs + 42GB media-cache),web-shell.html(full-window canvas, always-run, keyboard-focused), andserve-web.py(no-cache dev server). Confirmed the engine on web defaults cwd=/and reads/main.lua(argc==1, no game-folder arg). Booted.
Font parity (the long thread):
- Web fallback was a 21KB SUBSET Arimo (desktop always hit real Arial) -> body text visibly wrong. Bundled the real Arial family into
assets/, theme.lua SANS points at it on both sides (web==desktop; metric-clones read as "not Arial" to the owner). - Text still lighter/softer on web: desktop links its own FreeType 2.14.1 (bytecode + subpixel hinting), web used Emscripten's older
-sUSE_FREETYPE. Fixed by vendoring + compiling FreeType 2.14.1 for web:engine/setup_freetype_web.sh(downloads source, compiles the module set tolib/libfreetype-web.a), wired intobuild-web-engine.sh(dropped-sUSE_FREETYPE). Added lzw/bzip2 modules to resolveFT_Stream_OpenLZW.
web_native_resolution (new engine config flag):
- Text was "over-anti-aliased." Root cause: engine renders at the fixed logical
game_width x game_height(1920x1080) and SCALES that to the window (scale = window/game); on the smaller browser canvas that's a downscale -> blur + letterbox. Added opt-inweb_native_resolution(config setter + Emscripten branch inengine_initsettinggame_width/heighttoSDL_GL_GetDrawableSize); shell fills the canvas to the window. 1:1 crisp, fills width. Opt-in so fixed-res pixel-art games (arena) are unaffected. (Mouse mapping was fine — sidebar clicks worked.)
Lazy media (the 421MB content-image set):
- Posters (6.5MB) + content images (
../media/..., 421MB / 512 refs) 404'd on web. Serve media statically (dev server routes/media->site media,/media-cache->posters; = the R2 path) and fetch on demand. Engine web image path:image_info/texture_load/texture_load_fiton a VFS miss kickemscripten_async_wget(-> MEMFS) + setweb_images_dirty; renderer pollsimages_dirty(). - Perf passes after "works but hitches": throttled relayout (0.25s), then a per-frame
image_decode_budget(2/frame, web). Still hitched (everything fetched/decoded at page open). Final fix = build-time dimensions:tools/probe_image_dims.py(stdlib header parsing) ->data/image_dims.lua;canvas.luareads dims from there instead of probingimage_info, so fetches defer to the draw path (on-screen only) + decode spreads over the scroll. Relayout-on-arrival became conditional (only when a runtime image_info set a new box size).
image_load/image_load_fit nil-handle bug (framework):
- Post images didn't show on first click into a post (only on revisit / on the homepage when scrolled). Root cause:
image_load/image_load_fitwrapped the handle in animage()table even when the handle was nil (pending fetch) -> returned a non-nil image with no texture ->ensure_fitcached it and never retried. The homepage escaped it by rebuilding fresh elements. Fixed: return nil when no handle. Fixed in BOTHrenderer/anchor/image.luaand the Anchor2 framework source.
Font bundling — exact match (web==desktop==live):
- Remaining gaps were unbundled faces: tweet cards + ARTIFACTS banner (Segoe UI), CJK (Yu Gothic -> boxes), symbols (Segoe UI Symbol -> boxes). Per the owner ("bundle the desktop fonts so it's exactly the same"), copied Segoe UI (regular/bold/italic/semibold), Yu Gothic (R + M .ttc), Segoe UI Symbol into
assets/, theme.lua points at them (removed C:/Windows/Fonts branches). Yu Gothic pair = 28MB (flagged: subset before real web delivery). ARTIFACTS banner -> real Segoe UI Semibold.
Homepage visual-fidelity pass (vs live, same-theme dark):
- Re-converted content (
convert.lua --all, 362 pages) to refresh the feed. - Links: homepage inline links weren't hit-tested (logic was
view=='article'only); wiredcanvas_link_atintohomepage_drawover each message doc, madeopen_linkglobal. Wired crumb social links (Steam/GitHub/X + mailto) and artifact rows (It Follows->/posts/it_follows, SNKRX/BYTEPATH->Steam) as real links. Styling to match live: crumb gray+underlined; artifacts hover->blue (no underline); inline prose links blue+underlined by default (drawn from_link_rectsat the baseline). (Live hover affordance is brightness(1.15), not underline.) - Sidebar chevrons: filled triangles -> thin two-stroke chevrons (
layer_line), smaller/bolder/lefter, in the gutter with section text left-justified to the nav. - Labels: ARTIFACTS = real spec (Segoe Semibold ~9px, 0.16em letter-spacing via a tracked draw, uppercase, medium); feed tags (STORY/GAME/AI LOG) 0.85em -> ~10px italic.
- Spacing/alignment: tightened sidebar sections, loosened the feed, aligned the ARTIFACTS box-top to the sidebar
logsrow (crumb_gap from content_top=108, hp.top=40), nudged the crumb up. Final ARTIFACTS 9px / 1.4 tracking after 8px/1.2 read too small.
State for next session:
- Engine changes in shared
Anchor2/engine/src/anchor.c(+ setup_freetype_web.sh, build-web-engine.sh, lib/libfreetype-web.a) andframework/anchor/image.lua— committed to Anchor2 this session. - Renderer changes committed to a327ex-site.
- NEXT: Name.com->Cloudflare transfer, then R2 media serving (range-stream the 112 owned videos), then the rest (per-post fidelity, remaining visual passes, the real
<video>web backend, the music-video DMCA call). Yu Gothic .ttc needs subsetting before real delivery.
🔒 Only the summary of this log is public. Private because it contains too many website internal details.