| """ |
| scorer.py β zone-based, loose, evoked-feeling scoring for puzzle mode. |
| |
| Compares a user's shape-sentence affect against the target affect. Scoring lives |
| in GEOMETRY space (so scoring-space == shape-space), on the FORM axes only β |
| scale is excluded (it's compositional, not an evoked feeling). |
| |
| Loose & celebratory: generous bands, near-misses are wins, one gentle directional |
| hint drawn from the biggest felt-quality gap. |
| """ |
| from .mappings import affect_to_geometry |
|
|
| |
| SCORED = ["spikiness", "compactness", "segmentability"] |
| WEIGHTS = {"spikiness": 1.0, "compactness": 0.9, "segmentability": 0.7} |
|
|
| def score(target_affect, attempt_affect): |
| """Return dict: {score 0..1, band, hint}. Higher score = closer feel.""" |
| tg = affect_to_geometry(**target_affect) |
| ag = affect_to_geometry(**attempt_affect) |
| num = sum(WEIGHTS[k] * abs(tg[k] - ag[k]) for k in SCORED) |
| den = sum(WEIGHTS.values()) |
| dist = num / den |
| s = max(0.0, 1.0 - dist) |
|
|
| if s >= 0.85: band = "Uncanny." |
| elif s >= 0.70: band = "So close!" |
| elif s >= 0.55: band = "Good feel β nearly there" |
| elif s >= 0.40: band = "Right neighbourhood" |
| else: band = "Bold choice β different vibe" |
|
|
| |
| gaps = {k: tg[k] - ag[k] for k in SCORED} |
| k = max(gaps, key=lambda x: abs(gaps[x])) |
| if abs(gaps[k]) < 0.12: |
| hint = "energy matched" |
| elif k == "spikiness": |
| hint = "try sharper, edgier words" if gaps[k] > 0 else "try softer, gentler words" |
| elif k == "compactness": |
| hint = "try fuller, rounder words" if gaps[k] > 0 else "try thinner, sparser words" |
| else: |
| hint = "try busier, more fragmented words" if gaps[k] > 0 else "try simpler, more unified words" |
|
|
| return {"score": round(s, 3), "band": band, "hint": hint} |
|
|
|
|
| |
| PUZZLE_THRESHOLD = 0.70 |
|
|
| if __name__ == "__main__": |
| target = {"valence": 0.10, "arousal": 0.90, "dominance": 0.82} |
| for label, att in [ |
| ("a calm round pebble", {"valence": 0.85, "arousal": 0.12, "dominance": 0.40}), |
| ("a rough jagged rock", {"valence": 0.30, "arousal": 0.65, "dominance": 0.70}), |
| ("shattered glass shards",{"valence": 0.15, "arousal": 0.92, "dominance": 0.80}), |
| ]: |
| r = score(target, att) |
| print(f"{label:26s} {int(r['score']*100):3d}% {r['band']:28s} ({r['hint']})") |
|
|