/* #viz_standard @root @agglutinative @repro @hutter_leaderboard_goal_20260701 The visualization standard: what goes in anything we publish Binding on every published surface that shows a result: the progress chart, the viewers, explainers, archive index pages, per-generation panels, and one-off pages such as /hutter/pprog/p8v2-gen1/. We own the viz layer on both tracks -- ../cmpr-src emits raw counts, everything shown to a human is ours -- so this is not a per-page style preference to be re-argued each time. THE TEST: a reader who knows the CMP paper and the UM, and who can drive cmpr, opens the page cold and needs nothing else. Not the goal block, not the fixture, not the session that produced it, not us. A page that fails this test is not published work. It is a note to ourselves that happens to have a URL. ## What may be assumed, and what may not ASSUMED. Never re-explained, never paraphrased: - general cmpr terms: block, blockid, blockref, OFRA, --run, --grep, --replace, the E:/T:/P:/f:/Omega: headers, the staleness key. - CMP paper terms: everything in https://cmpr.ai/cmp.pdf. - UM terms: event space, total thought, log support, pattern, LPP, the forward pass, omega, SN, the factor tower. AND USE THE UM'S WORD, NOT THE IMPLEMENTATION'S. Where a mechanism already has a name in the paper, the published surface uses that name even when the code calls it something else. Two rulings so far, both MJC's: one settling iteration is a TIME STEP IN f -- one application of the total (visible) pattern -- and not a "sweep"; and what a time step is built from is a PATTERN APPLICATION, one LPP's contribution to one position, and not a "message". Both replaced borrowed vocabulary (loop-over-positions, belief propagation) that carried the wrong framing with it: min(w_p, w_s) combined by lsa_max is just max-min written longhand, and calling it message passing hides that. When the generating side's columns or code keep the old word, those are theirs; ours change anyway. Cite these; do not restate them. Duplicating paper content is already forbidden in #root and it is worse on a public page, where the copy drifts from the source in public. A link and a clause of context is the whole permitted treatment. NOT ASSUMED. Must be defined ON the page, in the reader's path, before the number that depends on it: - anything invented by this experiment or by the chain of experiments it belongs to. If we coined it, we define it. Concretely, that means: sample and fixture names (e64, t1k, t10k); variant, axis and alternative labels, and how to read a packed vector like 111113; program ids and block ids the page cites; per-experiment metric names and every column of every table; the goal the work is being measured against; and any term of art the push introduced (hyperparameter, settling window, token section, DSS at a particular size). RULE OF THUMB: from the paper -> cite it. From the experiment -> define it. When unsure which, define it; the cost is a sentence. A BLOCK ID IS NOT A DEFINITION. "#omega-p8-keep-sharp was written expecting to be inert" tells a cold reader nothing. Either publish the block alongside the page and link it, or say in one clause what it is. Same for a fixture: if the page's claims live in it, symlink it in. Prefer publishing the source document over paraphrasing it. Symlinking a goal block or an OFRA doc into the published tree costs nothing (see #root, "To publish something that lives outside docs/"), and it cannot drift. ## Numbers: the size classes, then k SUPERSEDED 2026-08-05. This section used to say "everywhere k is shown, show r = P/U and the bytes". The instruction behind it stands -- never a bare derived number, always the counts underneath -- but r as a single per-byte rate over the whole model file is WRONG and was reversing our rankings. #hutter_metrics (in ../cmpr-src) is the authority and this section defers to it. THE THREE CLASSES. Every byte of the model file S belongs to exactly one, and they are NEVER summed into one per-byte rate: (a) FIXED binary, header, k=0 table, k=1 model, any stored LPP. Reported as BYTES. No extrapolation; contributes fixed/DSS. (b) SUB-LINEAR the token/lexicon section: grows with the corpus but is capped by the architecture. Reported as BYTES at the sample AND BYTES extrapolated, as model_b(M) * log(DSS)/log(M), capped. NEVER as a per-byte rate: dividing a capped quantity by a tiny sample and multiplying by the DSS charges it thousands of times over. (c) LINEAR the memory trace: gaps plus recorded bytes. Reported as the per-byte rate a = trace(M)/M. At the DSS this is >99.99% of S, so a is the ONLY quantity whose differences reach the chart, and a is therefore the headline. S/U = fixed/DSS + model_b(DSS)/DSS + a k = log(S/U) / log(0.99) EVERYWHERE A DERIVED NUMBER IS SHOWN -- a, S/U or k -- show in the same view the class (a) and class (b) byte counts and the class (c) trace bytes it came from. Byte counts exact; rates and S/U to at least 3 significant figures. Any number derived for a DSS other than the sample's own says EXTRAPOLATED and carries its basis: the sample size measured at, and the target DSS. Measured and extrapolated never share an unlabelled column. WHERE r SURVIVES: only where a program's P is entirely class (c), which is true of p7 (its table is class (a) and it has no class (b) at all) and of nothing else we have. There, r IS the class (c) rate and is correct by construction. Do not carry it to a program with a token or lexicon section. A RATE ON A SMALL PREFIX IS A LOWER BOUND, NOT AN ESTIMATE. The model is learned from the same bytes it is measured on, so at small M it has memorised the sample and a is optimistic; as M grows the same contexts recur with different successors and a RISES. Measured on the p8v2 baseline: 0.219, 0.317, 0.584, 0.730, 0.770 at M = 64 .. 10^6, still rising. So report a over a PREFIX LADDER and publish the trend; a single a is a point on a curve and is read as one. Never present one as an estimate of the asymptote. (This direction was measured, not assumed -- #hutter_metrics asserted the opposite before it was checked.) NO CHART POINT WITHOUT AN END-TO-END MEASUREMENT. A point on the progress chart means a real run at the stated size, not an extrapolation from a prefix. k is still ours and still single-sourced to docs/update-progress.py k_of(); a page for an unregistered experiment shows the classes and says why there is no point. WHAT IS NOT A RESULT, and may not be published as one (#hutter_publication_handoff): the picks in an undecided generation; any chart point without the run behind it; a small-prefix rate as an estimate; and a round-trip failure presented as a bug where the step does not require round-trip. COSTS (#hutter_run_costs): anything expensive is published with the SHAPE that makes it expensive and one clause naming what is repeated, not just a wall clock -- a wall clock alone does not say whether the next size up is twice the cost or a hundred times. OPTIMIZATIONS (#um_optimizations): an optimization changes how the machine computes and never what is reported. Reporting stays in UM terms. If a published figure disagrees with the structural line or the query layer, the figure is wrong. ## One entry point per track, and the index does not grow THE TOP-LEVEL INDEX CARRIES ONE LINK PER TRACK, NOT ONE PER PAGE. docs/index.html is a front door, not a table of contents. A track gets exactly one entry there; everything the track produces -- instruments, panels, viewers, reports -- is linked from that entry, and adding a page to the track does not add a line to the index. For the current P-program track that entry point is docs/progress.html, which is also where results go. Its "The track" section is the list; the e64 instrument, the generation panels, the viewer and the generation reports hang off it. As of 2026-08-10 the index links Progress and nothing else from the track: the three top-level pprog links it used to carry are gone. WHY. An index that gains a line per page stops ranking anything, and the lines rot in place: the e64 link sat on the front page for five days advertising "9 of 11 variants reproduced exactly" after the page had moved to generation 2, where nothing is reproduced exactly at all. A per-track entry point has one owner and one place to be wrong, and the track's own page is written by whoever changed the track. WHAT THIS MEANS WHEN YOU BUILD A PAGE. Link it from its track's entry point and add it to #viz_surfaces. Do not add it to docs/index.html. If you believe a page deserves top-level billing, that is a claim that it is a new track, and it is the programmer's call, not yours. NOT YET TRUE OF THE UM TRACK. The index still carries eight separate UM-track links -- UM Viewer, Learning Rate, Trace Viewer, Trace Variants, Trace Log, LATD, and the two P-Programming pages. They predate this policy and were left alone when the P-program track was consolidated on 2026-08-10, so the index satisfies the rule for one track and not the other. Consolidating them means choosing that track's entry page, which is a call not yet made. Until it is, this section describes the intent and one worked instance of it, not the whole index. ## The page carries the DATA. Process and history are not published MJC, 2026-08-10, on the e64 page: "this is for me to review the data, not the process and protocol which I already know", and on the corrections narrative, "reasonable in blocks to make sure it doesn't revert, but doesn't belong on published pages". Two kinds of prose keep growing on our pages and neither is data. Cut both, and cut them at the template, not the rendered file. PROCESS. Who makes the picks; that agents do not prune; the division of labour; what may be called a result. #variant_protocol, #hutter_publication_handoff, this block. All true, all binding, and all ALREADY KNOWN to the one reader every one of these pages is for -- they are his own protocol, and restating them on the page he opened to read numbers is telling him his own rules back. The Sources table links the blocks; that is the entire permitted treatment. HISTORY OF OUR OWN CORRECTIONS. "This page said X until ." "That measurement was taken before the rule was fixed." "Nothing on the page used to define these." "The three facts the goal had wrong." Every one of those exists because an agent was pleased to have found its own error. Recording it is right and the block is where it goes -- that is what stops it reverting, and none of these should be deleted from the block that carries it. On the page it is a changelog of agent mistakes sitting between the reader and the figure. THE TEST, applied to a sentence rather than a page: would the reader read a number on this page WRONG without it? If yes it stays, however awkward. If it only explains how we came to be right, it belongs in the block. WHAT THAT KEEPS. The tag on a control that was never run; the refusal where a model would have to be borrowed; the note that an axis coincides with the baseline at this sample; the one-line "nothing here is a result, no picks, no chart point". Each of those is a property of what is on screen. What it removes is the paragraph after each of them explaining the protocol that produced it. AND THE CURRENT STATE OF THE MACHINE IS DATA, ITS HISTORY IS NOT. That the model file has no k=0 section and that every offset past the header sits 256 lower is what a reader needs to read the bytes; the removal's date, ruling and rationale are the block's. State what is true now, in the present tense, and link the block for why. ## The reader can see the whole thing at once A figure the reader cannot see all of has not been shown to them. A 64-column grid that scrolls sideways in a 1180px page is the standing example (found 2026-08-10): a horizontal scrollbar hides part of the data behind an interaction, and the one thing the e64 instrument is for is watching the WHOLE window settle. WRAP, DO NOT SCROLL, when the thing being shown is a sequence: break it into stacked blocks the way a hex dump does, sized to the container and recomputed on resize, with the position labels repeated on each block so a block still reads on its own. Keep overflow-x as the fallback for a viewport too narrow for even one block; do not keep it as the design. This is not licence to shrink a cell until it fits. Legibility first, then the block width follows from it. ## Provisional material is fenced, always Where a figure shows scaffolding rather than a decision -- a placeholder score, an unspecified f, a stand-in composite -- it is marked on the figure itself, in warning colour and dashed, and listed. tests/pprog/p8v2-viz-spec.md is the standing example and /cmpr-src/p8v2/ the standing rendering. Nothing provisional may read as a design decision, and no measurement derived from a placeholder is reported as a result without saying so in the same view. ## Versioning a viewer: the generation is the version Asked for by MJC on 2026-08-10, answering choice G of #p8v2_gen3 with "g2" -- a new generation-3 page, p8v2-e64 kept as the generation-2 record -- and then: "Let's establish a convention for versioning viewers along with the contemporaneous other artifacts; something lightweight." This is that convention. It is in force in this tree now; the half that binds cmpr-src is flagged at the end. THE RULE. A published surface that renders a generation's runs BELONGS TO THAT GENERATION. It carries the generation number in its URL, it states the generation in its first screen, and when the generation is superseded it is FROZEN rather than rebuilt -- because the alternative is what happened to the generation-1 e64 page, which was rebuilt in place for generation 2 and no longer exists to be compared with. A page rebuilt in place is not a version; it is the loss of the previous one. WHAT CARRIES THE NUMBER, given that not every surface is per-generation: THE PANEL gen.html, one per generation, rendered from gen-pos/ and linked automatically. Already versioned by filename; nothing to change. They live in the legacy p8v2-gen1/ directory, which is a URL and not a claim (see #viz_surfaces). THE INSTRUMENT a page that runs f live under controls. Versioned by PATH: /hutter/pprog/p8v2-gen-/, e.g. p8v2-gen3-e64/. The existing p8v2-e64/ is the generation-2 instrument under its old unversioned name and keeps it, because a published URL is not retracted; it gains a banner naming its generation and linking its successor. EVERYTHING ELSE a surface that is not about a generation -- the progress chart, an explainer, the standalone fixture renderer -- is not versioned and says which generation it draws from where it draws from one. WHY NOT PLAIN /hutter/pprog/p8v2-gen/, which is what cmpr-src proposed as g1 and is the more obvious form. Because p8v2-gen1/ ALREADY EXISTS and is not generation 1's page: it is the home of every generation's panel, its index is generation 1's findings, and the name is legacy. Taking p8v2-gen3/ for the generation-3 instrument would make p8v2-gen1/ and p8v2-gen3/ two different kinds of thing under one naming scheme, and a reader would reasonably expect a p8v2-gen2/ that does not exist. The sample suffix also earns its place on its own: e64 is one of three samples with dumps, and an instrument is built around the one it draws. THE ARTIFACTS THAT GO WITH IT, so "contemporaneous" means something checkable. A generation-N surface reads gen.tsv, gen-pos/ and the models retained for that generation, and it names all three on the page. It does NOT read another generation's dumps to fill a gap; where it has no data it says so. The builder discovers generations from the posdir names rather than listing them, so a new generation is linked and symlinked without an edit -- generation 3 arrived with a rendered panel that nothing linked and no data beside it, one day after #viz_surfaces warned that rebuilding renders a page without linking it, and that is the failure this paragraph exists to stop. CROSS-TREE, AND NOT OURS TO SETTLE ALONE. #variant_protocol is cmpr-src's and binds both trees; the naming of gen.tsv and gen-pos/ is already theirs and already follows this. What is proposed to them is only the sentence that a generation's surfaces are frozen when the generation is superseded, so that the two trees do not disagree about what a superseded artifact is. Raised 2026-08-10; until they take it, this section binds our tree only. ## How to actually get the source onto the page Two mechanisms, and the choice between them is not a matter of taste: A FILE that already exists in a working tree (a fixture, an OFRA doc, a renderer, an acceptance script, a generated panel or TSV) is SYMLINKED into the published directory with a relative target. nginx follows symlinks, so the file is live the moment the link exists, and re-running whatever generates it republishes it. Never copy: a copy is a fork that goes stale silently, and the page then lies about a number nobody re-checked. A BLOCK has no file, so it is dumped: `cmpr --print-block '#id' > id.txt` beside the page, and the directory carries a small refresh script that redumps every block the page cites, so the snapshot is one command from current. Resolve the cmpr binary the acceptance-* way — $CMPR, then the repo's cmpr/dist/cmpr, then PATH — and resolve paths to absolute BEFORE any cd; a stale /usr/local/bin/cmpr has silently mismeasured a result before. The published page says the .txt files are snapshots and names the script. tests/pprog's published directories are the worked example. Where the blocks live in a DIFFERENT cmpr project from the docs tree — as the p-program blocks do, in ../cmpr-src while docs/ is here — say so on the page and in #root. Someone will otherwise try to print the block from the wrong project and be told it does not exist. WHICH TREE A PAGE LIVES IN -- SETTLED 2026-08-05 by #hutter_publication_handoff: "../hutter PUBLISHES everything a human looks at". A published page is a real directory under docs/, not a symlink to somebody else's tree, and the renderer that builds it is ours too. What stays theirs and is symlinked in is the DATA and the step that derives rates from it. The p8v2 generation page was moved here on that basis and its /hutter/ URL was unchanged by the move; docs/pprog/p8v2-viz still points the old way and is the remaining exception. REGENERATE, DO NOT HAND-EDIT. A page built from measurements gets a builder script next to it that re-renders it from the current data, and the builder IMPORTS the upstream derivation rather than reimplementing it, so the page cannot drift from the numbers. docs/pprog/build-p8v2-gen1 is the worked example: it imports tests/pprog/gen1-report, runs no compression, and rebuilds panel, block dumps and page in one command. A number that was typed into HTML by hand is a number nobody will re-check. ## Reproducing Every page that shows a number shows how to get it again: the command, the fixture, and the binary resolution where it matters (the p7-measured-as-P4 gotcha in #hutter_leaderboard_goal_20260701 came from a stale cmpr on PATH). Generated artifacts are symlinked into the published tree rather than copied, so re-running the acceptance script republishes them and a stale page is not possible. ## An instrument is not an essay with a widget in it MJC, reviewing the generation-3 e64 page on 2026-08-15, when it had grown to a screenful of instrument under two thousand lines of prose: "the instrument is looking good and is where we should spend more effort generally, not adding so much prose". That review is the standing rule for every viewer we build, and it decomposes into things that are checkable before publishing: THE PANES SHARE THE INSTRUMENT'S SCREEN. Anything a reader consults WHILE driving the tool -- a pattern list, a per-position table, an expansion -- is a tab under the controls, not a section further down. "Near enough that looking at both at once becomes possible" is the test, and a section that requires scrolling away from the controls has failed it. EXPLANATIONS DECAY WITH DISTANCE FROM THE THING THEY EXPLAIN. Two sections of that page -- a per-variant results table and the definitions of the columns in it -- were each individually correct and each unreadable, because they sat far from the control they were about: "I genuinely don't understand what this section is saying", and "this explanation is so far on the page from what it's about that I don't actually know what it's about". Both were fixed by DELETING THE SECTION and moving its content onto the control: the numbers onto the button that loads the run, the definitions onto the same element as a tooltip. Prefer that move to rewording. ON HOVER FIRST, DISCLOSURE SECOND, SECTION LAST. Every control carries a title. The long form is a
AT the instrument. A prose section is the last resort and usually means the control is not explaining itself. ONE PLACE PER SUBJECT. Each axis is explained once. If a page describes an axis in the control table, again in an axis section and again in a findings panel, two of those are going to go stale and the reader has no way to know which. SAY NOTHING ABOUT WHAT THE PAGE DOES NOT DO. A box explaining that the page contains no picks, no ranking and no compression number is an agent reassuring itself in public. The FENCING still applies -- an unrun combination is still marked, per "Provisional material is fenced, always" -- but it is marked ON the figure, not announced in a banner. Meta about the page (which generation it is, which page supersedes it) goes at the bottom under Provenance. WHAT THE AGENT NEEDS TO REMEMBER GOES IN THE BLOCK, NOT ON THE PAGE. That page carried a section defining what a pattern application is. MJC: "this is for you, not for me; I know what it is, as long as you remember what it is that's fine but it doesn't necessarily have to be on the page (it can be in the blocks that the page is generated from)". So: write it in the goal block, cite the normative source from the page, and do not restate it. The reader of a published instrument is not the agent that built it. SHOW IT OVER THE DATA. The strongest thing that page gained was not a table: it was painting each pattern's support onto the sample itself, one colour per application, striped where two of them share a byte. MJC on the direction: "more visualizations like this (colors or heatmaps over the data) are a good direction". Where a fact can be a shading over the data the reader is already looking at, that beats a column of positions. DEEP LINKS ARE WHAT LET COMMENTARY LEAVE. A fragment carrying the instrument's state (#v=&p=&s=&t=) makes a cell citable, and once a cell is citable the discussion of it can live on its own page and link in. Without that, prose is stuck beside the widget saying "select position 2 and open the forward application". Wants: "We want every published page to define its own experiment-specific terms" 255. "We want the three size classes never summed into one per-byte rate" 255. "We want the class byte counts shown everywhere a rate or a k is shown" 255. "We want measured and extrapolated numbers never to share a column" 255. "We want every rate published as a prefix ladder, never as a single point" 255. "We want the k definition single-sourced to update-progress.py" 255. "We want every expensive result published with its cost shape" 255. "We want provisional material fenced on the figure that shows it" 255. "We want every published page rebuilt by a script, not edited by hand" 255. "We want no published page to require a block we have not published" 200. "We want published pages to carry the data and not the protocol behind it" 255. "We want our own corrections recorded in blocks and not narrated on pages" 255. "We want the current state stated in the present tense, not as a change" 255. "We want a figure the reader can see all of without scrolling sideways" 255. "We want what a reader consults while driving a tool to share its screen" 255. "We want every control to explain itself on hover" 255. "We want no section of a page saying what the page does not do" 255. "We want each axis explained in exactly one place" 255. "We want facts shown over the data before they are shown in a table" 200. "We want every instrument cell citable by a deep link" 200. */