Anchor Website 14
Opus 4.8
Summary
Continued the Anchor Website renderer (a327ex.com reimplemented inside the Anchor 2 engine). Three arcs: (1) full MOBILE SUPPORT for the engine renderer; (2) wiring the content-adding skills to sync the engine renderer (not just the Lua server); (3) building the BOX2D LETTER-DROP announcement effect — in progress, local-only.
Mobile support (Anchor2 engine committed 1825c77, renderer prod 5677ef1), 3 device-tested increments:
- Increment 1 (touch): SDL_FINGER* → the existing mouse/scroll model in SHARED
Anchor2/engine/src/anchor.c. Tap = a 2-step synthetic click (press one fixed-step, release the next, so update-timemouse_is_pressed/releasededges fire); one-finger drag = 1:1 scroll intotouch_scroll_dy, drained by newengine_get_touch_scroll(), routed to the pane under the finger. Disabled SDL touch→mouse emulation (SDL_HINT_TOUCH_MOUSE_EVENTS=0). Newengine_get_css_size()/engine_get_dpr(). Removed the engine-boot.js mobile bail. Temporary green DEV_HUD. Device test foundgame == css(384×696, dpr 2.81) — Emscripten defaults to a CSS-px backing store; the previous instance's "phone reports 1170 physical px" assumption was WRONG (breakpoint already worked, text was DPR-1 soft). - Increment 2 (crisp DPR):
web_native_resolutionnow sizes the canvas BACKING STORE to physical px (CSS×DPR viaemscripten_set_canvas_element_size, factored intoweb_target_backing()), DPR capped at 2.0 (WEB_DPR_CAP). Compositor +mouse_to_game_coordsmade backing-authoritative on web (window_w = game_width; mouse identity since Emscripten scales mouse to the backing) — fixed a left-shift/black-bar blit bug (compositor had trusted SDL's stale CSS window size). Renderer: globalpx(n)=round(n*UI_S),UI_S = width/css(dpr on web, 1 on desktop → byte-identical), defined before the content modules are required; wrapped theme fonts/metrics/gaps (theme_set_dpr), main.lua consts, homepagehp, sidebar, elements, canvas. Breakpointwidth < MOBILE_BP*UI_S. - PERF (the big win): homepage was 27fps / dc=1622 (a text post was 60fps/dc=17). Cause: the feed drew ALL ~330 link rows + 87 message date-rows every frame, each font switch breaking the batch. Fix (1) cull off-screen feed items → dc ~47, 60fps. Fix (2) VIRTUALIZED feed —
homepage_buildstores metadata only (no doc load / no media fetch); message docs build lazily inhomepage_draw(ensure_message_built) within a screen of the viewport,MSG_EST_H=260for the unbuilt tail, build budget 2/frame. Scales to thousands + fixes the cold-load (only newest/on-screen media fetches). The video "15fps" was this draw-storm compounding — gone (60fps in playback).web_video_pump1also skipstexImage2Dfor a paused/ended video. - Increment 3 (live-resize):
web_check_resize()polls the CSS viewport each frame → re-fit backing + game_width/height +layer_resize()over all layers (in-place color-tex + stencil-rbo realloc, drops the lazy effect buffer).sync_engine_globalsrefreshes width/height. DESKTOP browser resize WORKS; PHONE ROTATION does NOT (owner: "absolutely not necessary" — likely innerWidth stale on orientationchange). DEV_HUD off (local DEV_HUD=false). Owner kept the DPR cap at 2.0 (the fps fix was the culling, not the resolution).
Claude Code mobile access (research): owner wanted to feed phone screenshots into the session. Claude Code Remote Control does this, BUT is NOT available from the Claude Code DESKTOP app (only CLI + VS Code extension; issues #29006/#48949). Owner runs the Desktop app, so continued with the email round-trip.
Content skills sync the renderer (committed a5011fe): /msg, /draft publish, /end-session (normal+sealed), /unseal now append bash renderer/tools/deploy.sh --content after their prod push, so content syncs the ENGINE site too. Option A (always --content, non-aborting) + a gating tweak: new tools/uncached_tweets.py (mirrors uncached_media.py) so deploy.sh runs fetch_tweets --ids only for new tweets → a text-only post hits X zero times. Found a pre-existing drift (uncached YT id jr_Fzl2XwKU) the next sync will pull.
BOX2D LETTER-DROP effect — IN PROGRESS, LOCAL ONLY (engine bindings desktop-built only, NOTHING deployed/committed): clicking a [this](#drop) link turns the visible page into Box2D physics. Owner's iterated design: loose text = individual letters; video/image/gallery/embed = single rigid bodies; TWEETS = containers (4 wall fixtures = the card border, with text + portrait + media all tumbling as separate objects INSIDE, contained by the walls); separators = thin lines; sidebar letters fall but its bg tint stays; non-letter objects barely rotate; restitution/bounce; [this] stays static and clicking again = the reset (tween back).
- Engine (SHARED anchor.c, DESKTOP-built only, NOT committed):
engine_glyph_capture(on)/engine_glyph_capture_read()(records every glyphlayer_draw_textdraws into a buffer — enumerates visible letters);layer_draw_glyph_c(layerHANDLE, cp, font, cx, cy, r, sx, sy, color)(draw a glyph CENTERED + rotated — physics body → draw). Confirmedlayer_pushcomposes (current = parent*m) for nested transforms; reused the existingphysics_shape_set_restitution(no engine change for bounce). - Renderer (main.lua "drop section" + hooks in elements.lua/homepage.lua, LOCAL):
#drop/F9 → capture → build → fall;update()short-circuits (scroll off). Solid frames re-drawn rotated via a nestedlayer_pushtransform. Tweets = 4-wall containers +inner-tagged contents (inner↔inner/inner↔frame).drop_boxhelper sets restitution 0.5 on every shape (walls too). Tuned live: gravity 1700, letter spin ~±7, non-letter spin ~±0.4, tweet spin ±2.25, restitution 0.5,TW_WALL=6,DROP_MAX=2000. - WHAT'S LEFT (full detail in renderer/PLAN.md "▶ LETTER-DROP" section): (1) VIDEO in the drop — owner deciding (A) tap-to-play in place via rotated hit-test vs (B) click → the existing fullscreen player w/ controls (clicking a fallen video currently does nothing since update short-circuits; controls are axis-aligned so they don't fit a rotated card); (2) the RESET (increment 3) — static re-clickable
thislink + tween-back; (3) ship prep — rebuild the WEB engine w/ the 2 new bindings, real-phone test, remove the F9 trigger, commit anchor.c to Anchor2; (4) the announcement post (owner writes it + the video +[this](#drop), /msg it) then the FULL FLIP cutover.
Commits: Anchor2 1825c77 (mobile engine → GitHub); a327ex-site 5677ef1 (mobile renderer → prod), a5011fe (content-skill tooling → prod). Letter-drop engine + renderer are UNCOMMITTED + local. renderer/PLAN.md updated with the "Anchor Website 14" mobile section + the "▶ LETTER-DROP … IN PROGRESS" handoff (also uncommitted, local).
🔒 Only the summary of this log is public. Private because it contains too many website internal details.