/* compute.js — VADUGWI "Read the Room" scoring. Pure, deterministic, no DOM. Sets window.RTR_COMPUTE in browser; exports for Node when run under a module loader / test harness. */ (function (root) { 'use strict'; // Maps a raw slider answer (-6..+6) to an internal 0..255 value (128 = neutral). // Ported from rtr_logic.js: extremes saturate, the center band is compressed so // a "0" reads as mild-positive-of-neutral (153), matching the original instrument. var SCALE_MAP = { '-6': 0, '-5': 0, '-4': 51, '-3': 102, '-2': 128, '-1': 140, '0': 153, '1': 166, '2': 179, '3': 204, '4': 230, '5': 255, '6': 255 }; var CATS = ['V', 'A', 'D', 'U', 'G', 'W', 'I']; function clamp(v) { return Math.round(Math.max(0, Math.min(255, v))); } function meanOf(arr) { var s = 0; for (var i = 0; i < arr.length; i++) s += arr[i]; return s / arr.length; } // All-neutral pool: every probe is a projective scene with an expected-neutral // center. There are no NEG/POS tiers anymore, so a dimension is simply how the // user COLORS those neutral scenes — the per-cat mean of their internal values. // Arousal is the exception: it reads how much neutral scenes MOVE the user // (reactivity = mean absolute deviation of raw answers from "doesn't affect me"). function computeProfile(probes, answers) { var byCat = {}; var i; for (i = 0; i < CATS.length; i++) { byCat[CATS[i]] = { raw: [], internal: [] }; } var deviations = []; for (i = 0; i < probes.length; i++) { var p = probes[i]; var raw = answers[i]; if (raw === null || raw === undefined) continue; // unanswered: skip if (!byCat[p.cat]) continue; // unknown cat: skip var internal = SCALE_MAP[String(raw)]; byCat[p.cat].raw.push(raw); byCat[p.cat].internal.push(internal); // Every probe is neutral (expected center), so the deviation IS the rating: // how strongly / which way the user colored a scene that was meant to be blank. deviations.push({ i: i, id: p.id, cat: p.cat, tier: p.tier, text: p.text, answer: raw, internal: internal, dev: raw // signed projective reaction }); } deviations.sort(function (a, b) { return Math.abs(b.dev) - Math.abs(a.dev); }); // Per-dimension projective bias: mean of internal values over that cat's // answered probes. Default 128 (true neutral) when none answered. function catMean(cat, dflt) { var bucket = byCat[cat].internal; if (!bucket.length) return dflt; return clamp(meanOf(bucket)); } var V = catMean('V', 128); var D = catMean('D', 128); var G = catMean('G', 128); var W = catMean('W', 128); var I = catMean('I', 128); // U keeps its 0-based axis meaning (Urgency starts at 0/neutral), but is now // driven purely by the per-cat mean of the answered U probes; 0 when none. var U = catMean('U', 0); // A (Arousal) = reactivity = mean ABS deviation of raw answers, mapped 0..255. // How much do neutral scenes move you, regardless of which way. var aVals = byCat.A.raw, absSum = 0; for (i = 0; i < aVals.length; i++) absSum += Math.abs(aVals[i]); var avgAbs = aVals.length ? absSum / aVals.length : 0; var A = clamp((avgAbs / 6) * 255); return { V: V, A: A, D: D, U: U, G: G, W: W, I: I, byCat: byCat, deviations: deviations }; } var api = { SCALE_MAP: SCALE_MAP, computeProfile: computeProfile }; if (typeof window !== 'undefined') window.RTR_COMPUTE = api; if (typeof module !== 'undefined' && module.exports) module.exports = api; if (typeof root !== 'undefined' && root && !root.RTR_COMPUTE) root.RTR_COMPUTE = api; })(typeof globalThis !== 'undefined' ? globalThis : this);