From 9da7b1986bff54e75fe9f80366f1289251acba8c Mon Sep 17 00:00:00 2001 From: Josh-Heaps Date: Sat, 13 Jun 2026 20:06:56 -0600 Subject: [PATCH] bug fixes --- JoshHeaps.Net/wwwroot/css/chess/spectate.css | 26 ++++++++++++++++--- .../wwwroot/js/ChessScripts/Spectate.js | 26 ++++++++++++------- native/chess_engine/src/learned_model.cpp | 11 +++++--- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/JoshHeaps.Net/wwwroot/css/chess/spectate.css b/JoshHeaps.Net/wwwroot/css/chess/spectate.css index 3b57bc9..410c16d 100644 --- a/JoshHeaps.Net/wwwroot/css/chess/spectate.css +++ b/JoshHeaps.Net/wwwroot/css/chess/spectate.css @@ -88,16 +88,29 @@ html, body { cursor: pointer; } +/* Overlaid on the bottom of the card at game end, so it never adds to the card's height + (which would shift the whole grid as games finish and are cleared). */ +.gameOverlay { + position: absolute; + left: 0; + right: 0; + bottom: 0; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.6rem; + padding: 0.9rem 1rem; + background-color: rgba(20, 20, 22, 0.92); + border-radius: 0 0 10px 10px; +} + .gameResult { text-align: center; font-weight: bold; color: #8cd5ed; - margin-top: 0.75rem; } .copyPgnBtn { - display: block; - margin: 0.75rem auto 0; background-color: #8cd5ed; color: #262626; border: 0; @@ -154,6 +167,13 @@ body.fullscreen-open { text-align: center; font-weight: bold; margin-bottom: 0.75rem; + /* Reserve two lines so the header doesn't reflow as the move count gains digits, + the side-to-move text changes, or the check tag toggles. */ + line-height: 1.3; + min-height: 2.6em; + display: flex; + align-items: center; + justify-content: center; } /* Always occupies its space (it's only hidden, not removed) so toggling "check" never diff --git a/JoshHeaps.Net/wwwroot/js/ChessScripts/Spectate.js b/JoshHeaps.Net/wwwroot/js/ChessScripts/Spectate.js index a17d2f1..695d017 100644 --- a/JoshHeaps.Net/wwwroot/js/ChessScripts/Spectate.js +++ b/JoshHeaps.Net/wwwroot/js/ChessScripts/Spectate.js @@ -274,34 +274,42 @@ const Spectate = { if (!card) return; - let banner = card.querySelector(".gameResult"); + // The result and Copy PGN button live in an overlay anchored over the board so showing + // them at game end never changes the card's height (which would shift the whole grid). + let overlay = card.querySelector(".gameOverlay"); if (!text) { - banner?.remove(); - card.querySelector(".copyPgnBtn")?.remove(); + overlay?.remove(); card.classList.remove("over"); return; } - if (!banner) { - banner = document.createElement("div"); + if (!overlay) { + overlay = document.createElement("div"); + overlay.className = "gameOverlay"; + + const banner = document.createElement("div"); banner.className = "gameResult"; - card.appendChild(banner); + overlay.appendChild(banner); + + card.appendChild(overlay); } - banner.textContent = text; + overlay.querySelector(".gameResult").textContent = text; card.classList.add("over"); this.addCopyPgn(gameId, card); }, addCopyPgn(gameId, card) { - if (card.querySelector(".copyPgnBtn")) return; + const overlay = card.querySelector(".gameOverlay"); + + if (!overlay || overlay.querySelector(".copyPgnBtn")) return; const btn = document.createElement("button"); btn.className = "copyPgnBtn"; btn.textContent = "Copy PGN"; btn.onclick = (event) => { event.stopPropagation(); this.copyPgn(gameId); }; - card.appendChild(btn); + overlay.appendChild(btn); // Prefetch now (while the game is still in memory) so copy works during the // brief window before the finished game is cleaned up. diff --git a/native/chess_engine/src/learned_model.cpp b/native/chess_engine/src/learned_model.cpp index 4334d15..ea5ea1e 100644 --- a/native/chess_engine/src/learned_model.cpp +++ b/native/chess_engine/src/learned_model.cpp @@ -87,10 +87,12 @@ static void save_global_weights(); /* defined below; load rewrites stale files static void load_global_weights(const char* path) { WinCounters loaded{}; bool ok = false; + bool fileExisted = false; if (path && *path) { std::ifstream f(path); if (f) { + fileExisted = true; int version = 0; if ((f >> version) && version == LEARNED_VERSION) { auto readTable = [&](double t[chess::PIECE_TYPE_NB][64]) -> bool { @@ -110,9 +112,12 @@ static void load_global_weights(const char* path) { g_counts = ok ? loaded : WinCounters{}; recompute_weights(); - /* Stale / wrong-version / unreadable: wipe the file's contents (keep the file) by - * rewriting it blank-but-versioned, so the next load matches and we never reread garbage. */ - if (!ok) + /* Only create a fresh file when none exists yet (first run): write it blank-but-versioned + * so there's a valid target to persist into. If a file IS present but couldn't be parsed + * (old format, corrupt, or a partial write), leave its bytes untouched — never destroy + * accumulated training data on startup. We just play from neutral weights this session; + * the next training apply() overwrites the file with a clean, current-format save. */ + if (!ok && !fileExisted) save_global_weights(); }