From a4dab59f21d3d2babd7dadc3d8fe0c4b3244072c Mon Sep 17 00:00:00 2001 From: teahubxyz Date: Wed, 21 Oct 2026 06:20:23 +0000 Subject: [PATCH] brain: typed negatives count against the track, warm words never include what the station was told to avoid, a young station gets only a faint region prior from the other stations (no words), and the listener's avoid list wins Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01K6897PWcUEwHiqZMcVnNF6 --- brain.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/brain.js b/brain.js index 0c42e97..0f0f684 100644 --- a/brain.js +++ b/brain.js @@ -7,12 +7,14 @@ ============================================================ */ /* ---------- signals: every reaction and every play, as one signed weight per track ---------- */ +const NEG = /\b(no|not|less|stop|without|drop|enough|never|skip|hate|too much|lose|kill|cut)\b/i; function reactionWeight(spec, row) { const r = String(spec.reaction || ""); let w = 0; for (const ch of r.replace(/"[^"]*"/g, "")) { if ("πŸ”₯❀️⚑🎸🎹🌧️".includes(ch) && ch !== "️") w += 1; if ("🚫😴".includes(ch)) w -= 1; } if (/\[skipped\]/.test(r)) w -= 1.5; - if (/"[^"]*"/.test(r)) w += 0.3; // words mean engagement, mildly warm + // typed words: engagement, mildly warm, unless they say no ("no violin", "less strings", "stop the classical") + for (const m of r.matchAll(/"([^"]*)"/g)) w += NEG.test(m[1]) ? -0.6 : 0.3; const plays = (row && row.plays) || spec.plays || 0; if (plays > 1) w += Math.log2(plays) * 0.6; // on repeat: 4 plays β‰ˆ a πŸ”₯, 100 β‰ˆ four of them if (row && row.kept) w += 1.5; @@ -119,10 +121,13 @@ function tasteText() { const p0 = net.predict({ ...base, tags: "" }); const scored = [...vocab.keys()].filter((w) => vocab.get(w) >= 2).map((w) => [w, net.predict({ ...base, tags: w }) - p0]).sort((a, b) => b[1] - a[1]); const likes = scored.slice(0, 6).filter((s) => s[1] > 0.02).map((s) => s[0]).join(", "), dislikes = scored.slice(-4).filter((s) => s[1] < -0.02).map((s) => s[0]).join(", "); - const which = brains.station ? "this station" : "all their stations"; - return `Taste model (a small net trained on the listener's reactions and play counts across ${which}, ${net.n} signals): most likely to land: ${top}. Least likely: ${low}.` + - (likes ? ` Words it warms to: ${likes}.` : "") + (dislikes ? ` Words it cools on: ${dislikes}.` : "") + - ` Aim near the likely region and lean on the warm words unless the listener's own words say otherwise; keep some variety.`; + // never recommend what this station was told to avoid; a young station borrows only the region from the others, not their words + const avoidWords = new Set([...(state.taste.avoid || []), ...(state.taste.notes || []).filter((n) => NEG.test(n))].flatMap((s) => tokens(s))); + const likesList = scored.slice(0, 8).filter((s) => s[1] > 0.02 && !avoidWords.has(s[0])).slice(0, 6).map((s) => s[0]).join(", "); + if (!brains.station) return `Taste prior (from the listener's other stations, ${net.n} signals; a faint hint only β€” this station's seed and the listener's own words come first): they tend to land around ${top}; less so ${low}.`; + return `Taste model (a small net trained on the listener's reactions and play counts on this station, ${net.n} signals): most likely to land: ${top}. Least likely: ${low}.` + + (likesList ? ` Words it warms to: ${likesList}.` : "") + (dislikes ? ` Words it cools on: ${dislikes}.` : "") + + ` Aim near the likely region; keep some variety. The listener's explicit asks above and below outrank this model.`; } /* the page's hooks (index.html β€Ί hooks): the taste text rides into the DJ's prompt, and a station opening retrains */ hooks.djPrompt = (base) => {