@@ -1,18 +1,10 @@+// A real same-origin URL (not a blob), so Stockfish resolves its .wasm next to itself.
+// The server must send the .wasm as application/wasm.
const ENGINE_URL = new URL(`${import.meta.env.BASE_URL}stockfish/stockfish-18-lite-single.js`, location.href).href;
-const WASM_URL = new URL(`${import.meta.env.BASE_URL}stockfish/stockfish-18-lite-single.wasm`, location.href).href;
-// A tiny worker that fetches the wasm as a plain binary and hands it to Stockfish
-// through Module.wasmBinary. That path ignores the response's MIME type, so a server
-// that serves .wasm as text/plain (a very common misconfig) can't break the engine.
-const BOOTSTRAP = `self.onmessage=function(e){var d=e.data||{};if(!d.__boot)return;` +
- `fetch(d.wasmUrl).then(function(r){return r.arrayBuffer();}).then(function(buf){` +
- `self.Module={wasmBinary:buf,locateFile:function(p){return p.slice(-5)==='.wasm'?d.wasmUrl:p;}};` +
- `try{importScripts(d.jsUrl);postMessage('__ready');}catch(err){postMessage('__err '+err);}` +
- `}).catch(function(err){postMessage('__err '+err);});};`;
export class Engine {
constructor() {
this.listeners = [];
- const bootUrl = URL.createObjectURL(new Blob([BOOTSTRAP], { type: "text/javascript" }));
- this.worker = new Worker(bootUrl);
+ this.worker = new Worker(ENGINE_URL);
this.worker.onmessage = (e) => {
const line = typeof e.data === "string" ? e.data : String(e.data?.data ?? "");
for (const fn of this.listeners)
@@ -44,8 +36,6 @@ } });
}
async boot() {
- this.worker.postMessage({ __boot: true, jsUrl: ENGINE_URL, wasmUrl: WASM_URL });
- await this.waitFor("__ready", 20000);
this.send("uci");
await this.waitFor("uciok", 20000);
this.send("isready");
@@ -1,16 +1,8 @@ import type { Personality } from "./personalities";
+// A real same-origin URL (not a blob), so Stockfish resolves its .wasm next to itself.
+// The server must send the .wasm as application/wasm.
const ENGINE_URL = new URL(`${import.meta.env.BASE_URL}stockfish/stockfish-18-lite-single.js`, location.href).href;
-const WASM_URL = new URL(`${import.meta.env.BASE_URL}stockfish/stockfish-18-lite-single.wasm`, location.href).href;
-
-// A tiny worker that fetches the wasm as a plain binary and hands it to Stockfish
-// through Module.wasmBinary. That path ignores the response's MIME type, so a server
-// that serves .wasm as text/plain (a very common misconfig) can't break the engine.
-const BOOTSTRAP = `self.onmessage=function(e){var d=e.data||{};if(!d.__boot)return;` +
- `fetch(d.wasmUrl).then(function(r){return r.arrayBuffer();}).then(function(buf){` +
- `self.Module={wasmBinary:buf,locateFile:function(p){return p.slice(-5)==='.wasm'?d.wasmUrl:p;}};` +
- `try{importScripts(d.jsUrl);postMessage('__ready');}catch(err){postMessage('__err '+err);}` +
- `}).catch(function(err){postMessage('__err '+err);});};`;
type Line = string;
@@ -20,8 +12,7 @@ private ready: Promise<void>; private listeners: ((line: Line) => void)[] = [];
constructor() {
- const bootUrl = URL.createObjectURL(new Blob([BOOTSTRAP], { type: "text/javascript" }));
- this.worker = new Worker(bootUrl);
+ this.worker = new Worker(ENGINE_URL);
this.worker.onmessage = (e) => {
const line = typeof e.data === "string" ? e.data : String(e.data?.data ?? "");
for (const fn of this.listeners) fn(line);
@@ -54,8 +45,6 @@ }); }
private async boot() {
- this.worker.postMessage({ __boot: true, jsUrl: ENGINE_URL, wasmUrl: WASM_URL });
- await this.waitFor("__ready", 20000);
this.send("uci");
await this.waitFor("uciok", 20000);
this.send("isready");
@@ -216,9 +216,28 @@ transition: transform .26s cubic-bezier(.2,.7,.25,1); will-change: transform;
}
.piece .glyph { position: relative; z-index: 2; }
-.piece::before { content: ""; position: absolute; bottom: 8%; width: 64%; height: 18%; border-radius: 50%; z-index: 1; }
-.piece.w::before { background: rgba(252,249,240,.92); box-shadow: inset 0 0 0 1px rgba(0,0,0,.3); }
-.piece.b::before { background: rgba(30,27,20,.85); box-shadow: inset 0 0 0 1px rgba(255,255,255,.28); }
+/* Emoji share one glyph per type, so the light and dark armies are told apart by the
+ token each piece rides on, not by the emoji's own colors. */
+/* A grounding shadow plus a hairline light rim, so a mid-tone emoji (a grey wolf, say)
+ still reads on the dark army's token without being darkened into it. */
+.piece:not(.traditional) .glyph {
+ filter: drop-shadow(0 1px 1px rgba(0,0,0,.4)) drop-shadow(0 0 .5px rgba(255,255,255,.55));
+}
+.piece::before {
+ content: "";
+ position: absolute;
+ inset: 9%;
+ border-radius: 50%;
+ z-index: 1;
+}
+.piece.w::before {
+ background: radial-gradient(circle at 38% 32%, #fffdf6, #e7dcbe 78%);
+ box-shadow: 0 2px 4px rgba(0,0,0,.22), inset 0 0 0 2px rgba(90,70,28,.5);
+}
+.piece.b::before {
+ background: radial-gradient(circle at 38% 32%, #3b362a, #14110b 78%);
+ box-shadow: 0 2px 4px rgba(0,0,0,.38), inset 0 0 0 2px rgba(243,238,220,.5);
+}
.piece.traditional { font-family: var(--mono); font-size: calc(var(--sq) * .8); }
.piece.traditional.w { color: #fbf7ec; text-shadow: 0 0 1px #000, 0 1px 1px rgba(0,0,0,.45); }
.piece.traditional.b { color: #141109; }
@@ -230,11 +249,11 @@ @keyframes hop { 0% { transform: translateY(0); } 45% { transform: translateY(-24%); } 100% { transform: translateY(0); } } .piece.threat::after {
content: "";
position: absolute;
- inset: 7%;
+ inset: 3%;
border: 2px solid var(--check);
border-radius: 50%;
- opacity: .85;
- z-index: 0;
+ opacity: .9;
+ z-index: 3;
pointer-events: none;
}
@@ -14,6 +14,10 @@ workbox: { globPatterns: ["**/*.{js,css,html,wasm,woff,woff2,svg}"],
// The Stockfish wasm is ~7MB, so raise the precache size limit.
maximumFileSizeToCacheInBytes: 9 * 1024 * 1024,
+ // Take over at once and drop old caches, so a bad old cache can't linger.
+ skipWaiting: true,
+ clientsClaim: true,
+ cleanupOutdatedCaches: true,
},
manifest: {
name: "Emchess",