Anchor Website 7
Opus 4.8
Summary
Resolved the long-standing Anchor 2 "minimize/unfocus hang" (root-caused to Windows window-ghosting, not a loop bug) and completed the M2b video pull for the a327ex.com renderer (112/120 media owned locally), including replacing dead video embeds and mapping out the remaining web-delivery roadmap.
Minimize/unfocus hang — root cause & fix (main focus):
- Reopened the bug that last session ("Anchor Website 6") wrongly reported as fixed. Last session's render-throttle (skip render +
SDL_GL_SwapWindow+SDL_Delay(10)whileMINIMIZED/HIDDEN) was necessary but not sufficient — it stopped the free-spin but introduced a new symptom: a long-minimized window would no longer restore at all (window never reappears; force-kill needed). - User repro testing pinned the behavior: minimized (fullscreen or windowed) hangs after ~5 min; unfocused-but-visible does NOT hang; quick minimize→restore always works; happens while actively using the computer with the display on (ruled out display-sleep / screensaver / GPU TDR).
- First built a Lua "flight recorder" (once/sec heartbeat →
hang_trace.log) but found its clock was game-time, not wall-clock —update()is always called with a fixeddt = PHYSICS_RATE = 1/120, sotandupdwere the same measurement and couldn't distinguish a healthy loop from one blocking in clamped catch-up bursts (MAX_UPDATES=10). - Replaced it with a C watchdog thread in
anchor.c: real wall-clock (SDL_GetPerformanceCounter) + a main-loop iteration counter + a phase marker (TOP/EVENTS/UPDATE/RENDER/SWAP/DELAY) + cachedSDL_GetWindowFlags, logged off-thread towatchdog.logso it survives a main-thread stall (marksSTALLafter ~2s of no iteration progress). Later added a raw Win32 message probe viaSDL_SetWindowsMessageHookcounting messages delivered before SDL processes them. - The watchdog proved the main loop was always healthy: across a ~58-min run,
iteradvanced ~100 Hz the entire time minimized, zeroSTALL, flat RSS, constant GL textures/FBOs. Window flags showedFULLSCREEN_DESKTOP|MINIMIZED(0x1067) set throughout. The raw-message count (nmsg) FROZE at 34 for ~58 minutes — zero Win32 messages reached the app during restore-click attempts. - Root cause: Windows window-ghosting. Because last session's fix makes the app stop presenting while minimized, DWM marks the window unresponsive after a few minutes and spawns a ghost; taskbar/Alt-Tab/restore clicks hit the ghost, and the real (alive) window receives nothing — so it never un-minimizes. Quick restores work because the ghost hasn't formed yet (explains the time-dependence). Also explains the symptom change vs last session (pre-fix the app kept presenting → no ghost).
- Fix: one line —
DisableProcessWindowsGhosting()at desktop startup (#ifdef _WIN32) in sharedAnchor2/engine/src/anchor.c. User-verified end-to-end: after a 13.7-min minimize, the restore-click madenmsgclimb, theMINIMIZEDflag clear (0x1067→0x1227), real input flow (WM_MOUSEMOVE/WM_LBUTTONDOWN/WM_SYSKEYDOWN), and repeated re-minimize/restore all worked. - Removed all diagnostic scaffolding (watchdog thread, message hook, phase markers, extra globals, the Lua recorder); only
DisableProcessWindowsGhosting()remains. Rebuilt the engine clean, redeployedrenderer/anchor.exe. UpdatedPLAN.md+ theproject-anchor-hang-followupmemory (now RESOLVED). Cost: a genuinely-hung Anchor app no longer shows the "Not Responding" ghost UI (Task Manager still kills it). Affects all Anchor2 desktop (Windows) games.
Video pull (M2b) — completed (112/120 usable):
tools/fetch_media.py(yt-dlp) pulls owned 1080p-h264 copies of every::youtube/::short(106 refs) + 14 local::video. 5 cached at start.- YouTube's 2026 anti-bot defenses required escalating fixes, diagnosed via the task logs: (1) cookie-less = HTTP 403 →
--cookies-from-browser firefox; (2) heavy pulling rate-flags the IP ("No title found" degraded extraction, then "Sign in to confirm you're not a bot") → pull in batches with cooldowns; (3) the logged-intvclient requires solving the signature/n-challenge ("Only images available" across ALL clients — ios/mweb/web_safari/tv_embedded/android/android_vr/tv) → node JS runtime + yt-dlp EJS solver (--remote-components ejs:github, fetchesyt.solver.lib.min.js); (4) YouTube throttles a download stream to ~0 B/s mid-file, hanging a no-timeout yt-dlp (one file wedged at 404 MB for an hour, process died silently) →--socket-timeout 30 --retries 10 --fragment-retries 10 --throttled-rate 100K. - Final: 98/106 YouTube + 14 local = 112/120 usable. The user logged into YouTube in Firefox mid-session (cookies 5→70) which cleared the bot-wall. The IP rate-flag pattern: a cooldown clears it; the heavy pulls re-trip it.
- 8 unrecoverable: 6 Super People dev/test streams (game shut down, channel privated — inaccessible even logged in),
HPM6rf0-e6M(Nas Daily / SBF, account terminated),gmbjqe9g7g0(Baby Steps / NHK copyright-blocked). These get the per-post fallback card next session.
Dead-video replacements (4 posts):
- User supplied working reuploads: Son of a Serpent
mIafcqCZNew→CFJsWP2prEs, Carnival6BT8tBwLdtc→u-u0t487byw(Thank You Scientist music videos — both had been pulled from YouTube), and the "You're Not a Lottery Ticket" talkiZM_JmZdqCw→Kuc6pBZ-Q0I. - Swapped embeds + inline discography links across
son_of_a_serpent.md,the_foreign_man.md,small_games.md,snkrx_log.md(the lottery-ticket talk was embedded in 3 posts, 4 refs total). Stale&list=…&index=…params on the inline links were dropped to the clean bare URLs the user provided. All three replacements downloaded successfully. - Located + identified every dead (4) and private (7) video by title-from-context and
file:line; 6 of the 7 private are Super People dev streams.
Web-delivery requirements (planning, no code):
- Established the current architecture: a327ex.com is a Lua server (
server/) behind nginx on a VPS, rendering markdown→HTML server-side — so SEO/crawler HTML already exists today. The desired model is progressive enhancement: keep the Lua server as the SEO/HTML layer, mount the engine WASM canvas on top (HTML stays in the DOM for crawlers, canvas covers it for humans). - Remaining web work, in dependency order: (1) Cloudflare in front of the domain — the "forgotten step" (README §144): the Name.com→Cloudflare registrar transfer was in progress; once complete, switch nameservers + SSL Full(strict) + orange-cloud proxy + nginx
CF-Connecting-IP. (2) R2 for range-streamed video (zero egress), ideally on amedia.subdomain (depends on Cloudflare-in-front). (3) Engine WASM web port (build-web-engine.sh/package-web-game.shexist; needs lazy per-page content loading + browser-<video>→gl.texImage2D; libav stays desktop-only). (4) Music-video DMCA decision (sharpened by seeing 2 TYS videos already pulled from YT). - Open decisions for the user: keep the Lua server permanently as the SEO layer (recommended: yes); the DMCA policy; physically verify whether the domain transfer completed.
Commits / housekeeping:
- Anchor2 engine:
DisableProcessWindowsGhosting()fix inengine/src/anchor.c— committed with a generic message, NOT pushed (private session targeting a shared repo). - a327ex-site: 4 post edits +
renderer/PLAN.md(added a "▶ NEXT SESSION" handoff block) +renderer/anchor.exe(rebuilt clean) +renderer/tools/fetch_media.py(auth/EJS/robustness patches).renderer/data/is gitignored (generated artifacts, incl.media.lua).renderer/media-cache/(tens of GB of pulled video) gitignored → feeds R2 in the web-delivery phase. - Next session = two buckets: (1) per-post fidelity, (2) web delivery (recorded in
PLAN.md,MEMORY.md, and the project memory). - Sealed privately as Private Session 11.
🔒 Only the summary of this log is public. Private because it contains too many website internal details.