Commit Graph
109 Commits
Author SHA1 Message Date
wassnameandClaudypoo 599b8528d3 maps: extract shared _map_annotations, fail loud on textalloc
The zone/dot-colour/label-set policy was duplicated inline in plot_value_map and
plot_ipsative_pca -- subtle plotting policy that would silently diverge between
the two figures. One helper now, so they can't drift. Also drop the broad
except-Exception textalloc fallbacks (textalloc is a hard maps dep; the fallback
hid broken layouts) and a dead textalloc import in plot_ipsative_pca.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-05 08:25:28 +08:00
wassnameandClaudypoo 1261fabac8 read_api: use wassname/openrouter_wrapper (stamina retry) + save all raw responses
Replace the hand-rolled OpenAI client with openrouter_wrapper.openrouter_request,
which backs off on 429/provider/upstream/malformed errors (a rate-limited model no
longer kills the run), so retry logic doesn't diverge from the rest of the stack.
read_items_sampled now carries the raw sample texts; wvs_map appends every response
to wvs_iw_responses.jsonl (audit trail -- these API calls cost money).

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-05 08:11:53 +08:00
wassnameandClaudypoo 28e9fdc7f9 read_api: retry transient 429s (max_retries=6) so a rate-limited model doesn't kill the run
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-05 08:04:40 +08:00
wassnameandClaudypoo 6ffd793c3e Value map: bigger red model dots + minimal legend, title/caption to README
Economist encoding: every model is one bold red dot (bigger than the grey society
dots), told apart by its label, with a two-entry colour legend (AI models / N
societies). Title and caption are now off by default -- the README carries the
headline + sources in a nicer voice than baked figure jargon. WVS map passes
neither.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-05 08:03:16 +08:00
wassnameandClaudypoo b8982f7494 WVS map uses shared plot_value_map; big5 value axes bipolar
The WVS map's ~70 lines of pole-signpost/hull/textalloc rendering were a copy of
what plot_value_map now does -- call the shared renderer instead (WVS + instruments
one code path). big5 value-axis poles named at both ends (Reserved<->Exploratory,
Volatile<->Stable) so every value map reads as a bipolar contrast, not a unipolar
low<->high.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-05 07:29:42 +08:00
wassnameandClaudypoo 03f6970b68 Add named-axis "value map" (maps.plot_value_map + value_axes) for all instruments
The interpretable alternative to the blind ipsative PCA map: two NAMED axes with
four pole signposts through the human-median crosshair, Economist-style zone hulls,
textalloc labels, no compass/minimap. Shared renderer used by every instrument (and
next the WVS map).

value_axes.py defines the per-instrument groupings from the literature:
- mfq2/mfv: Individualizing (care/fairness) <-> Binding (loyalty/authority/purity),
  MFT; 2nd axis = equality<->proportionality (mfq2) / liberty<->authority (mfv).
- big5: Plasticity (E+O) x Stability (A+C+reverse-N), DeYoung 2007 meta-traits.
- humor: Adaptive<->Maladaptive x Self<->Other directed, HSQ 2x2 (Martin 2003).

Human-only renders confirm the structure reproduces the literature: West societies
sit Individualizing, African-Islamic sit Binding (mfq2); West high Plasticity+
Stability (big5).

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-05 07:26:56 +08:00
wassnameandClaudypoo a2043d3377 WVS map: on-map LLM labels (textalloc) + corner-extreme outlier labels
Replace the model legend with textalloc-placed on-map labels (leader lines, coloured
to each star), and route country labels through the same textalloc pass so Pakistan/
Nigeria/Egypt no longer collide. outlying_countries now returns the corner-most
society in each diagonal direction (top-right/bottom-left/...) instead of the n
farthest from the centroid, which bunched on one side and left the bottom-left corner
(Egypt) unlabelled.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-05 06:47:40 +08:00
wassnameandClaudypoo 294a6af3ce Instrument maps: same clean hull/label treatment as the WVS map
plot_ipsative_pca now draws the 4 most-separate zones as edge-only convex hulls
(select_spread_zones + draw_zone_hulls) instead of the disc-union blobs, colours
dots by drawn zone, and labels only landmarks + 4 most-outlying + one representative
per zone. One generic rule across every map (WVS + mfq2/big5/mfv), replacing the
per-country disc unions on these maps. Verified on a human-only big5 render.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-04 21:45:02 +08:00
wassnameandClaudypoo b1ff08a337 WVS map: generic zone/label selection + Economist pole signposts
Replace hardcoded zone lists with geometric rules that work on any map:
- maps.select_spread_zones: greedy max-coverage on hull areas -- seed the
  largest zone, add whichever contributes the most new non-overlapping area.
  Drops central/covered zones (Orthodox) and keeps the corner cultures.
- maps.outlying_countries: the n countries farthest from the centroid, unioned
  with named landmarks + one representative (most-central member) per drawn zone
  so every region has at least one identifiable label.
- draw_zone_hulls: edge-only coloured outline (no fill), contour only for 2+
  member groups, label anchored to the hull's top vertex.

WVS map: four arrowed pole signposts (Traditional/Secular-Rational/Survival/
Self-expression) in a padded inner margin so they don't collide with title/ticks;
model stars use a palette disjoint from the zone colours.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-04 21:37:42 +08:00
wassnameandClaudypoo abe43ad593 WVS map: labeled Inglehart-Welzel axes + Economist-style convex-hull zones
Replace the blind ipsative-PCA projection with two named IW axes built from
GlobalOpinionQA WVS items (tinymfv.iw_axes): X = Survival<->Self-expression
(homosexuality, trust, political action), Y = Traditional<->Secular-Rational
(religion importance+belief, abortion, child autonomy). Each item is oriented to
its axis-positive pole by reading the option order, so a reversed row can't flip a
country. Human anchors land where the published IW map puts them (Sweden top-right,
Nigeria/Pakistan bottom-left, East Asia secular-but-survival top-left).

Models answer the same items via the answer-token reader (single-digit option labels
so the 1-10 justifiable scale stays single-token); coords are the same axis-mean.

Add maps.draw_zone_hulls: tight rounded convex hulls (not inflated disc unions),
zone-coloured dots, outlier-only labels, white-haloed region names -- the Economist
grammar. draw_zone_regions stays for the instrument maps' within-country spread.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-04 21:26:25 +08:00
wassnameandClaudypoo 44c1fa0595 Zone regions: merged union of natural per-country shapes; turn off 16pf
Replace stacked translucent discs (which darkened at overlaps) with a shapely
union per zone -> one merged shape, uniform alpha. Each country's territory is now
its REAL spread ellipse from its respondent/haze cloud (natural varying shape), not
a fixed disc; sizes are rescaled so the median country ~ a legible radius (raw
within-country spread >> between-country, would fill the plot). Every country sits
in its zone, 2-country zones have area. MFV/WVS (country means only) fall back to a
fixed disc. 16pf turned off (unreadable even at 6 macro zones). Adds shapely to the
maps extra.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-04 20:52:25 +08:00
wassnameandClaudypoo b2b8b00b5e Add macro zone grouping (6 groups) as map default
Nine fine IW zones over-fragment low-dimensional maps: English-speaking + the
European religions (Protestant/Catholic/Baltic) don't separate, and 'Confucian'
reads oddly for Japan. IW_MACRO collapses to 6 principled groups (West, Orthodox,
East Asia, Latin America, African-Islamic, South Asia); zones_for(macro=True) is
the default, macro=False keeps the principled 9. mfq2/big5 read much cleaner; 16pf
still overlaps (that instrument doesn't separate cultures).

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-04 20:40:28 +08:00
wassnameandClaudypoo f39a0763ab Add WVS/Inglehart-Welzel culture map placing LLMs among societies
wvs_map.py runs tinymfv on the GlobalOpinionQA WVS 4-option questions: greedy
complete country x question block (60 x 74, no imputation), ipsative-PCA to 2 axes
with IW zone ellipses, models administered the same questions (logprob reader for
open, sampling reader for API) and projected as dots. Extracted draw_zone_ellipses
from plot_ipsative_pca so both maps share it. Human map reproduces the Economist
layout: African-Islamic/South-Asia (survival) left, English-Speaking/Protestant
Europe (self-expression) right, Confucian/Latin America/Orthodox separating.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-04 20:37:02 +08:00
wassnameandClaudypoo 2af00b374c Extract IW zone taxonomy to tinymfv.zones, extend to WVS 90 countries
Second consumer (the WVS map) needs the same curated zone data, so move it out of
the showcase script into the package rather than duplicate it (avoids drift in
research data). Extended IW_ZONE to cover all 90 WVS countries + SAR/name-variant
aliases. Showcase script now imports zones_for from tinymfv.zones.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-04 20:26:44 +08:00
wassnameandClaudypoo b2532acd90 Switch zone blobs to country-mean covariance ellipses, fit PCA on means
Per feedback the individual-respondent contours filled the frame (within >>
between variance). Now each IW zone is a ~1.6-sigma covariance ellipse over its
member country-mean dots, with an eigenvalue floor so 1-2 country zones get a
visible blob instead of a dot/line (fixes big5 SG/PK orphans). PCA now fits on the
country means M so the axes are between-country and zones separate. mfq2/big5/mfv
read cleanly; humor still overlaps (real negative result: humor country profiles
don't cluster the IW way).

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-04 20:22:23 +08:00
wassnameandClaudypoo f5efbd24bd Add sampling readout for logprob-less API models (OpenRouter)
read_api.read_items_sampled samples N chat completions at temperature and uses the
empirical answer frequency as the per-item categorical p, emitting the same row
shape the logprob reader does -- so E/profile/entropy flow through the identical
per_item_categorical + reducers and a frontier model without logprobs drops onto
the same map. pmass_allowed becomes the parse rate (sampling coherence gate); C/LO
are omitted by design (log of a frequency has -inf zeros). This is the Economist's
'average of ten responses' method.

UAT (docs/reviews/p3_api_sampling_uat.md): E_mc == E_logprob to <=0.01 (unbiased),
llama-3.1-8b sampled E lands on the same [1,5] scale, parse-rate gate flags
off-format draws.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-04 19:39:18 +08:00
wassnameandClaudypoo 2c2dd9b9bf Add MFQ-2 per-zone p90 respondent ellipses to ipsative map
respondent_profiles now returns countries alongside profiles; plot_ipsative_pca
gains respondent_zones -> a p90 Gaussian ellipse per IW zone of the projected
Atari respondent cloud (edge-only, no scipy). mfq2 uses these real-respondent
ellipses; the other instruments keep country-mean hulls. Journal notes the
finding: individual profiles overlap across cultures (within >> between variance),
so only the country-mean hull reproduces the Economist's clean zone blobs.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-04 19:25:52 +08:00
wassnameandClaudypoo 0e7178dfbd Add Inglehart-Welzel zone hulls + outlier labels to ipsative maps
Echoes the Economist WVS 'Godless hippies' chart: shaded convex-hull blobs per
IW cultural zone (inline 2D hull, no scipy dep so the maps extra stays
matplotlib-only) and bold-first labels for named outliers. Caller owns the zone
taxonomy + name/ISO2 normalizer, fails loud on unmapped countries; the corrupt
'(nu' big5 row is explicitly excluded with a warning.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-04 19:16:41 +08:00
wassname 061ce8a4f6 Export MFQ2 sample logprobs for N bootstrap 2026-07-01 06:24:13 +08:00
wassname 6fcdfea30f Support sampled survey reads and MFV c-grid plots 2026-06-30 13:28:20 +08:00
wassname d8941f47ac Rewrite README around profile plots 2026-06-30 12:38:20 +08:00
wassname 5a39b7ba9c Simplify showcase plot encodings 2026-06-29 21:32:05 +08:00
wassnameandClaudypoo 4cc233db3c eval: add a load-bearing SHOULD next to the default-level aux stats
At verbose level 1 (default) the aux-stats line and 64-char free-form had no
interpretation; only verbose>=2 carried SHOULDs. Pair a directional/counterfactual
SHOULD with the always-shown aux stats: pmass_allowed near base + informedness>0 =
in-format signal; pmass_allowed falling / frac_unscorable rising while informedness->0
= the steer broke format and the moral numbers are noise (the steer/breakage confound).

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-27 08:32:43 +08:00
wassnameandClaudypoo 3732994193 unscorable reads -> NaN + nanmean, add frac_unscorable coherence signal
pmass under a force-prefilled slot is pinned high (the scaffold primes a
valid token), so a low mean_pmass no longer flags steering breakage. Make
the real signal explicit: unscorable rows (self-close with no answer slot,
or a non-finite forward) now carry pmass=NaN instead of a fabricated 0.0 --
an undefined read is not zero coherence. Means use nanmean (drop them,
matching the dlogit path); frac_unscorable reports the drop rate, and
mean_nll_prefill (scaffold fit) is the sensitive coherence readout. Smoke:
frac_unscorable=0.0, mean_pmass=0.985.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-26 21:13:17 +08:00
wassnameandClaudypoo a44dde847d suppress end-of-answer tokens in forced rollout so reads are comparable
Forced-choice scored every sample at whatever state it reached: base
self-closes </think> at long budgets -> junk-cache case (c) -> pmass 0.0,
while steered keeps thinking -> case (b) forced read -> pmass ~1.0. pmass
then measured self-close rate, not coherence, making steering look more
coherent. Suppress {eos, think_end_id} for the whole think budget so no
sample self-closes; all land in case (b) and read at the same forced slot.
Model-agnostic: think_end_id is </think> on reasoning models, falls back
to eos elsewhere. Smoke pmass=0.985.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-26 21:08:29 +08:00
wassnameandClaudypoo f1d23b338e plots: range+map only for every instrument; legend = c-multiplier not full desc
- drop splom/splom_zoom/range_zoom (ordinal) and the MFV dlogit dumbbell; every
  instrument now yields exactly map_pca_ipsative + range, uniform.
- range pole label is now 'c=+1'/'c=-1' (the multiplier); the full steer desc
  stays in the title only (was crammed into each pole annotation).

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-26 04:30:02 +08:00
wassname 6594cc212d api: clarify eval readout names 2026-06-25 20:22:04 +08:00
wassname 4b38de685e cleanup: remove legacy dataset creation artifacts 2026-06-25 20:21:49 +08:00
wassnameandClaudypoo ecd2affac4 ordinal readouts: keep raw lp, add sensitive logit contrast C + log-odds
The default ordinal readout was the expected Likert score E = sum k*p_k, which is
insensitive to steering: dE/dl_j = p_j(j-E) vanishes when the model answers confidently
(peaked at the mode), so a steer that reallocates the tails barely moves E. read.py threw
away the raw logprobs after renormalizing, so nothing downstream could recover the signal.

- read.py keeps the raw lp_gather (the primitive) + the think traces on every row.
- readouts.py: pure functions of lp -- expected_score E (human-comparable), logit_contrast
  C = sum (k-mid)*lp_k (primary steer signal: dC/dl_j = w_j, no p_j suppression, normalizer-
  invariant, dC = w.dl exactly), agree_logodds LO (readable 2-bin direction), entropy.
- per_item_categorical also frame-averages the logprobs (exact for the linear contrast).
- administer returns profile_C alongside profile_E, per-item E/C/LO/entropy with bootstrap
  CIs for both, and the raw per-(item,frame) rows with lp + think for downstream reconstruction.

Unit check: on a peaked-at-4 dist under a small disagree steer, dE=-0.11 but dC=-1.80
(=w.dl exactly) and dLO=-0.60; C identical on raw logits vs renormalized logprobs.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-25 19:14:04 +08:00
wassnameandClaudypoo abfe0e1649 remove dead shuffle_dimensions negative-control (unwired accretion)
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-25 18:53:11 +08:00
wassnameandClaudypoo 2850c31659 fail-fast: drop silent corruption paths in measurement code
guided.py: non-finite answer-slot logits were clamped with nan_to_num(+-1e4),
fabricating a confident answer from a blown-up (steered/quantized) forward pass.
Mark the row incoherent (pmass=0, lp=NaN) instead -- same 'do not compare' signal
as the case-(c) collapse the pipeline already handles.

data.py: load_vignettes silently inner-joined the two condition files and dropped
mismatched ids, so a missing rewrite would change N (and every metric) without
failing. Assert the id sets are identical instead.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-25 18:53:11 +08:00
wassnameandClaudypoo dfa9cdfe5a quiet per-eval logging (INFO->DEBUG) + trim README to use-focused 120 lines
Per-eval INFO lines (rows/think_tokens/aux-stats/first-row/profile/demos) demoted to
DEBUG so a consumer calling evaluate() ~47x/run is not drowned; one-time + WARNING+ kept.
README 308->120: cut process-archeology + per-instrument showcase, added crisp dlogit
and SI definitions for new users.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-25 18:51:39 +08:00
wassnameandClaudypoo 2160304541 plots: MFV reuses shared ipsative map + range in z-space; fix range coherence-gate
MFV got two bespoke figures (z-emphasis bars + dlogit dumbbell). Per GPT-5.5 code
review, reuse the geometry not the ordinal semantics: a _mfv_zspace adapter feeds the
same plot_ipsative_pca + plot_range the ordinal instruments use, in z-scored relative-
emphasis space (logit-violation and 1-5 wrongness cannot share a raw axis). Keep the
dlogit dumbbell as a raw-magnitude diagnostic; drop the bespoke map_emphasis.

Also fix a real bug the review found: plot_ordinal gated the map/SPLOM trajectory to
coherent c (pmass >= 0.95*base) but still passed ALL cs to range/range_zoom, so the
figures disagreed on which poles were valid. Range/zoom now use the same coh_cs.
plot_range gains an ylabel param (MFV is z-space, not 1-M); draw_steer asserts sorted
cs; MFV asserts only Social Norms is dropped.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-25 15:51:40 +08:00
wassnameandClaudypoo fe0d27b9bd maps: more bottom padding + clearer minimap title
Bump the bottom legend strip (PAD_B 0.52->0.62) and lower/shrink the compass + minimap insets so
the compass title clears the lowest data points. Rename the minimap title "full space" ->
"all human respondents" (says what the backdrop cloud is).

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-25 11:48:31 +08:00
wassnameandClaudypoo 22f4f64201 maps: deterministic bottom legend strip (compass left, minimap right)
Replace the per-plot least-crowded-corner heuristic (+ label pruning) with a fixed layout: pad the
y-axis bottom by PAD_B of the data range to reserve a clean strip, then always place the minimap
bottom-right (reads like a small map) and the compass bottom-left. Insets are opaque so the backdrop
haze stays behind them. Same on every instrument, no dependence on where the trajectory heads, and
drops the crowd/rank/prune code + the now-unused pad param.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-25 11:46:12 +08:00
wassnameandClaudypoo 5d2e0c3630 maps: violin SPLOM diagonals, de-grid off-diagonals, label-aware compass
- SPLOM diagonal: replace the confusing histogram/rug + vertical AI rules with a horizontal violin
  of the human spread + the AI base/+C/-C as the same trajectory dots used off-diagonal (1-D
  analogue), so the diagonal reads the same way as the rest. Unifies full + zoom (no branch).
- SPLOM off-diagonal: jitter the respondent cloud -- MFQ-2 ordinal scores land on a lattice that
  reads as grid-dots; jitter softens it to a density (it is the human joint covariance backdrop).
- Compass corner now counts text labels (society codes, steer labels), not just data points, and a
  final pass drops any society label still under the compass/minimap box. No legend-over-label.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-25 10:58:00 +08:00
wassnameandClaudypoo 00dca35f85 maps: place-or-omit society labels, unconditional society+steer crop, readable zoom diagonals
- Society labels: pin each ISO code beside its dot (small fixed offset) and DROP any that would
  collide rather than fling it far on a leader line -- close or omitted, never ambiguous.
- Crop to societies + steer for EVERY instrument (drop the synthetic/real branch): the human cloud
  (mfq2 respondents too) is far wider than the societies, so it buried them in a central blob. The
  cloud stays a clipped backdrop + a "full space" minimap shows where the frame sits.
- SPLOM zoom diagonals: the narrow window slices the marginal into solid blocks, so swap the
  histogram for a visible mid-panel society rug + the AI steer rules. Full SPLOM keeps the histogram.
- Minimap: no labels (inset too small); orientation comes from the viewport rectangle.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-25 10:49:17 +08:00
wassnameandClaudypoo 761bf7412a maps: drop society label leader-lines (draw_lines=False)
The textalloc leader lines made a spider-web across the map; 2-letter ISO codes
are short enough to sit beside their dot with only a small overlap-avoidance
nudge. Cleaner, less ink, label stays next to its point in almost all cases.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-25 10:31:36 +08:00
wassnameandClaudypoo a2fe33ac53 maps: foundation SPLOM + minimap, crop synthetic-haze maps to societies+steer
- plot_splom: KxK pairs-plot (mfq2 real joint only -- others ship independent-
  marginal haze that would fabricate off-diagonal correlation). lower=joint
  scatter w/ AI base->steer trajectory, diag=marginal+AI rules, upper=Pearson r
  sized by |r|. Ordered by PC1 loading so binding (authority/loyalty) cluster.
  full + AI-zoom (macro/micro). NaN-safe at collapsed poles.
- ipsative map: synthetic haze is far wider than the societies, so it now crops to
  societies+steer (haze clips) and adds a 'full space' minimap with a viewport
  rectangle -- readable big5/16pf/humor maps with macro context kept.
- compass labels via textalloc (was overlapping); NaN-safe crop.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-25 08:45:01 +08:00
wassnameandClaudypoo 9b13db3390 maps: multi-C trajectory overlay on ipsative map (path + hollow-at-incoherent + adaptive compass)
Driver writes the signed c-multiplier per row; plotter feeds the full c-sweep to
the range (already multi-c capable) and a connected path to the map. Headline
arrows point to calibrated c=+-1; trajectory dots at |c|>1 extend beyond, growing
with |c|, drawn hollow where admin pmass fell below the coherence floor. Compass
moves to the least-crowded corner so the +c arm (which heads toward its own
loading) stops colliding with it.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-25 08:35:29 +08:00
wassnameandClaudypoo 0e92e66415 maps: human haze on all ipsative maps (synthetic resample for big5/16pf/humor)
Split the PCA-fit basis from the scattered cloud in plot_ipsative_pca: add a
'haze' arg so instruments with only society-level mean+sd get a backdrop
(marginal Normal resample per country) without that resample dictating the
axes. mfq2 keeps its real Atari respondents (fit + haze). Previously only mfq2
had any human scatter; big5/16pf/humor showed bare society dots.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-25 07:30:37 +08:00
wassnameandClaudypoo 9a52bf3641 nan-aware zoom y-limits; render valid ordinal showcase on unified eval
plot_range_zoom crashed on the NaN-at-collapse cells (a collapsed steer pole
reads NaN by design, "do not compare"). nanmin/nanmax frame the un-collapsed
cells; draw_steer already skips the NaN arm. Figures are the first valid
ordinal showcase: mfq2/big5/16pf/humor pmass 0.99+ and the steer registers at
think=64 (mfq2 care 3.86 base -> 3.13 at C=+1), unlike the old think=0 run.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-24 21:23:43 +08:00
wassnameandClaudypoo d20f717251 maps: respondent-haze ipsative map + glossed axes (port from mft_honesty)
plot_ipsative_pca gains an optional respondents arg: when given it fits the
ipsative PCA basis on the individual cloud (3902 Atari respondents) and scatters
them behind the society dots, with an envelope-core crop, matching the
mft_honesty fig_pca_ipsative the project standardized on. Axis labels carry a
foundation-loading gloss. respondents=None keeps the old society-only behavior.

Bundles atari_study2_raw.csv + a respondent_profiles loader (keying verbatim
from Atari Code_Study2.R) so tinymfv owns the data, no mft_honesty dependency.
plot_steer_showcase passes respondents for mfq2 only.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-24 15:49:24 +08:00
wassnameandClaudypoo 9d3741fb45 Unify ordinal survey readout onto the guided think-then-read core
administer()/read_items() now route through _rollout_natural_or_forced (the
nominal MFV core) instead of a think=0 single forward, so an activation steer
accrues over the think trace before the prefilled answer slot is read (spec
moral_aliens_engine.md, resolved decision: ordinal needs a think budget). The
only per-instrument difference is the answer-token set + the downstream reducer.

force_only on the shared core: the ordinal "(" prefill is one common char, so
natural-emission detection would match it by chance in the think trace and read
logits mid-think; surveys always force-read the answer slot. Nominal path keeps
natural emission (force_only defaults False). max_think_tokens floor is 1.

Smoke (tiny-random): ordinal + nominal both run; force-only demo reads the
forced ( slot.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-24 15:37:47 +08:00
wassnameandClaudypoo e2c6c9cfe8 maps: restore joint dots on main-range arms (poles stay head-only)
The c=+-1,+-2 steps were dropped from the main range as clutter, but they
carry the steer's spacing/curvature; bring them back small (ms*0.6) while
the calibrated poles (cs[0]/cs[-1] = -+signed_C) keep head-only arrowheads.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-24 12:33:18 +08:00
wassnameandClaudypoo 95b397cabf maps: base->pole fan arrows + human/AI brackets + trimmed titles
plot_range/plot_range_zoom now draw the steer via a shared draw_steer: two arms
fanning from the base dot (red +c, blue -c), each a line + constant-size triangle
head pointing away from base (matches plot_ipsative_pca's base->pole convention,
not the old -c->+c single arrow). Marker heads not FancyArrows so short arms don't
shrink/flip; arms x-offset so a non-bidirectional steer reads as two parallel arms;
poles are head-only (no dot blob); interior step dots off on the main range, on in
the zoom. human/AI direct-label brackets pinned to the panel top. Legend prose out
of the suptitle (short headline only); callers put it in the figure caption.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-24 12:15:33 +08:00
wassnameandClaudypoo 48bb900f45 revert F1: keep NaN-at-collapse in the answer-token renorm (it is the signal)
The prior commit "fixed" p_a/pmass into a softmax to avoid NaN at coherence collapse.
That was wrong for this codebase: renormalizing within allowed tokens discards the mass,
so a distribution built from ~zero mass and one from real mass look equally comparable
after renorm -- they are not (the mean of 10 != the mean of 130). p/pmass -> NaN at
collapse poisons that item's factor, which is the honest "do not compare" signal, not a
bug. softmax was a silent fallback fabricating a comparable-looking number from incoherent
output. Restored, with a comment so it does not get re-"fixed". (Over-trusted the external
reviewer here against the repo's own no-defensive-programming rule.)

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-23 21:31:15 +08:00
wassnameandClaudypoo d5876a46d3 correctness fixes from gpt-5.5 review (ordinal path + fail-fast asserts)
Second external review (gpt-5.5, correctness-focused) on the post-cleanup tree.
Found no off-by-one/double-flip in the ordinal canonicalization+keying. Fixed the
parts I agreed with and could verify on the path the experiment uses:
- read.py: NaN-safe answer-token renorm. p_a/pmass poisons the profile with NaN when
  pmass underflows to 0 at coherence collapse -- exactly when pmass should just flag it.
  softmax(logp_allowed) is identical when pmass>0 and stable at collapse.
- maps.ipsative_pca: move SVD sign-stabilization INTO the helper so it and
  plot_ipsative_pca share one orientation (saved coords could otherwise mirror the figure).
- instrument: assert ordinal answer_space is ['1'..scale_max] IN ORDER (reduce_ordinal
  weights by position; a reordered space silently inverts E) -- was length-only.
- instrument.per_item_categorical: assert per-item dimension/sign agree across frames and
  frames are distinct, instead of silently averaging under rows[0]'s metadata.
- pyproject: move matplotlib+textalloc to an optional `maps` extra; evals stay headless.
- tests: drop imports of the deleted reduce_nominal/expected_value, inline the expectation,
  remove the now-impossible nominal-reducer test.

Deferred (flagged to maintainer): two NaN/window issues in guided.py's forced-choice
rollout (nominal evaluate() path) -- not exercised by this experiment, can't smoke-test,
and the NaN-as-collapse-signal there is a deliberate design.

Verified: experiment smoke green on all 4 instruments (no assert false-fires), 6 pure
unit tests pass, headless import clean, 16pf map renders.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-23 21:25:33 +08:00
wassnameandClaudypoo 442bc5bba3 address external API review: lazy maps, prune dead code, type administer return
Whole-library review (deepseek-v4-pro) flagged tinymfv as not-yet-ready as a shared
dep. Fixes for the parts I agreed with:
- lazy `maps` import via module __getattr__ so `import tinymfv` stays headless/fast
  (no forced matplotlib) for numeric-only consumers; `tinymfv.maps.*` still works.
- trim __all__ to the front door (entrypoints + types + data api); plumbing stays
  importable but out of `import *`.
- delete dead code: reduce_nominal + REDUCERS (evaluate folds its profile inline),
  expected_value, HF_REPO, ROOT, _DEFAULT_FORCED_HINT.
- type administer's return as a TypedDict (AdministerResult/ItemRow/ItemFrameRow) so
  the schema is documented + checkable without reading source; still a plain dict at
  runtime (zero consumer churn).
- maps.plot_ipsative_pca: parametrize the legend labels (defaults preserve output)
  and rename hon/dis -> pos/neg so a non-honesty steer gets a correct legend.
- drop 'canary' jargon and panel/review-# archaeology from comments.

Verified: `import tinymfv` no longer loads matplotlib; lazy maps still resolves;
experiment mfq2 smoke green through the typed administer.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-23 20:40:04 +08:00
wassnameandClaudypoo 494a9bbfa9 eval: verbose as int level (1=terse free-form 64ch default, 2=full demos)
Default eval logs were dominated by DEMO A/B full prompt+generation dumps at every
monitor step. verbose is now a level: 1 (default) prints the one-line aux stats plus
the free-reasoning generation collapsed to 64 chars (no prompt), bracketed by blank
lines so it stands apart from the steer demos; 2 reproduces the full first-row trace,
profile table, and complete DEMO B. bool callers still work (True->1, False->0).

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-06-23 10:51:05 +08:00