Loading…
a327ex.com

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 #else web-stub set (the 15 l_video_* + a no-op video_pump_all); video_load returns 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), and serve-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 to lib/libfreetype-web.a), wired into build-web-engine.sh (dropped -sUSE_FREETYPE). Added lzw/bzip2 modules to resolve FT_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-in web_native_resolution (config setter + Emscripten branch in engine_init setting game_width/height to SDL_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_fit on a VFS miss kick emscripten_async_wget (-> MEMFS) + set web_images_dirty; renderer polls images_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.lua reads dims from there instead of probing image_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_fit wrapped the handle in an image() table even when the handle was nil (pending fetch) -> returned a non-nil image with no texture -> ensure_fit cached it and never retried. The homepage escaped it by rebuilding fresh elements. Fixed: return nil when no handle. Fixed in BOTH renderer/anchor/image.lua and 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); wired canvas_link_at into homepage_draw over each message doc, made open_link global. 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_rects at 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 logs row (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) and framework/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.