Replaces the vacuous 'measurement is the profile' opener with the intended-vs-unintended-while-coherent
framing: pmass/entropy as the coherence gate, the human-comparable profile (and why it hides steering),
and C the rank-centered logit contrast as the sensitive steer signal. Keeps the formulas.
Group figures into three narrative sections instead of per-instrument boilerplate. Introduce moral
foundations and the steering knob in plain words; drop invented units (nat, reader-logit, % country SD)
for concrete comparisons. Intro paragraphs left as the user edited them.
One shared orient_geographic() helper folds axis signs into the coords (value maps) or into Vt
(ipsative PCA, so dots/haze/steer/compass all inherit one flip). Replaces the WVS-only invert_x hack;
every map now reads left/right the same way. Regenerate instrument showcase maps.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
docs/img/README.md is a captioned gallery: each figure states what the dots are, the
named axes and their source paper, the readout (rated sampling N=12 for the frontier
panel, logprobs N=8 for the steering showcase), and for the steer the base model
(Qwen3-4B), the Authority PCA vector, and +c/-c directions. Regenerate all maps with the
edge-inset + roomier margins.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
allocate_labels now keeps a few px inside the axes (edge_pad), so a label pushed toward
the frame (e.g. Serbia against the Adaptive pole) can't render flush-clipped; and the
value-map margin goes 0.13 -> 0.17 so the pole signposts sit in a real inner margin.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
The four pole signposts (Adaptive / Self-directed ...) draw on the crosshair edges after
label placement, so a zone name near the frame collided with them (African-Islamic vs
Adaptive on the humor map). Feed each pole in as a short keep-out band of obstacle points.
Regenerate committed WVS + showcase maps.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
plot_ipsative_pca (society codes + zone names + base/steer) and plot_range_zoom now use
labelplace.allocate_labels, the same placer as the WVS/value maps, so all maps read
alike: zone names hug their hull, points sit adjacent, short leader only when crowded.
Labels are collected then placed AFTER the crop so the pixel-space allocator sees the
final window. The compass rose is deterministic radial-outward (a rose is not a map --
the nearest-clear-slot search fought the radial layout). No map imports textalloc now.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Region labels maximised clearance, which fled to the FARTHEST open space -- the polygon
name ended up floating far from its hull. Instead take the nearest perimeter ring that
has a clear slot (emptiest spot on that ring), so the name hugs its own hull edge. Marker
spacing beyond the glyph is now anisotropic (spacing_x=0.5, spacing_y=0.3): text stacks
tighter vertically, so pull labels in more up-down than left-right. Marker clearance pad
is unscaled, so nothing lands on its own star. Re-render WVS + showcase.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Renders the dream artifact: a 5x5 grid of MFV profile bar charts (one per
(hc, cc) cell), per-foundation dlogit heatmaps for MFV, per-factor C heatmaps
for ordinal instruments, and a pmass coherence heatmap. Reads the 2D-grid
profiles CSVs written by steering-lite's run_2d_grid_showcase.py.
The per-instrument value maps (big5/humor/mfq2) now route labels through
labelplace.allocate_labels: zone names in clear air (no white boxes), country
labels adjacent, steer path unchanged. Ipsative/range figures re-emitted from the
same run for a consistent set.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Fresh-eyes QA caught the "East Asia" zone label hugging its (central, crowded) red hull
edge. Region labels only searched 0-0.9 text-heights off the perimeter; widen to 3.0 so
the clearance search can escape a surrounded hull into open space.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
The 17-model IW map now renders through labelplace.allocate_labels; save the artifact
into the repo (png for viewing, svg for hand-editing labels, CI table as companion).
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Each label owns a set of 1..N candidate anchor points: a marker label passes its
single point, a zone label passes its whole densified hull perimeter (densify_polygon
promotes the polygon PATH to points, since a hull stores only ~6 corners). Two obstacle
classes: hard (markers + placed labels, never covered) and soft (polygon edges, only
region labels avoid; marker labels wear a thin white outline and may cross). Region
labels maximise clearance over their perimeter -> emptiest open air, no white box, no
leader; marker labels take the nearest clear slot with a ~half-char gap and a leader
only when far. Runs in pixel space AFTER invert_xaxis so the try-every-side geometry
isn't mirrored (fixes the all-labels-drift-left bug). Adapted from textalloc + wassname's
plotly placer.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Switch point labels back from adjustText (force-relaxation, local minima, sometimes
parks a label on its own marker) to textalloc, whose grid+candidate-box algorithm
tries slots on every side of a marker and takes the roomier one, off the marker,
with a leader only when it must reach. Keep _hull_label_pos (with outward ha/va) for
zone labels so they sit just outside the emptiest arc of their hull. Feed textalloc
the dots + sampled hull edges + zone-label spots so it dodges polygons too.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Point labels (countries/models) now avoid sitting on a coloured hull line, not just
on a dot -- adjustText only knows points, so sample every other hull-boundary vertex
into its obstacle cloud.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Replace the fling-to-open-space-with-a-leader zone placement with _hull_label_pos:
walk the hull's own boundary vertices, nudge each slightly outward, and put the
label on the vertex whose nearest dot/label is farthest -- so the name sits against
an uncrowded arc of its own outline, no leader line. And flip X (invert_x) so
Self-expression is on the left and Survival on the right: this is a better map than
the Economist's, and it puts the cultural West on the left / East Asia on the right.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Zone labels now get a dedicated open-space search (_open_slot: scan a ring around
the hull centroid, keep the slot whose nearest dot/label is farthest) instead of
adjustText's local minimum -- so Latin America/African-Islamic land in the sparse
margins with a leader to their hull, not the crowded centre. And with 17 models the
names were too many: plot every star but LABEL only the latest per family (highest
version), dropping the redundant 'claude-' so the flagship reads 'opus-4.8'. Colour
+ legend carry the unlabelled siblings.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
The zone names (Latin America, African-Islamic, ...) were nailed to their hull's
top vertex and bypassed the label allocator, so they landed in crowded spots while
open space sat nearby. Now every label -- country, model star, zone name, steer --
goes through a single adjustText call (force repulsion off the dots and each other,
leader lines), and each zone label is seeded at its hull's OUTWARD-most vertex so it
starts in the sparse margin. Also widen the family-colour hue spacing (grok now
near-black, clear of gpt blue; llama/gemini/gemma separated). textalloc stays for
the compass/range insets for now.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Consolidates provenance that was only in terse CSV source-tags + commit bodies:
per source, full citation + DOI/OSF URL + which table/figure + the exact data
transformation (Care-collapse, Brazil affine bias-cal, Australia raw-recompute).
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Computed from raw participant ratings in dat_rep.sav (OSF cmwpv, full 90-item
Clifford MFV, 1-5), using the author's own item->foundation map from the R
notebook. Complete-case N=756 reproduces the paper's abstract exactly. The
paper's headline is a genetic-algorithm-abbreviated MFV; we deliberately used
the FULL 90 items so Australia is comparable to the other full-instrument rows.
The paper's other sample (580 US MTurk) is skipped -- it would duplicate US.
Cloud now 7 -> 8. Fresh-eyes subagent reproduced all six means to <=0.0005.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Models are now stars coloured by lab family instead of one Economist red, hue
chosen to echo the lab's home region: Chinese labs warm (qwen orange, deepseek
pink) near the East-Asia red, US labs cool (claude purple, gpt blue, gemini/gemma
sea blue, grok indigo, llama steel), Europe green (mistral). Legend keys each
family. Also drop the ' (rated)' tag from on-map labels and Nigeria from the
always-on landmarks (Egypt already anchors the African-Islamic corner).
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
SDs recovered from user-digitized 95% CI whiskers (per Fig 3 caption),
sd = halfwidth/1.96*sqrt(494); each whisker pair's midpoint matches its
mean to <=0.005 (validated).
Bias-corrected the digitized means with an affine fit to the two paper-
stated Purity anchors (Brazil 3.45, Clifford US 3.85; text lines 361-362):
true = 0.9155*digitized + 0.228. Purity now lands exactly on 3.45. Since
the map z-scores within-country, this is provably map-neutral (max |dz|
= 0.017, all rounding); it only makes the stored absolute means/SDs right
for any non-map reuse.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
With a dozen+ models the 95% CI crosses overlap into noise. Remove the whiskers
from plot_value_map (clean red dots) and instead write a widest-first CI table
(wvs_model_ci.md) next to the figure. The table also documents that the wide CIs
are item-disagreement (a model rating different WVS items inconsistently on an
axis), which more samples will not shrink -- not sampling noise.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
A reasoning model (e.g. gemini-2.5-pro) can spend its whole token budget thinking
and truncate the JSON mid-object -> parse collapse -> the model was dropped from
the panel. Now, when a rating reply doesn't parse, fire one follow-up in the same
conversation: feed the truncated reasoning back and demand a compact one-line answer
NOW. It has already thought, so it commits. Works even where reasoning can't be
disabled (gemini 400s on reasoning_effort=none) since we constrain the output, not
the thinking. Pattern from wassname's bounded-thinking judge (gist 72eed3a1).
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
No per-foundation table in the paper; means digitized (WebPlotDigitizer)
from Figure 3 Brazil series. Care collapsed from Care E(3.93)+Care P(4.68)
-> 4.31. SDs not recoverable (whiskers not captured, paper tabulates none),
so those columns left empty; the map reads only mean.
Calibration: digitized Purity 3.52 vs paper's stated 3.45 (~0.07 high, a
uniform bias that cancels under the map's within-country z-scoring).
Brazil profile reproduces the paper: individualizing (care/fairness/liberty)
high, loyalty lowest, sanctity relatively low vs the US. Cloud now 6 -> 7.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
One request stuck in the wrapper's stamina backoff (a rate-limited provider)
would stall the whole model's asyncio.gather, blocking the panel. Cap each
request at req_timeout=90s and drop it as a failed sample so slow providers
degrade gracefully instead of hanging the run.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
plot_value_map now takes a steer= dict and draws the steer as a connected
black->red(+c)/blue(-c) path (matching the ipsative map's trajectory) instead
of three disconnected model dots. draw_zone_hulls returns its label anchors so
textalloc routes country/model labels around zone names. Regenerated the
showcase (mfq2/big5/humor value maps are new; mfv ipsative picks up Netherlands).
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Clifford MFV wrongness vignettes, 1-5 scale (same as existing LatAm/Japan
rows). Care collapsed from their split physical(4.09)+emotional(3.53) to a
single 3.81; other foundations from their Table 1 with paper 95% CIs.
Grows the human country cloud 5 -> 6.
Note: the Dutch and LatAm authors both flag MFV measurement non-invariance
(DIF) across countries, so between-country means are a rough reference, not
a calibrated ranking. Map is ipsative (within-country z), which is affine-
invariant to the scale, so it reads relative foundation emphasis.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Replace the single forced-choice sampling reader (1 bit/call, truncated verbose
models to empty at max_tokens=32, positionally biased) with a dense readout:
rate every option 1-5 as JSON, N samples, binary items order-balanced to cancel
positional bias, ratings mapped back to canonical order and normalized to a
distribution. All of a model's calls fire concurrently (asyncio.gather over the
async openrouter_wrapper; per_call=1 since providers ignore n>1). Each model
carries a bootstrap 95% CI (over items + samples) drawn as error bars, so mushy
models (deepseek-flash was near coin-flip) read as uncertain, not confident dots.
One flaky provider is skipped, not fatal.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
_zscore now asserts nonzero variance (a flat profile drew a degenerate all-zero
map that looked valid) instead of dividing by std+1e-9. _soft_nll docstring now
states the 1e-12 floor it actually applies (was 'Unbounded'). Trim archaeology
comments (guided/wvs/read_api) to the invariant, dropping the dated model names.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
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>
Hatchling rejects a direct git reference in an optional-dependency unless this
flag is set; without it uv could not build tiny-mfv and every 'uv run' failed.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Russian validation of the Knutson/Kruepke Realistic Moral Vignettes
(norm violation / social affect / intention), sent by the authors.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
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>
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>
plot_ordinal now saves map_value.{png,svg} (societies + AI base/steered projected
onto the instrument's two named value axes) next to map_pca_ipsative. Untested
end-to-end (needs a run_allinstr_showcase profile set); the shared plot_value_map
renderer it calls is verified on human-only renders.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
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>
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>
The cache was written once at the very end, so a killed run (session teardown)
discarded every sampled model. Persist after each model instead, and expand the
model-star palette to 14 so a large model panel doesn't silently drop stars past
the 6th via zip truncation.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
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>