// Simple numbering for non-book documents #let equation-numbering = "(1)" #let callout-numbering = "1" #let subfloat-numbering(n-super, subfloat-idx) = { numbering("1a", n-super, subfloat-idx) } // Theorem configuration for theorion // Simple numbering for non-book documents (no heading inheritance) #let theorem-inherited-levels = 0 // Theorem numbering format (can be overridden by extensions for appendix support) // This function returns the numbering pattern to use #let theorem-numbering(loc) = "1.1" // Default theorem render function #let theorem-render(prefix: none, title: "", full-title: auto, body) = { if full-title != "" and full-title != auto and full-title != none { strong[#full-title.] h(0.5em) } body } // Some definitions presupposed by pandoc's typst output. #let content-to-string(content) = { if content.has("text") { content.text } else if content.has("children") { content.children.map(content-to-string).join("") } else if content.has("body") { content-to-string(content.body) } else if content == [ ] { " " } } #let horizontalrule = line(start: (25%,0%), end: (75%,0%)) #let endnote(num, contents) = [ #stack(dir: ltr, spacing: 3pt, super[#num], contents) ] #show terms.item: it => block(breakable: false)[ #text(weight: "bold")[#it.term] #block(inset: (left: 1.5em, top: -0.4em))[#it.description] ] // Some quarto-specific definitions. #show raw.where(block: true): set block( fill: luma(230), width: 100%, inset: 8pt, radius: 2pt ) #let block_with_new_content(old_block, new_content) = { let fields = old_block.fields() let _ = fields.remove("body") if fields.at("below", default: none) != none { // TODO: this is a hack because below is a "synthesized element" // according to the experts in the typst discord... fields.below = fields.below.abs } block.with(..fields)(new_content) } #let empty(v) = { if type(v) == str { // two dollar signs here because we're technically inside // a Pandoc template :grimace: v.matches(regex("^\\s*$")).at(0, default: none) != none } else if type(v) == content { if v.at("text", default: none) != none { return empty(v.text) } for child in v.at("children", default: ()) { if not empty(child) { return false } } return true } } // Subfloats // This is a technique that we adapted from https://github.com/tingerrr/subpar/ #let quartosubfloatcounter = counter("quartosubfloatcounter") #let quarto_super( kind: str, caption: none, label: none, supplement: str, position: none, subcapnumbering: "(a)", body, ) = { context { let figcounter = counter(figure.where(kind: kind)) let n-super = figcounter.get().first() + 1 set figure.caption(position: position) [#figure( kind: kind, supplement: supplement, caption: caption, { show figure.where(kind: kind): set figure(numbering: _ => { let subfloat-idx = quartosubfloatcounter.get().first() + 1 subfloat-numbering(n-super, subfloat-idx) }) show figure.where(kind: kind): set figure.caption(position: position) show figure: it => { let num = numbering(subcapnumbering, n-super, quartosubfloatcounter.get().first() + 1) show figure.caption: it => block({ num.slice(2) // I don't understand why the numbering contains output that it really shouldn't, but this fixes it shrug? [ ] it.body }) quartosubfloatcounter.step() it counter(figure.where(kind: it.kind)).update(n => n - 1) } quartosubfloatcounter.update(0) body } )#label] } } // callout rendering // this is a figure show rule because callouts are crossreferenceable #show figure: it => { if type(it.kind) != str { return it } let kind_match = it.kind.matches(regex("^quarto-callout-(.*)")).at(0, default: none) if kind_match == none { return it } let kind = kind_match.captures.at(0, default: "other") kind = upper(kind.first()) + kind.slice(1) // now we pull apart the callout and reassemble it with the crossref name and counter // when we cleanup pandoc's emitted code to avoid spaces this will have to change let old_callout = it.body.children.at(1).body.children.at(1) let old_title_block = old_callout.body.children.at(0) let children = old_title_block.body.body.children let old_title = if children.len() == 1 { children.at(0) // no icon: title at index 0 } else { children.at(1) // with icon: title at index 1 } // TODO use custom separator if available // Use the figure's counter display which handles chapter-based numbering // (when numbering is a function that includes the heading counter) let callout_num = it.counter.display(it.numbering) let new_title = if empty(old_title) { [#kind #callout_num] } else { [#kind #callout_num: #old_title] } let new_title_block = block_with_new_content( old_title_block, block_with_new_content( old_title_block.body, if children.len() == 1 { new_title // no icon: just the title } else { children.at(0) + new_title // with icon: preserve icon block + new title })) align(left, block_with_new_content(old_callout, block(below: 0pt, new_title_block) + old_callout.body.children.at(1))) } // 2023-10-09: #fa-icon("fa-info") is not working, so we'll eval "#fa-info()" instead #let callout(body: [], title: "Callout", background_color: rgb("#dddddd"), icon: none, icon_color: black, body_background_color: white) = { block( breakable: false, fill: background_color, stroke: (paint: icon_color, thickness: 0.5pt, cap: "round"), width: 100%, radius: 2pt, block( inset: 1pt, width: 100%, below: 0pt, block( fill: background_color, width: 100%, inset: 8pt)[#if icon != none [#text(icon_color, weight: 900)[#icon] ]#title]) + if(body != []){ block( inset: 1pt, width: 100%, block(fill: body_background_color, width: 100%, inset: 8pt, body)) } ) } // arxivai — Quarto Typst template partial. // Replaces Quarto's default `article()` with the arxivai preprint layout. // Both packages are importable from raw ```{=typst}``` blocks in the body: // #arxivai.operation-box(...) #arxivai.app-figure(...) #lettreai.badge(...) #import "lettreai.typ" as lettreai #import "arxivai.typ" as arxivai // --------------------------------------------------------------------------- // Re-theme Quarto's automatic callouts (same contract as lettreai: block // nesting kept structurally identical to Quarto's default so the crossref // figure show rule in definitions.typ keeps working). // --------------------------------------------------------------------------- #let callout( body: [], title: "Callout", background_color: rgb("#dddddd"), icon: none, icon_color: black, body_background_color: white, ) = { let accent = if icon_color == black { lettreai.colors.oxford-blue } else { icon_color } block( breakable: false, fill: lettreai.tint(accent, 0.94), stroke: (left: 3pt + accent, rest: 0.5pt + lettreai.tint(accent, 0.5)), width: 100%, radius: 3pt, block( inset: 1pt, width: 100%, below: 0pt, block( fill: lettreai.tint(accent, 0.84), width: 100%, inset: 8pt, )[#if icon != none [#text(accent, weight: 900)[#icon] ]#text(fill: lettreai.shade(accent, 0.25), weight: 700)[#title]]) + if (body != []) { block( inset: 1pt, width: 100%, block(fill: body_background_color, width: 100%, inset: 8pt, body)) } ) } // --------------------------------------------------------------------------- // arXiv preprint article — same signature contract as Quarto's default, // extended with keywords and running-title. // --------------------------------------------------------------------------- #let article( title: none, subtitle: none, authors: none, keywords: (), running-title: none, book: false, date: none, abstract-title: none, abstract: none, thanks: none, cols: 1, lang: "en", region: "US", font: none, fontsize: 10.5pt, title-size: 1.7em, subtitle-size: 1.15em, heading-family: none, heading-weight: "bold", heading-style: "normal", heading-color: black, heading-line-height: 0.65em, mathfont: none, codefont: none, linestretch: 1, sectionnumbering: none, linkcolor: none, citecolor: none, filecolor: none, toc: false, toc_title: none, toc_depth: none, toc_indent: 1.5em, doc, ) = { // PDF metadata. set document(title: title) set document( author: authors.map(a => lettreai.to-string(a.name)).join(", ", last: " & "), ) if authors != none and authors != () set document( keywords: keywords.map(k => lettreai.to-string(k)), ) if keywords != none and keywords != () set par(justify: true, leading: linestretch * 0.65em) set text(lang: lang, region: region, size: fontsize) show math.equation: set text(font: mathfont) if mathfont != none set heading(numbering: sectionnumbering) // Running header + footer; book mode gets chapter-aware heads and the // O'Reilly outer-margin geometry. let rt = if running-title != none { running-title } else if title != none { title } else { [] } set page( header: if book { arxivai.book-header() } else { arxivai.arxiv-header(rt) }, footer: if book { arxivai.book-footer() } else { arxivai.arxiv-footer() }, ) set page(margin: arxivai.book-margin) if book // Resolve fonts: explicit Quarto fonts win, else theme defaults. let resolved-font = if font != none { font } else { lettreai.body-font } let resolved-code = if codefont != none { codefont } else { lettreai.mono-font } let resolved-heading-color = if heading-color != black { heading-color } else { lettreai.colors.oxford-blue } let resolved-link = if linkcolor != none { rgb(lettreai.to-string(linkcolor)) } else { lettreai.colors.oxford-blue } // Paint everything with the shared theme, then the figure rules. show: lettreai.theme-rules.with( fonts: resolved-font, code-font: resolved-code, heading-color: resolved-heading-color, link-color: resolved-link, ) show: arxivai.arxiv-figure-rules // Book chapter rule is the innermost show, so it transforms level-1 // headings FIRST (inner rules apply first), replacing them with chapter // openers before the theme's h1 rule could wrap them in a container. // Synthetic non-outlined headings (e.g. the outline's own title) are // excluded inside book-heading-rules. show: doc2 => if book { arxivai.book-heading-rules(doc2) } else { doc2 } show ref: set text(fill: rgb(lettreai.to-string(citecolor))) if citecolor != none // Preprint front matter. arxivai.arxiv-title-block( title: title, subtitle: subtitle, authors: authors, date: date, ) if abstract != none { arxivai.arxiv-abstract( abstract, abstract-title: if abstract-title == none { [Abstract] } else { abstract-title }, ) } arxivai.arxiv-keywords(keywords) // Book mode: the front matter is a title page of its own. if book { pagebreak(weak: true) } // Optional table of contents (its own page in book mode). if toc { block(above: 0.5em, below: 2em, lettreai.lettreai-outline( title: if toc_title == none { auto } else { toc_title }, depth: toc_depth, indent: toc_indent, )) if book { pagebreak(weak: true) } } if cols > 1 { columns(cols, doc) } else { doc } } #import "@preview/fontawesome:0.5.0": * #let brand-color = (:) #let brand-color-background = (:) #let brand-logo = (:) #set page( paper: "a4", margin: (x: 2.4cm,y: 2.6cm,), numbering: "1", columns: 1, ) #show: doc => article( title: [Where Did Kepler's Stars Come From?], subtitle: [A chat, its corrections, and an evidence-bound answer about the star catalogue of the Tabulae Rudolphinae], authors: ( ( name: [Gerd Graßhoff], affiliation: [Humboldt-Universität zu Berlin, Deutsches Museum], email: [gerd.grasshoff\@hu-berlin.de], orcid: [0000-0002-4450-357X] ), ), keywords: ([Kepler],[Tycho Brahe],[Tabulae Rudolphinae],[star catalogue],[large language models],[evidence-bound answers],[justification],), running-title: [Famulus Tabula Rudolphina], date: [2026-10-08], lang: "en", abstract: [SKETCH --- to be written last, from the measured results. Planned content: a regular chat about the origin of the star catalogue of Kepler's Tabulae Rudolphinae (1627) gives a fluent history in which the decisive propositions are unsupported; only the questioner's objections move it, and each move replaces one unsupported claim by another. We decompose the three turns of a real chat into a ledger of propositions, unfold the question "how did Kepler obtain the data for his star catalogue and how did he compute the tabulated values?" into subgoals, build the evidence space (Kepler's Gesammelte Werke, Tycho Brahe's Opera omnia, the secondary literature in the collection), filter it per subgoal with Famulus Evidence Filter, and answer each subgoal with loci, reasoning and a verdict. The chat's claims are then judged one by one: validated, not validated, or refuted by evidence. ], abstract-title: "Abstract", font: ("Source Sans 3",), sectionnumbering: "1.1", toc_title: [Table of contents], toc_depth: 3, doc, ) = The question and the thesis #emph[To write (W10).] The question G0: #strong[How did Kepler obtain the data for the star catalogue of the Tabulae Rudolphinae, and how did he compute the tabulated values?] Why it is a good test: the catalogue is a table with a numeric ground truth in two editions (Tycho's catalogues in Dreyer's Opera omnia, Kepler's in KGW X); its origin is stated, with different reach, by the author himself (headings, Praecepta, letters), by the editors (Hammer's Nachbericht, Dreyer's annotations), and by the secondary literature. Thesis, in three parts: + A regular chat answer to G0 is a fluent narrative whose decisive propositions have no registered ground; several are wrong, and the chat does not mark which. + The answer improves only where the questioner objects. The questioner has to know the answer to force it; each correction introduces new unsupported propositions (authority drift: an encyclopaedia, then specialist web pages, then an untranslated "own words of Kepler"). + An evidence-bound answer reverses the order: the evidence space is determined first, filtered per subgoal, and every proposition of the answer names the loci it follows from. Where the space holds no ground the answer says so ("not validated"), which is not the same as "false". = The case: a chat in three turns #emph[To write (W10), from `source/grok-chat.md`.] Turn by turn: what was asked, what was answered, what changed. The transcript is reproduced in #ref(, supplement: [Section]). - #strong[Turn 1] asks for the history and chronology of Kepler's work on the tables. The answer gives a chronology and, under #emph[Means], "Brahe's … observations …, supplemented by Kepler's own". - #strong[Turn 2] objects: "Kepler did not add 200 stellar positions from his own observations. He could not handle observations, nor had he the instruments." The chat concedes at once and now cites "more than 400 stars from Ptolemy and Johann Bayer". - #strong[Turn 3] demands evidence and names the Almagest's systematic error. The chat withdraws the "400", introduces Tycho's \~1,004-star manuscript catalogue, a #emph[Secunda Classis] with a "translation" of Kepler's description, a #emph[Tertia Classis] from Keyser and de Houtman, and closes that no star-by-star list is given in the literature it consulted. Note for the text: the questioner's premises are claims too (U2-02, U2-03 in the ledger) and are judged by the same standard (Q11). = The claim ledger #emph[Data: `data/claims.yaml` (W3, built); verdict columns filled in W9; table generated into `_claim-ledger.qmd`.] Each turn is decomposed into propositions with a role (core / side), a kind (number, provenance, method, chronology, quotation, attribution, appraisal), the support the chat offers (a web domain, a quotation, none), its course through the turns (asserted, conceded, retracted, replaced) and the subgoal that decides it. = The dynamics of correction #emph[To measure from the ledger (W9):] propositions per turn; share retracted only when the questioner objected; share newly introduced without a ground; internal inconsistencies within one turn (T2: "\~228 beyond Brahe's 777" next to "more than 400 from Ptolemy and Bayer"); the supports cited per turn. = Goal and unfolding #emph[Data: `famulus-proof/eval/ziele/tabula-rudolphina.yaml` (W5).] G0 and its subgoals: #table( columns: (33.33%, 33.33%, 33.33%), align: (auto,auto,auto,), table.header([id], [subgoal], [evidence kind],), table.hline(), [Q1], [composition of the catalogue: classes, headings, entries per class], [primary (KGW X tables)], [Q2], [Kepler's own statements on the source of each class], [primary (KGW X)], [Q3], [Tycho's catalogues in the edition: printed 777, manuscript 1000; epoch, entries], [primary (Opera omnia II, III)], [Q4], [collation: Kepler's class I against Tycho's 1000-star catalogue, entry by entry], [primary, computed], [Q5], [epoch and precession: which epoch, which constants, how reduced], [primary + recomputation], [Q6], [the second class: which stars, which source, which transformation], [primary (+ Almagest if available)], [Q7], [the southern stars: which entries, which attribution], [primary (KGW X)], [Q8], [correspondence: letters on the catalogue, Tycho's data and the heirs, dated], [primary (KGW XIII--XVIII)], [Q9], [working papers on fixed stars and on the computation], [primary (KGW XX--XXI)], [Q10], [editorial commentary (Hammer, Dreyer)], [commentary], [Q11], [Kepler as observer of stellar positions (tests the questioner's premise)], [primary, all KGW + Opera omnia], ) = The evidence space #emph[Data: readiness reports (W4).] Collections, volumes and their kinds of evidence, each with its justificatory reach: - #strong[Primary edited text.] KGW X (#emph[Tabulae Rudolphinae], ed.~Hammer 1969, #cite(, form: "prose")); Tycho's Opera omnia (ed.~Dreyer), in particular II (#emph[Progymnasmata], 777-star catalogue, #cite(, form: "prose")), III (the catalogue of 1000 stars, #emph[Stellarum inerrantium restitutio], #cite(, form: "prose")), and the observation volumes X--XIII with their star tables (#cite(, form: "prose")\; #cite(, form: "prose")\; #cite(, form: "prose")\; #cite(, form: "prose")). - #strong[Editorial commentary.] Hammer's Nachbericht and Anmerkungen in KGW X; Dreyer's #emph[Annotationes editoris]. A commentary sentence justifies only "the editor states that p"\; it supports a claim about Kepler only together with the primary loci it cites. - #strong[Instruments.] KGW XXII (index), the letter register of KGW XIII--XVIII. - #strong[Secondary literature] in `kepler-sec`. Also in the space, though not yet read as text: Ptolemy's star catalogue (#emph[Almagest] VII 5 -- VIII 1) in Toomer's translation (`greek-astronomy`, scans of 1984; only the first four or five pages of each document there are OCR'd although the collection reports itself search-ready; a pilot of the table pass on five catalogue pages, 2026-10-08, was paused and nothing of it is applied, so Verbunt and van Gent's machine-readable edition of the catalogue after Toomer @VerbuntVanGent2012 stands in for it), and Graßhoff's #emph[History of Ptolemy's Star Catalogue] (1990) as 361 page images with a plain-text OCR layer of July 2026 but no page XML, in a collection (`assyrischeSammlung`) that is not search-ready. Heiberg's #emph[Syntaxis] I covers Books I--VI only. Gaps: Opera omnia vol.~XIV is missing; no Bayer #emph[Uranometria] and no Grienberger 1612 were found (search of 2026-10-08). #strong[Careful table OCR.] The catalogues are tables; a cell read correctly but set one row off gives a star its neighbour's identification. The KGW X catalogue pages had been read in March/April 2026 as flat lines or not at all; Tycho's catalogue pages had their editorial Flamsteed/Bayer numbers shifted by one row. The tables were therefore re-read (2026-10-08) by two engines under a catalogue page class (Kimi K2.7 as producer, Gemini 3.8 Flash as second reader), placed on the page by Apple Vision's line geometry (row shifts corrected only where the moved grid verifies), compared cell by cell, and disputed cells decided by a three-voter arbiter on the enlarged row (signs: by comparison with exemplar rows of the same typeface). Every decision is stored beside the page (`page_NNN.grid.json`). #emph[Numbers to report (W6): pages, cells, agreement, corrections, open cells.] = Filtering the evidence space #emph[Data: `famulus-proof/reports/data/tabula-rudolphina/.json` (W6), table `_evidence-funnel.qmd`.] Per subgoal: the whole space, each filter stage of Famulus Evidence Filter, the carriers that remain; counts and selectivity; undecided carriers kept for recall. A lexical seed over KGW (`data/ evidence-seed-kepler-prim.json`: 446 sentences on 355 pages) shows why a filter is needed: most hits of #emph[catalogus] are book-fair catalogues. #figure([ #table( columns: (9%, 8%, 16%, 10%, 10%, 37%, 10%), align: (auto,auto,right,right,right,auto,right,), table.header([subgoal], [coll.], [objects in the space], [kept], [volumes], [kinds kept], [ms],), table.hline(), [Q1], [KGW], [637,959], [1,490], [1], [1,487 rows · 2 par. · 1 sent.], [346], [Q2], [KGW], [637,959], [55], [1], [55 sent.], [465], [Q3], [OO], [293,238], [5,153], [13], [5,101 rows · 52 sent.], [334], [Q4], [KGW], [637,959], [1,487], [1], [1,487 rows], [268], [Q4], [OO], [293,238], [1,903], [2], [1,903 rows], [245], [Q5], [KGW], [637,959], [14], [1], [14 sent.], [351], [Q6], [KGW], [637,959], [23], [1], [17 sent. · 6 rows], [302], [Q7], [KGW], [637,959], [59], [1], [59 sent.], [335], [Q8], [KGW], [637,959], [23], [5], [23 sent.], [467], [Q9], [KGW], [637,959], [494], [6], [494 sent.], [348], [Q10], [KGW], [637,959], [32], [8], [32 sent.], [487], [Q10], [OO], [293,238], [3], [2], [3 sent.], [403], [Q11], [KGW], [637,959], [143], [20], [143 sent.], [413], [Q11], [OO], [293,238], [161], [14], [161 sent.], [365], ) ], caption: figure.caption( position: top, [ Filtering the evidence space per subgoal (Famulus Evidence Filter, nine stages, given classification; KGW = kepler-prim, OO = braheOO). Goal file `eval/ziele/tabula-rudolphina.yaml`. ]), kind: "quarto-float-tbl", supplement: "Table", ) = Justified answers per subgoal #emph[To write (W7, W8).] For each subgoal: loci (Latin with English translation, machine-checked against the OCR), the reasoning, recomputations, verdict. #block[ #callout( body: [ - KGW X Tab. S. 138 (1627 pagination 118): the heading of the #emph[Tertia Classis] names its source. The figures come "Ex BAYERI verò Tabulis et Manuscriptis ultimis", put "in numeros et chartam" by Jacob Bartsch and sent "nuper mihi Augusta Ulmam"\; Bayer, Kepler reports, credits the constellations to Vespucci, Corsali and Pedro de Medina and their first correction to "Petrus Theodori" (B-TR-138). - KGW X Tab. S. 133: for stars of Sagittarius Kepler took distances that Tycho had noted by hand at Regiomontanus' observation of 19 June 1465, "Secundùm harum differentiam à Ptolemaicis accommodavi et reliquarum Sagittarij longitudines ex PTOLEMAEO" (B-TR-133). - KGW X Tab. S. 141: "Hactenus Bartschianus Catalogus. Finis Catalogi Fixarum" (B-TR-141). No verdict on the chat's claims is drawn here; that is task W9. ] , title: [ First primary loci (checked on the facsimile, 2026-10-08; register `data/belege.yaml`) ] , background_color: rgb("#dae6fb") , icon_color: rgb("#0758E5") , icon: fa-info() , body_background_color: white ) ] #block[ #callout( body: [ KGW X, Nachbericht S. 42#emph[--43] (scan page\_458--459), states in German that Kepler divides his catalogue into three classes: first Brahe's list of 1000 fixed stars with longitudes and latitudes computed for 1600 (citing Brahe, Op. om. III, 344--373); second more than 200 stars drawn from Ptolemy's catalogue, reduced to 1600 by taking over the corrections of nearby stars in Tycho's catalogue; third 136 southern stars whose numbers Kepler, "wie er Tab. S. 118 sagt", received in Ulm from Jakob Bartsch, who had them from Johann Bayer. This is commentary-level evidence: it justifies "Hammer states that …", and it names the primary loci (Tab. S. 118, Tab. S. 130, Op. om. III 344--373) that must be read before any of it is asserted about Kepler. One detail to check against the facsimile: the range given for the first class ("Tab. 105--114"). ] , title: [ Probe, not yet a result: what the editor states (Q10, commentary only) ] , background_color: rgb("#dae6fb") , icon_color: rgb("#0758E5") , icon: fa-info() , body_background_color: white ) ] = Chat and evidence, claim by claim #emph[Generated from the ledger with verdicts (W9).] Validated / not validated / refuted, with loci. A false premise makes an argument unsound and withdraws its justification; it does not make its conclusion false. = Limits #emph[To write.] Table OCR residue (open cells), missing volumes and sources, LLM-proposed relevance labels awaiting owner confirmation, the chat as a single specimen. = Conclusion: a reusable challenge #emph[To write.] The goal, the query set and the evidence space as a benchmark for evidence-bound answers. = Appendix: the transcript #emph[Included from `source/grok-chat.md` (verbatim, with the provenance header).] = Appendix: query set and Schemakarte #emph[Generated from `famulus-proof/eval/ziele/tabula-rudolphina.yaml` and the karten.] #bibliography(("bibliography.bib"))